3 $revision = "src/ae2cvs.pl 0.04.D001 2005/08/14 15:13:36 knight";
5 $copyright = "Copyright 2001, 2002, 2003, 2004, 2005 Steven Knight.";
8 # All rights reserved. This program is free software; you can
9 # redistribute and/or modify under the same terms as Perl itself.
17 use vars qw( @add_list @args @cleanup @copy_list @libraries
18 @mkdir_list @remove_list
20 $ae_copy $aedir $aedist
21 $cnum $comment $commit $common $copyright
22 $cvs_command $cvsmod $cvsroot
23 $delta $description $exec $help $indent $infile
24 $proj $pwd $quiet $revision
25 $summary $usedir $usepath );
33 print "ae2cvs: $revision\n";
41 Getopt::Long::Configure('no_ignore_case');
43 my $ret = GetOptions (
44 "aedist" => sub { $aedist = 1 },
45 "aegis" => sub { $aedist = 0 },
50 "library=s" => \@libraries,
51 "module=s" => \$cvsmod,
52 "noexecute" => sub { $exec = 0 },
53 "project=s" => \$proj,
55 "usedir=s" => \$usedir,
56 "v|version" => \&version,
57 "x|execute" => sub { $exec++ if ! defined $exec || $exec != 0 },
58 "X|EXECUTE" => sub { $exec = 2 if ! defined $exec || $exec != 0 },
61 Pod::Usage::pod2usage(-verbose => 0) if $help || ! $ret;
63 $exec = 0 if ! defined $exec;
66 $cvs_command = $cvsroot ? "cvs -d $cvsroot -Q" : "cvs -Q";
69 # Wrap up the $quiet logic in one place.
73 my $string = join('', @_);
74 $string =~ s/^/$indent/msg if $indent;
79 # Wrappers for executing various builtin Perl functions in
80 # accordance with the -n, -q and -x options.
92 my ($source, $dest) = @_;
93 printit "cp $source $dest\n";
104 chdir($dir) || die "ae2cvs: could not chdir($dir): $!";
110 printit "mkdir $dir\n";
117 # Put some input data through an external filter and capture the output.
120 my ($cmd, $input) = @_;
125 my $pid = open2(*READ, *WRITE, $cmd) || die "Cannot exec '$cmd': $!\n";
128 my $output = join('', <READ>);
134 # Parse a change description, in both 'aegis -l cd" and "aedist" formats.
136 # Returns an array containing the project name, the change number
137 # (if any), the delta number (if any), the SUMMARY, the DESCRIPTION
138 # and the lines describing the files in the change.
143 my ($p, $c, $d, $c_or_d, $sum, $desc, $filesection, @flines);
145 # The project name line comes after NAME in "aegis -l cd" format,
146 # and PROJECT in "aedist" format. In both cases, the project name
147 # and the change/delta name are separated a comma.
148 ($p = $output) =~ s/(?:NAME|PROJECT)\n([^\n]*)\n.*/$1/ms;
149 ($p, $c_or_d) = (split(/,/, $p));
151 # In "aegis -l cd" format, the project name actually comes after
152 # the string "Project" and is itself enclosed in double quotes.
153 $p =~ s/Project "([^"]*)"/$1/;
155 # The change or delta string was the right-hand side of the comma.
156 # "aegis -l cd" format spells it "Change 123." or "Delta 123." while
157 # "aedist" format spells it "change 123."
158 if ($c_or_d =~ /\s*[Cc]hange (\d+).*/) { $c = $1 };
159 if ($c_or_d =~ /\s*[Dd]elta (\d+).*/) { $d = $1 };
161 # The SUMMARY line is always followed the DESCRIPTION section.
162 # It seems to always be a single line, but we grab everything in
163 # between just in case.
164 ($sum = $output) =~ s/.*\nSUMMARY\n//ms;
165 $sum =~ s/\nDESCRIPTION\n.*//ms;
167 # The DESCRIPTION section is followed ARCHITECTURE in "aegis -l cd"
168 # format and by CAUSE in "aedist" format. Explicitly under it if the
169 # string is only "none," which means they didn't supply a description.
170 ($desc = $output) =~ s/.*\nDESCRIPTION\n//ms;
171 $desc =~ s/\n(ARCHITECTURE|CAUSE)\n.*//ms;
173 if ($desc eq "none" || $desc eq "none\n") { $desc = undef }
175 # The FILES section is followed by HISTORY in "aegis -l cd" format.
176 # It seems to be the last section in "aedist" format, but stripping
177 # a non-existent HISTORY section doesn't hurt.
178 ($filesection = $output) =~ s/.*\nFILES\n//ms;
179 $filesection =~ s/\nHISTORY\n.*//ms;
181 @flines = split(/\n/, $filesection);
183 ($p, $c, $d, $sum, $desc, \@flines)
192 # Fetch the file list either from our aedist input
193 # or directly from the project itself.
199 my $infile_redir = "";
201 if (! $infile || $infile eq "-") {
202 $contents = join('', <STDIN>);
204 open(FILE, "<$infile") || die "Cannot open '$infile': $!\n";
206 $contents = join('', <FILE>);
208 if (! File::Spec->file_name_is_absolute($infile)) {
209 $infile = File::Spec->catfile($pwd, $infile);
211 $infile_redir = " < $infile";
214 my $output = filter("aedist -l -unf", $contents);
215 my ($p, $c, $d, $s, $desc, $fl) = parse_change($output);
217 $proj = $p if ! defined $proj;
219 $description = $desc;
223 printit qq(MYTMP="/tmp/ae2cvs-ae.\$\$"\n),
226 printit q(perl -MMIME::Base64 -e 'undef $/; ($c = <>) =~ s/.*\n\n//ms; print decode_base64($c)'),
229 qq( | cpio -i -d --quiet\n);
231 push(@cleanup, $aedir);
233 $aedir = File::Spec->catfile(File::Spec->tmpdir, "ae2cvs-ae.$$");
235 push(@cleanup, $aedir);
240 $contents =~ s/.*\n\n//ms;
241 $contents = filter("zcat", decode_base64($contents));
243 open(CPIO, "|cpio -i -d --quiet");
244 print CPIO $contents;
249 foreach my $dest (@_) {
250 my $source = File::Spec->catfile($aedir, "src", $dest);
251 execute(qq(cp $source $dest));
255 $cnum = $ENV{AEGIS_CHANGE} if ! defined $cnum;
256 $proj = $ENV{AEGIS_PROJECT} if ! defined $proj;
258 $common = "-lib " . join(" -lib ", @libraries) if @libraries;
259 $common = "$common -proj $proj" if $proj;
261 my $output = `aegis -l cd $cnum -unf $common`;
262 my ($p, $c, $d, $s, $desc, $fl) = parse_change($output);
266 $description = $desc;
270 print STDERR "ae2cvs: No delta number, exiting.\n";
275 execute(qq(aegis -cp -ind -delta $delta $common @_));
280 $usedir = File::Spec->catfile(File::Spec->tmpdir, "ae2cvs.$$");
282 push(@cleanup, $usedir);
288 if (! File::Spec->file_name_is_absolute($usepath)) {
289 $usepath = File::Spec->catfile($pwd, $usepath);
292 if (! -d File::Spec->catfile($usedir, "CVS")) {
293 $cvsmod = (split(/\./, $proj))[0] if ! defined $cvsmod;
295 execute(qq($cvs_command co $cvsmod));
299 $usepath = File::Spec->catfile($usepath, $cvsmod);
303 # Figure out what we have to do to accomplish everything.
305 foreach (@filelines) {
306 my @arr = split(/\s+/, $_);
307 my $type = shift @arr; # source / test
308 my $act = shift @arr; # modify / create
311 if ($act eq "create" or $act eq "modify") {
312 # XXX Do we really only need to do this for
313 # ($act eq "create") files?
314 my (undef, $dirs, undef) = File::Spec->splitpath($file);
315 my $absdir = $usepath;
318 foreach $d (File::Spec->splitdir($dirs)) {
320 $absdir = File::Spec->catdir($absdir, $d);
321 $reldir = $reldir ? File::Spec->catdir($reldir, $d) : $d;
322 if (! -d $absdir && ! $seen_dir{$reldir}) {
323 $seen_dir{$reldir} = 1;
324 push(@mkdir_list, $reldir);
328 push(@copy_list, $file);
330 if ($act eq "create") {
331 push(@add_list, $file);
333 } elsif ($act eq "remove") {
334 push(@remove_list, $file);
336 print STDERR "Unsure how to '$act' the '$file' file.\n";
340 # Now go through and mkdir() the directories,
341 # adding them to the CVS tree as we do.
344 printit qq(# The following "mkdir" and "cvs -Q add" calls are not\n),
345 qq(# necessary for any directories that already exist in the\n),
346 qq(# CVS tree but which aren't present locally.\n);
348 foreach (@mkdir_list) {
350 printit qq(if test ! -d $_; then\n);
354 execute(qq($cvs_command add $_));
361 printit qq(# End of directory creation.\n);
365 # Copy in any files in the change, before we try to "cvs add" them.
366 $ae_copy->(@copy_list) if @copy_list;
369 execute(qq($cvs_command add @add_list));
373 execute(qq(rm -f @remove_list));
374 execute(qq($cvs_command remove @remove_list));
377 # Last, commit the whole bunch.
379 $comment .= "\n" . $description if $description;
380 $commit = qq($cvs_command commit -m '$comment' .);
382 printit qq(# Execute the following to commit the changes:\n),
395 foreach $dir (@cleanup) {
396 printit "rm -rf $dir\n";
399 # print STDERR "unlink($_)\n" if (!-d $_);
400 # print STDERR "rmdir($_)\n" if (-d $_ && $_ ne ".");
401 unlink($_) if (!-d $_);
402 rmdir($_) if (-d $_ && $_ ne ".");
405 rmdir($dir) || print STDERR "Could not remove $dir: $!\n";
414 ae2cvs - convert an Aegis change set to CVS commands
418 ae2cvs [-aedist|-aegis] [-c change] [-d cvs_root] [-f file] [-l lib]
419 [-m module] [-n] [-p proj] [-q] [-u dir] [-v] [-x] [-X]
421 -aedist use aedist format from input (default)
422 -aegis query aegis repository directly
423 -c change change number
424 -d cvs_root CVS root directory
425 -f file read aedist from file ('-' == stdin)
426 -l lib Aegis library directory
430 -q quiet, don't print commands
431 -u dir use dir for CVS checkin
432 -v print version string and exit
433 -x execute the commands, but don't commit;
434 two or more -x options commit changes
435 -X execute the commands and commit changes
439 The C<ae2cvs> utility can convert an Aegis change into a set of CVS (and
440 other) commands to make the corresponding change(s) to a carbon-copy CVS
441 repository. This can be used to keep a front-end CVS repository in sync
442 with changes made to an Aegis project, either manually or automatically
443 using the C<integrate_pass_notify_command> attribute of the Aegis
446 By default, C<ae2cvs> makes no changes to any software, and only prints
447 out the necessary commands. These commands can be examined first for
448 safety, and then fed to any Bourne shell variant (sh, ksh, or bash) to
449 make the actual CVS changes.
451 An option exists to have C<ae2cvs> execute the commands directly.
455 The C<ae2cvs> utility supports the following options:
461 Reads an aedist change set.
462 By default, the change set is read from standard input,
463 or a file specified with the C<-f> option.
467 Reads the change directly from the Aegis repository
468 by executing the proper C<aegis> commands.
472 Specify the Aegis change number to be used.
473 The value of the C<AEGIS_CHANGE> environment variable
478 Specify the CVS root directory to be used.
479 This option is passed explicitly to each executed C<cvs> command.
480 The default behavior is to omit any C<-d> options
481 and let the executed C<cvs> commands use the
482 C<CVSROOT> environment variable as they normally would.
486 Reads the aedist change set from the specified C<file>,
487 or from standard input if C<file> is C<'-'>.
491 Specifies an Aegis library directory to be searched for global states
492 files and user state files.
496 Specifies the name of the CVS module to be brought up-to-date.
497 The default is to use the Aegis project name,
498 minus any branch numbers;
499 for example, given an Aegis project name of C<foo-cmd.0.1>,
500 the default CVS module name is C<foo-cmd>.
504 No execute. Commands are printed (including a command for a final
505 commit of changes), but not executed. This is the default.
509 Specifies the name of the Aegis project from which this change is taken.
510 The value of the C<AEGIS_PROJECT> environment variable
515 Quiet. Commands are not printed.
519 Use the already checked-out CVS tree that exists at C<dir>
520 for the checkins and commits.
521 The default is to use a separately-created temporary directory.
525 Print the version string and exit.
529 Execute the commands to bring the CVS repository up to date,
530 except for the final commit of the changes. Two or more
531 C<-x> options will cause the change to be committed.
535 Execute the commands to bring the CVS repository up to date,
536 including the final commit of the changes.
540 =head1 ENVIRONMENT VARIABLES
546 Specifies any options to be used to initialize
547 the C<ae2cvs> utility.
548 Options on the command line override these values.
554 Steven Knight (knight at baldmt dot com)
558 If errors occur during the execution of the Aegis or CVS commands, and
559 the -X option is used, a partial change (consisting of those files for
560 which the command(s) succeeded) will be committed. It would be safer to
561 generate code to detect the error and print a warning.
563 When a file has been deleted in Aegis, the standard whiteout file can
564 cause a regex failure in this script. It doesn't necessarily happen all
565 the time, though, so this needs more investigation.
569 Add an explicit test for using ae2cvs in the Aegis
570 integrate_pass_notify_command field to support fully keeping a
571 repository in sync automatically.
575 Copyright 2001, 2002, 2003, 2004, 2005 Steven Knight.