source: ProjectBuilder/devel/pb-modules/lib/ProjectBuilder/VCS.pm@ 1554

Last change on this file since 1554 was 1554, checked in by Bruno Cornec, 12 years ago
  • pb: Update documentation, the newproj docs were missing an important argument. Expanded out the newproj documentation. Fixed the cms2build documentation to match the same style as the other documentation. (Eric Anderson)
  • CMS.pm/VCS.pm: Tolerate the file: scheme -- it was tolerated in some places but not others. (Eric Anderson)
File size: 16.2 KB
Line 
1#!/usr/bin/perl -w
2#
3# Project Builder VCS module
4# VCS subroutines brought by the the Project-Builder project
5# which can be easily used across projects needing to perform
6# VCS related operations
7#
8# $Id$
9#
10# Copyright B. Cornec 2007-2012
11# Provided under the GPL v2
12
13package ProjectBuilder::VCS;
14
15use strict 'vars';
16use Carp 'confess';
17use Data::Dumper;
18use English;
19use File::Basename;
20use File::Copy;
21use POSIX qw(strftime);
22use lib qw (lib);
23use ProjectBuilder::Version;
24use ProjectBuilder::Base;
25use ProjectBuilder::Conf;
26
27# Inherit from the "Exporter" module which handles exporting functions.
28
29use vars qw($VERSION $REVISION @ISA @EXPORT);
30use Exporter;
31
32# Export, by default, all the functions into the namespace of
33# any code which uses this module.
34
35our @ISA = qw(Exporter);
36our @EXPORT = qw(pb_vcs_export pb_vcs_get_uri pb_vcs_copy pb_vcs_checkout pb_vcs_up pb_vcs_checkin pb_vcs_isdiff pb_vcs_add pb_vcs_cmd);
37($VERSION,$REVISION) = pb_version_init();
38
39=pod
40
41=head1 NAME
42
43ProjectBuilder::VCS, part of the project-builder.org
44
45=head1 DESCRIPTION
46
47This modules provides version control system functions.
48
49=head1 USAGE
50
51=over 4
52
53=item B<pb_vcs_export>
54
55This function exports a VCS content to a directory.
56The first parameter is the URL of the VCS content.
57The second parameter is the directory in which it is locally exposed (result of a checkout). If undef, then use the original VCS content.
58The third parameter is the directory where we want to deliver it (result of export).
59It returns the original tar file if we need to preserve it and undef if we use the produced one.
60
61=cut
62
63sub pb_vcs_export {
64
65my $uri = shift;
66my $source = shift;
67my $destdir = shift;
68my $tmp;
69my $tmp1;
70
71pb_log(1,"pb_vcs_export uri: $uri - destdir: $destdir\n");
72pb_log(1,"pb_vcs_export source: $source\n") if (defined $source);
73my @date = pb_get_date();
74# If it's not flat, then we have a real uri as source
75my ($scheme, $account, $host, $port, $path) = pb_get_uri($uri);
76my $vcscmd = pb_vcs_cmd($scheme);
77$uri = pb_vcs_mod_socks($uri);
78
79if ($scheme =~ /^svn/) {
80 if (defined $source) {
81 if (-d $source) {
82 $tmp = $destdir;
83 } else {
84 $tmp = "$destdir/".basename($source);
85 }
86 $source = pb_vcs_mod_htftp($source,"svn");
87 pb_system("$vcscmd export $source $tmp","Exporting $source from $scheme to $tmp ");
88 } else {
89 $uri = pb_vcs_mod_htftp($uri,"svn");
90 pb_system("$vcscmd export $uri $destdir","Exporting $uri from $scheme to $destdir ");
91 }
92} elsif ($scheme eq "svk") {
93 my $src = $source;
94 if (defined $source) {
95 if (-d $source) {
96 $tmp = $destdir;
97 } else {
98 $tmp = "$destdir/".basename($source);
99 $src = dirname($source);
100 }
101 $source = pb_vcs_mod_htftp($source,"svk");
102 # This doesn't exist !
103 # pb_system("$vcscmd export $path $tmp","Exporting $path from $scheme to $tmp ");
104 pb_log(4,"$uri,$source,$destdir,$scheme, $account, $host, $port, $path,$tmp");
105 if (-d $source) {
106 pb_system("mkdir -p $tmp ; cd $tmp; tar -cf - -C $source . | tar xf -","Exporting $source from $scheme to $tmp ");
107 } else {
108 # If source is file do not use -C with source
109 pb_system("mkdir -p ".dirname($tmp)." ; cd ".dirname($tmp)."; tar -cf - -C $src ".basename($source)." | tar xf -","Exporting $src/".basename($source)." from $scheme to $tmp ");
110 }
111 } else {
112 # Look at svk admin hotcopy
113 die "Unable to export from svk without a source defined";
114 }
115} elsif ($scheme eq "dir") {
116 pb_system("cp -r $path $destdir","Copying $uri from DIR to $destdir ");
117} elsif (($scheme eq "http") || ($scheme eq "ftp")) {
118 my $f = basename($path);
119 unlink "$ENV{'PBTMP'}/$f";
120 pb_system("$vcscmd $ENV{'PBTMP'}/$f $uri","Downloading $uri with $vcscmd to $ENV{'PBTMP'}/$f\n");
121 # We want to preserve the original tar file
122 pb_vcs_export("file://$ENV{'PBTMP'}/$f",$source,$destdir);
123 return("$ENV{'PBTMP'}/$f");
124} elsif ($scheme =~ /^file/) {
125 eval
126 {
127 require File::MimeInfo;
128 File::MimeInfo->import();
129 };
130 if ($@) {
131 # File::MimeInfo not found
132 die("ERROR: Install File::MimeInfo to handle scheme $scheme\n");
133 }
134
135 my $mm = mimetype($path);
136 pb_log(2,"mimetype: $mm\n");
137
138 # Check whether the file is well formed
139 # (containing already a directory with the project-version name)
140 #
141 # If it's not the case, we try to adapt, but distro needing
142 # to verify the checksum will have issues (Fedora)
143 # Then upstream should be notified that they need to change their rules
144 # This doesn't apply to patches or additional sources of course.
145 my ($pbwf) = pb_conf_get_if("pbwf");
146 if ((defined $pbwf) && (defined $pbwf->{$ENV{'PBPROJ'}}) && ($path !~ /\/pbpatch\//) && ($path !~ /\/pbsrc\//)) {
147 $destdir = dirname($destdir);
148 pb_log(2,"This is a well-formed file so destdir is now $destdir\n");
149 }
150 pb_mkdir_p($destdir);
151
152 if ($mm =~ /\/x-bzip-compressed-tar$/) {
153 # tar+bzip2
154 pb_system("cd $destdir ; tar xfj $path","Extracting $path in $destdir ");
155 } elsif ($mm =~ /\/x-lzma-compressed-tar$/) {
156 # tar+lzma
157 pb_system("cd $destdir ; tar xfY $path","Extracting $path in $destdir ");
158 } elsif ($mm =~ /\/x-compressed-tar$/) {
159 # tar+gzip
160 pb_system("cd $destdir ; tar xfz $path","Extracting $path in $destdir ");
161 } elsif ($mm =~ /\/x-tar$/) {
162 # tar
163 pb_system("cd $destdir ; tar xf $path","Extracting $path in $destdir ");
164 } elsif ($mm =~ /\/zip$/) {
165 # zip
166 pb_system("cd $destdir ; unzip $path","Extracting $path in $destdir ");
167 } else {
168 # simple file: copy it (patch e.g.)
169 copy($path,$destdir);
170 }
171} elsif ($scheme =~ /^hg/) {
172 if (defined $source) {
173 if (-d $source) {
174 $tmp = $destdir;
175 } else {
176 $tmp = "$destdir/".basename($source);
177 }
178 $source = pb_vcs_mod_htftp($source,"hg");
179 pb_system("cd $source ; $vcscmd archive $tmp","Exporting $source from Mercurial to $tmp ");
180 } else {
181 $uri = pb_vcs_mod_htftp($uri,"hg");
182 pb_system("$vcscmd clone $uri $destdir","Exporting $uri from Mercurial to $destdir ");
183 }
184} elsif ($scheme =~ /^git/) {
185 if (defined $source) {
186 if (-d $source) {
187 $tmp = $destdir;
188 } else {
189 $tmp = "$destdir/".basename($source);
190 }
191 $source = pb_vcs_mod_htftp($source,"git");
192 pb_system("cd $source ; $vcscmd archive --format=tar HEAD | (mkdir $tmp && cd $tmp && tar xf -)","Exporting $source/HEAD from GIT to $tmp ");
193 } else {
194 $uri = pb_vcs_mod_htftp($uri,"git");
195 pb_system("$vcscmd clone $uri $destdir","Exporting $uri from GIT to $destdir ");
196 }
197} elsif ($scheme =~ /^cvs/) {
198 # CVS needs a relative path !
199 my $dir=dirname($destdir);
200 my $base=basename($destdir);
201 if (defined $source) {
202 # CVS also needs a modules name not a dir
203 $tmp1 = basename($source);
204 } else {
205 # Probably not right, should be checked, but that way I'll notice it :-)
206 pb_log(0,"You're in an untested part of project-builder.org, please report any result upstream\n");
207 $tmp1 = $uri;
208 }
209 # If we're working on the CVS itself
210 my $cvstag = basename($ENV{'PBROOTDIR'});
211 my $cvsopt = "";
212 if ($cvstag eq "cvs") {
213 my $pbdate = strftime("%Y-%m-%d %H:%M:%S", @date);
214 $cvsopt = "-D \"$pbdate\"";
215 } else {
216 # we're working on a tag which should be the last part of PBROOTDIR
217 $cvsopt = "-r $cvstag";
218 }
219 pb_system("cd $dir ; $vcscmd -d $account\@$host:$path export $cvsopt -d $base $tmp1","Exporting $tmp1 from $source under CVS to $destdir ");
220} else {
221 die "cms $scheme unknown";
222}
223return(undef);
224}
225
226=item B<pb_vcs_get_uri>
227
228This function is only called with a real VCS system and gives the URL stored in the checked out directory.
229The first parameter is the schema of the VCS systems (svn, cvs, svn+ssh, ...)
230The second parameter is the directory in which it is locally exposed (result of a checkout).
231
232=cut
233
234sub pb_vcs_get_uri {
235
236my $scheme = shift;
237my $dir = shift;
238
239my $res = "";
240my $void = "";
241my $vcscmd = pb_vcs_cmd($scheme);
242
243if ($scheme =~ /^svn/) {
244 open(PIPE,"LANGUAGE=C $vcscmd info $dir |") || return("");
245 while (<PIPE>) {
246 ($void,$res) = split(/^URL:/) if (/^URL:/);
247 }
248 $res =~ s/^\s*//;
249 close(PIPE);
250 chomp($res);
251} elsif ($scheme =~ /^svk/) {
252 open(PIPE,"LANGUAGE=C $vcscmd info $dir |") || return("");
253 my $void2 = "";
254 while (<PIPE>) {
255 ($void,$void2,$res) = split(/ /) if (/^Depot/);
256 }
257 $res =~ s/^\s*//;
258 close(PIPE);
259 chomp($res);
260} elsif ($scheme =~ /^hg/) {
261 open(HGRC,".hg/hgrc/") || return("");
262 while (<HGRC>) {
263 ($void,$res) = split(/^default.*=/) if (/^default.*=/);
264 }
265 close(HGRC);
266 chomp($res);
267} elsif ($scheme =~ /^git/) {
268 open(GIT,"git --git-dir=$dir/.git remote -v |") || return("");
269 while (<GIT>) {
270 next unless (/^origin\s+(\S+) \(push\)$/);
271 return $1;
272 }
273 close(GIT);
274 warn "Unable to find origin remote for $dir";
275 return "";
276} elsif ($scheme =~ /^cvs/) {
277 # This path is always the root path of CVS, but we may be below
278 open(FILE,"$dir/CVS/Root") || die "$dir isn't CVS controlled";
279 $res = <FILE>;
280 chomp($res);
281 close(FILE);
282 # Find where we are in the tree
283 my $rdir = $dir;
284 while ((! -d "$rdir/CVSROOT") && ($rdir ne "/")) {
285 $rdir = dirname($rdir);
286 }
287 die "Unable to find a CVSROOT dir in the parents of $dir" if (! -d "$rdir/CVSROOT");
288 #compute our place under that root dir - should be a relative path
289 $dir =~ s|^$rdir||;
290 my $suffix = "";
291 $suffix = "$dir" if ($dir ne "");
292
293 my $prefix = "";
294 if ($scheme =~ /ssh/) {
295 $prefix = "cvs+ssh://";
296 } else {
297 $prefix = "cvs://";
298 }
299 $res = $prefix.$res.$suffix;
300} else {
301 die "cms $scheme unknown";
302}
303pb_log(1,"pb_vcs_get_uri returns $res\n");
304return($res);
305}
306
307=item B<pb_vcs_copy>
308
309This function copies a VCS content to another.
310The first parameter is the schema of the VCS systems (svn, cvs, svn+ssh, ...)
311The second parameter is the URL of the original VCS content.
312The third parameter is the URL of the destination VCS content.
313
314Only coded for SVN now as used for pbconf itself not the project
315
316=cut
317
318sub pb_vcs_copy {
319my $scheme = shift;
320my $oldurl = shift;
321my $newurl = shift;
322my $vcscmd = pb_vcs_cmd($scheme);
323$oldurl = pb_vcs_mod_socks($oldurl);
324$newurl = pb_vcs_mod_socks($newurl);
325
326if ($scheme =~ /^svn/) {
327 $oldurl = pb_vcs_mod_htftp($oldurl,"svn");
328 $newurl = pb_vcs_mod_htftp($newurl,"svn");
329 pb_system("$vcscmd copy -m \"Creation of $newurl from $oldurl\" $oldurl $newurl","Copying $oldurl to $newurl ");
330} elsif ($scheme =~ /^(flat)|(ftp)|(http)|(file)\b/o) {
331 # Nothing to do.
332} else {
333 die "cms $scheme unknown for project management";
334}
335}
336
337=item B<pb_vcs_checkout>
338
339This function checks a VCS content out to a directory.
340The first parameter is the schema of the VCS systems (svn, cvs, svn+ssh, ...)
341The second parameter is the URL of the VCS content.
342The third parameter is the directory where we want to deliver it (result of export).
343
344=cut
345
346sub pb_vcs_checkout {
347my $scheme = shift;
348my $url = shift;
349my $destination = shift;
350my $vcscmd = pb_vcs_cmd($scheme);
351$url = pb_vcs_mod_socks($url);
352
353if ($scheme =~ /^svn/) {
354 $url = pb_vcs_mod_htftp($url,"svn");
355 pb_system("$vcscmd co $url $destination","Checking out $url to $destination ");
356} elsif ($scheme =~ /^svk/) {
357 $url = pb_vcs_mod_htftp($url,"svk");
358 pb_system("$vcscmd co $url $destination","Checking out $url to $destination ");
359} elsif ($scheme =~ /^hg/) {
360 $url = pb_vcs_mod_htftp($url,"hg");
361 pb_system("$vcscmd clone $url $destination","Checking out $url to $destination ");
362} elsif ($scheme =~ /^git/) {
363 $url = pb_vcs_mod_htftp($url,"git");
364 pb_system("$vcscmd clone $url $destination","Checking out $url to $destination ");
365} elsif (($scheme eq "ftp") || ($scheme eq "http")) {
366 return;
367} elsif ($scheme =~ /^cvs/) {
368 my ($scheme, $account, $host, $port, $path) = pb_get_uri($url);
369
370 # If we're working on the CVS itself
371 my $cvstag = basename($ENV{'PBROOTDIR'});
372 my $cvsopt = "";
373 if ($cvstag eq "cvs") {
374 my @date = pb_get_date();
375 my $pbdate = strftime("%Y-%m-%d %H:%M:%S", @date);
376 $cvsopt = "-D \"$pbdate\"";
377 } else {
378 # we're working on a tag which should be the last part of PBROOTDIR
379 $cvsopt = "-r $cvstag";
380 }
381 pb_mkdir_p("$destination");
382 pb_system("cd $destination ; $vcscmd -d $account\@$host:$path co $cvsopt .","Checking out $url to $destination ");
383} elsif ($scheme =~ /^file/) {
384 pb_vcs_export($url,undef,$destination);
385} else {
386 die "cms $scheme unknown";
387}
388}
389
390=item B<pb_vcs_up>
391
392This function updates a local directory with the VCS content.
393The first parameter is the schema of the VCS systems (svn, cvs, svn+ssh, ...)
394The second parameter is the list of directory to update.
395
396=cut
397
398sub pb_vcs_up {
399my $scheme = shift;
400my @dir = @_;
401my $vcscmd = pb_vcs_cmd($scheme);
402
403if ($scheme =~ /^((svn)|(cvs)|(svk))/o) {
404 pb_system("$vcscmd up ".join(' ',@dir),"Updating ".join(' ',@dir));
405} elsif ($scheme =~ /^((hg)|(git))/o) {
406 foreach my $d (@dir) {
407 pb_system("(cd $d && $vcscmd pull)", "Updating $d ");
408 }
409} elsif ($scheme =~ /^(flat)|(ftp)|(http)|(file)\b/o) {
410 # Nothing to do.
411} else {
412 die "cms $scheme unknown";
413}
414}
415
416=item B<pb_vcs_checkin>
417
418This function updates a VCS content from a local directory.
419The first parameter is the schema of the VCS systems (svn, cvs, svn+ssh, ...)
420The second parameter is the directory to update from.
421The third parameter is the comment to pass during the commit
422
423=cut
424
425sub pb_vcs_checkin {
426my $scheme = shift;
427my $dir = shift;
428my $msg = shift;
429my $vcscmd = pb_vcs_cmd($scheme);
430
431if ($scheme =~ /^((svn)|(cvs)|(svk))/o) {
432 pb_system("cd $dir && $vcscmd ci -m \"$msg\" .","Checking in $dir ");
433} elsif ($scheme =~ /^git/) {
434 pb_system("cd $dir && $vcscmd commit -a -m \"$msg\"", "Checking in $dir ");
435} elsif ($scheme =~ /^(flat)|(ftp)|(http)|(file)\b/o) {
436 # Nothing to do.
437} else {
438 die "cms $scheme unknown";
439}
440pb_vcs_up($scheme,$dir);
441}
442
443=item B<pb_vcs_add>
444
445This function adds to a VCS content from a local directory.
446The first parameter is the schema of the VCS systems (svn, cvs, svn+ssh, ...)
447The second parameter is a list of directory/file to add.
448
449=cut
450
451sub pb_vcs_add {
452my $scheme = shift;
453my @f = @_;
454my $vcscmd = pb_vcs_cmd($scheme);
455
456if ($scheme =~ /^((hg)|(git)|(svn)|(svk)|(cvs))/o) {
457 pb_system("$vcscmd add ".join(' ',@f),"Adding ".join(' ',@f)." to VCS ");
458} elsif ($scheme =~ /^(flat)|(ftp)|(http)|(file)\b/o) {
459 # Nothing to do.
460} else {
461 die "cms $scheme unknown";
462}
463pb_vcs_up($scheme,@f);
464}
465
466=item B<pb_vcs_isdiff>
467
468This function returns a integer indicating the number f differences between the VCS content and the local directory where it's checked out.
469The first parameter is the schema of the VCS systems (svn, cvs, svn+ssh, ...)
470The second parameter is the directory to consider.
471
472=cut
473
474sub pb_vcs_isdiff {
475my $scheme = shift;
476my $dir =shift;
477my $vcscmd = pb_vcs_cmd($scheme);
478my $l = undef;
479
480if ($scheme =~ /^((svn)|(cvs)|(svk))/o) {
481 open(PIPE,"$vcscmd diff $dir |") || die "Unable to get $vcscmd diff from $dir";
482 $l = 0;
483 while (<PIPE>) {
484 # Skipping normal messages in case of CVS
485 next if (/^cvs diff:/);
486 $l++;
487 }
488} elsif ($scheme =~ /^(flat)|(ftp)|(http)|(file)\b/o) {
489 $l = 0;
490} else {
491 die "cms $scheme unknown";
492}
493pb_log(1,"pb_vcs_isdiff returns $l\n");
494return($l);
495}
496
497sub pb_vcs_mod_htftp {
498
499my $url = shift;
500my $proto = shift;
501
502$url =~ s/^$proto\+((ht|f)tp[s]*):/$1:/;
503pb_log(1,"pb_vcs_mod_htftp returns $url\n");
504return($url);
505}
506
507sub pb_vcs_mod_socks {
508
509my $url = shift;
510
511$url =~ s/^([A-z0-9]+)\+(socks):/$1:/;
512pb_log(1,"pb_vcs_mod_socks returns $url\n");
513return($url);
514}
515
516
517sub pb_vcs_cmd {
518
519my $scheme = shift;
520my $cmd = "";
521
522# If there is a socks proxy to use
523if ($scheme =~ /socks/) {
524 # Get the socks proxy command from the conf file
525 my ($pbsockscmd) = pb_conf_get("pbsockscmd");
526 $cmd = "$pbsockscmd->{$ENV{'PBPROJ'}} ";
527}
528
529if ($scheme =~ /hg/) {
530 return($cmd."hg")
531} elsif ($scheme =~ /git/) {
532 return($cmd."git")
533} elsif ($scheme =~ /svn/) {
534 return($cmd."svn")
535} elsif ($scheme =~ /svk/) {
536 return($cmd."svk")
537} elsif ($scheme =~ /cvs/) {
538 return($cmd."cvs")
539} elsif (($scheme =~ /http/) || ($scheme =~ /ftp/)) {
540 my $command = pb_check_req("wget",1);
541 if (-x $command) {
542 return($cmd."$command -nv -O ");
543 } else {
544 $command = pb_check_req("curl",1);
545 if (-x $command) {
546 return($cmd."$command -o ");
547 } else {
548 die "Unable to handle $scheme.\nNo wget/curl available, please install one of those";
549 }
550 }
551} else {
552 return($cmd);
553}
554}
555
556
557
558=back
559
560=head1 WEB SITES
561
562The main Web site of the project is available at L<http://www.project-builder.org/>. Bug reports should be filled using the trac instance of the project at L<http://trac.project-builder.org/>.
563
564=head1 USER MAILING LIST
565
566None exists for the moment.
567
568=head1 AUTHORS
569
570The Project-Builder.org team L<http://trac.project-builder.org/> lead by Bruno Cornec L<mailto:bruno@project-builder.org>.
571
572=head1 COPYRIGHT
573
574Project-Builder.org is distributed under the GPL v2.0 license
575described in the file C<COPYING> included with the distribution.
576
577=cut
578
5791;
Note: See TracBrowser for help on using the repository browser.