5 Getopt
::Long
::Configure
("bundling", "pass_through");
7 use File
::Temp
qw(tempfile tempdir);
10 ################################################################################
13 # Whether or not to be chatty about what we're doing.
14 # Set this on the command line with --verbose.
17 # A horizontal rule for formatting lines.
18 my $ss = "--------------------------------------------------------------------";
20 # Whether or not to continue when we encounter a potentially serious problem.
21 # Set this on the command line with --live-dangerously.
24 # The branches to land on.
25 # Set this on the command line once for each branch with --branch <name>.
28 # Convenient shorthand for --branch HEAD and --branch MOZILLA_1_8_BRANCH.
29 # Set these on the command line with --trunk and --moz18.
33 # The branches to land on if the user doesn't specify a branch.
34 my @DEFAULT_BRANCHES = qw(HEAD MOZILLA_1_8_BRANCH);
36 # The CVS options. Some of these may not make sense in the context
37 # of this script. Use them at your own risk. Note that -f and -r are both
38 # CVS options and CVS commit options (i.e. they can go either before
39 # the command as general CVS options or after the commit command as different
40 # commit-specific options). To avoid ambiguity, you must specify
41 # the CVS options as --cvs-f and --cvs-r.
42 our $CVS_OPTION_allow_root;
62 # The CVS commit options: -l -R -r -F <file> -f and -m.
63 # Some of these may not make sense in the context of this script.
64 # Use them at your own risk.
65 our $CVS_COMMIT_OPTION_l;
66 our $CVS_COMMIT_OPTION_R;
67 our $CVS_COMMIT_OPTION_r;
68 our $CVS_COMMIT_OPTION_F;
69 our $CVS_COMMIT_OPTION_f;
70 our $CVS_COMMIT_OPTION_m;
72 our @CVS_COMMIT_OPTIONS;
74 # Retrieve configuration from a config file, if any. Config files are just
75 # regular Perl files and can override the values of all configuration variables
76 # declared above with "our".
78 if (-e
".xcconfig") { $cfg_file = ".xcconfig" }
79 elsif (-e
"~/.xcconfig") { $cfg_file = "~/.xcconfig" }
81 my $return = do $cfg_file;
82 die "couldn't parse $cfg_file: $@" if $@
;
83 die "couldn't do $cfg_file: $!" unless defined $return;
84 die "couldn't run $cfg_file" unless $return;
87 # Parse options from the command line.
89 # Options specific to this script.
90 "verbose" => \
$VERBOSE,
93 "branch=s" => \
@branches,
94 "live-dangerously" => \
$DOUBLEOH7,
96 # CVS options (those that go between "cvs" and "commit").
97 "allow-root=s" => \
$CVS_OPTION_allow_root,
98 "a" => \
$CVS_OPTION_a,
99 "b=s" => \
$CVS_OPTION_b,
100 "T=s" => \
$CVS_OPTION_T,
101 "d=s" => \
$CVS_OPTION_d,
102 "e=s" => \
$CVS_OPTION_e,
103 "cvs-f" => \
$CVS_OPTION_f,
104 "n" => \
$CVS_OPTION_n,
105 "Q" => \
$CVS_OPTION_Q,
106 "q" => \
$CVS_OPTION_q,
107 "cvs-r" => \
$CVS_OPTION_r,
108 "s" => \
$CVS_OPTION_s,
109 "t" => \
$CVS_OPTION_t,
110 "v|version" => \
$CVS_OPTION_v,
111 "w" => \
$CVS_OPTION_w,
112 "x" => \
$CVS_OPTION_x,
113 "z" => \
$CVS_OPTION_z,
115 # CVS commit options (those that go after "commit").
116 "l" => \
$CVS_COMMIT_OPTION_l,
117 "R" => \
$CVS_COMMIT_OPTION_R,
118 "r" => \
$CVS_COMMIT_OPTION_r,
119 "F=s" => \
$CVS_COMMIT_OPTION_F,
120 "f" => \
$CVS_COMMIT_OPTION_f,
121 "m=s" => \
$CVS_COMMIT_OPTION_m,
125 # The rest of the command line should be files or directories to commit.
126 # You can also leave it blank, in which case it'll check the current directory,
127 # just like "cvs commit" does.
130 $CVS_OPTION_allow_root ?
("--allow-root", $CVS_OPTION_allow_root) : (),
131 $CVS_OPTION_a ?
"-a" : (),
132 $CVS_OPTION_b ?
("-b", $CVS_OPTION_b) : (),
133 $CVS_OPTION_T ?
("-T", $CVS_OPTION_T) : (),
134 $CVS_OPTION_d ?
("-d", $CVS_OPTION_d) : (),
135 $CVS_OPTION_e ?
("-e", $CVS_OPTION_e) : (),
136 $CVS_OPTION_f ?
"-f" : (),
137 $CVS_OPTION_n ?
"-n" : (),
138 $CVS_OPTION_Q ?
"-Q" : (),
139 $CVS_OPTION_q ?
"-q" : (),
140 $CVS_OPTION_r ?
"-r" : (),
141 $CVS_OPTION_s ?
"-s" : (),
142 $CVS_OPTION_t ?
"-t" : (),
143 $CVS_OPTION_v ?
"-v" : (),
144 $CVS_OPTION_w ?
"-w" : (),
145 $CVS_OPTION_x ?
"-x" : (),
146 $CVS_OPTION_z ?
("-z", $CVS_OPTION_z) : (),
149 push(@CVS_COMMIT_OPTIONS,
150 $CVS_COMMIT_OPTION_l ?
"-l" : (),
151 $CVS_COMMIT_OPTION_R ?
"-R" : (),
152 $CVS_COMMIT_OPTION_r ?
"-r" : (),
153 $CVS_COMMIT_OPTION_F ?
("-F", $CVS_COMMIT_OPTION_F) : (),
154 $CVS_COMMIT_OPTION_f ?
"-f" : (),
155 $CVS_COMMIT_OPTION_m ?
("-m", $CVS_COMMIT_OPTION_m) : (),
159 ################################################################################
162 # Duplicate the VERBOSE filehandle to STDOUT if we're being verbose;
163 # otherwise point it to /dev/null.
164 my $devnull = File
::Spec
->devnull();
165 open(VERBOSE
, $VERBOSE ?
">-" : ">$devnull") or warn "Can't output verbose: $!";
168 ################################################################################
169 # Get Modified Files and Current Branch
171 my $files = get_modified_files
(\
@ARGV);
172 if (scalar(keys(%$files)) == 0) {
173 die "*** Didn't find any modified files.\n";
176 print VERBOSE
"*** Modified Files:\n " .
177 join("\n ", sort(keys(%$files))) . "\n";
180 my $current_branch = get_current_branch
($files);
181 print VERBOSE
"*** Working Branch:\n $current_branch\n";
184 ################################################################################
185 # Get Branches to Land On
187 # Figure out what branches the user wants to land on. Branches can be specified
188 # via "--branch <name>" or the "--trunk" and "--moz18" shortcuts. If the user
189 # doesn't specify any branches, we land on the trunk and the MOZILLA_1_8_BRANCH.
190 push(@branches, "HEAD") if $TRUNK and !grep($_ eq "HEAD", @branches);
191 push(@branches, "MOZILLA_1_8_BRANCH")
192 if $MOZ18 and !grep($_ eq "MOZILLA_1_8_BRANCH", @branches);
193 push(@branches, @DEFAULT_BRANCHES) if scalar(@branches) == 0;
194 print VERBOSE
"*** Committing to Branches:\n " . join("\n ", @branches) .
197 ################################################################################
200 # Make sure the changes apply cleanly to all branches the user wants
203 foreach my $branch (@branches) {
204 print VERBOSE
"*** Checking for conflicts on $branch... ";
205 foreach my $spec (sort(keys(%$files))) {
206 my ($rv, $output, $errors) =
207 run_cvs
("update", [cvs_branch
($branch), $spec], 1, 1);
209 # These are spurious errors that go away once we check in
210 # the removal to the working branch, so we can ignore them.
211 # XXX Can we really? Might they not also occur in other situations
212 # where we shouldn't ignore them?
213 if ($errors =~ m/removed $spec was modified by second party/) {
214 print VERBOSE
"(we can safely ignore this conflict)\n";
217 push(@conflicts, $branch);
221 if (scalar(@conflicts) > 0) {
222 die "Conflicts found on " . join(", ", @conflicts) . ".\n"
223 . "Please resolve them, then try your commit again.\n";
226 print VERBOSE
"No conflicts found; good.\n";
230 ################################################################################
231 # Land on Some Branch
233 # From now on, if we encounter an error, we should try to return the user's tree
234 # to its original state, so override the die handler with a function that tries
235 # to CVS update the tree back to the original working branch.
236 local $SIG{__DIE__
} = sub {
240 print VERBOSE
"*** Returning your tree to its original working branch... ";
241 run_cvs
("update", [cvs_branch
($current_branch), keys(%$files)]);
245 # We gotta land somewhere once and then merge those changes into other branches.
247 if (grep($_ eq $current_branch, @branches)) {
248 # The changes are landing on the current branch. Groovy, let's land
249 # there first. It matters for additions and removals, I think.
250 $land_branch = $current_branch;
253 # Just land on the first branch in the list.
254 $land_branch = $branches[0];
255 print VERBOSE
"*** Switching to $land_branch... ";
256 run_cvs
("update", [cvs_branch
($land_branch), keys(%$files)]);
259 print VERBOSE
"*** Committing to $land_branch... ";
260 my ($rv, $output, $errors) =
261 run_cvs
("commit", [@CVS_COMMIT_OPTIONS, keys(%$files)]);
264 ################################################################################
265 # Extract Commit Info
267 print VERBOSE
"*** Extracting commit info.\n";
268 my @lines = (split/\n/, $output);
269 for ( my $i = 0 ; $i < scalar(@lines); $i++ ) {
270 if ($lines[$i] =~ m/^(?:Checking in|Removing) (.*);$/) {
272 print VERBOSE
" $spec: ";
273 my $entry = $files->{$spec};
274 $entry or die " not on the list of files committed!\n";
276 $lines[$i] =~ m
/^(initial
|new
)\srevision
:\s
277 ([\d\
.]+|delete)(?
:;\s
278 previous\srevision
:\s
281 print VERBOSE
"$3 -> $2.\n";
282 $entry->{new_rev
} = $2 eq "delete" ?
"" : $2;
283 $entry->{old_rev
} = $3;
285 elsif ($1 eq "initial") {
286 print VERBOSE
"new file -> $2.\n";
287 $entry->{new_rev
} = $2;
288 $entry->{old_rev
} = "";
291 die "can't figure out its old and new revisions!\n";
297 ################################################################################
298 # Check In to Other Branches
300 foreach my $branch (@branches) {
301 next if $branch eq $land_branch;
303 foreach my $spec (sort(keys(%$files))) {
304 my $entry = $files->{$spec};
306 if ($entry->{old_rev
} && $entry->{new_rev
}) {
307 print VERBOSE
"*** Merging $spec changes from $entry->{old_rev} " .
308 "to $entry->{new_rev} into $branch... ";
309 run_cvs
("update", [cvs_branch
($branch), "-j", $entry->{old_rev
},
310 "-j", $entry->{new_rev
}, $spec]);
312 elsif ($entry->{old_rev
}) {
313 print VERBOSE
"*** Removing $spec on $branch... ";
314 # CVS doesn't tag removed files with a new revision number,
315 # so we merge from the old revision to the branch itself.
316 run_cvs
("update", [cvs_branch
($branch), "-j", $entry->{old_rev
},
317 "-j", $land_branch, $spec]);
319 elsif ($entry->{new_rev
}) {
320 print VERBOSE
"*** Adding $spec on $branch... ";
321 run_cvs
("update", [cvs_branch
($branch), "-j", $entry->{new_rev
},
325 print VERBOSE
"*** Committing $spec on $branch... ";
326 run_cvs
("commit", [@CVS_COMMIT_OPTIONS, $spec]);
330 print VERBOSE
"*** Returning your tree to its original working branch... ";
331 run_cvs
("update", [cvs_branch
($current_branch), keys(%$files)]);
333 ################################################################################
336 # Returns a hash of modified files indexed by file spec.
337 sub get_modified_files
{
340 # We figure out which files are modified by running "cvs update"
341 # and grepping for /^(M|A) /. We run the command in dry run mode so we
342 # don't actually update the files in the process.
343 # XXX perhaps we should update them, since we won't be able to commit them
344 # if they aren't up-to-date; on the other hand, CVS makes you update them
345 # manually rather than automatically upon commit, so perhaps there's method
348 print VERBOSE
"*** Looking for modified files... ";
349 my ($rv, $output, $errors) = run_cvs
("update", $args, 1);
350 # Break the output into lines and filter for lines about changes.
351 my @lines = grep(m/^(M|A|R) /, split(/\n/, $output));
353 foreach my $line (@lines) {
354 $line =~ m/^(M|A|R) (.*)/;
355 $files{$2} = get_cvs_file
($2);
356 $files{$2}->{change_type
} = $1;
361 # Given a file spec, returns a hash of information about the file extracted
362 # from the CVS/Entries file.
365 my ($volume, $directories, $filename) = File
::Spec
->splitpath($spec);
366 my $cvsdir = $directories ? File
::Spec
->catdir($directories, "CVS") : "CVS";
367 my $files = File
::Spec
->catpath($volume, $cvsdir, "Entries");
368 open(ENTRIES
, "<", $files)
369 or die "Can't read entries file $files for file $spec: $!";
371 my ($name, $revision, $timestamp, $conflict, $options, $tagdate) =
372 ($_ =~ m
|/([^/]*) # filename
374 /([^/+]*) # timestamp
375 (\
+[^/]*)?
# (optional) conflict
379 next if $name ne $filename;
381 return { name
=> $name, revision
=> $revision, conflict
=> $conflict,
382 options
=> $options, tagdate
=> $tagdate };
384 die "Couldn't find entry for file $spec in entries file $files.";
387 # Given a set of files, extracts their current working branch, testing for
388 # multiple branches and date-based tags in the process.
389 sub get_current_branch
{
392 foreach my $filename (keys %$files) {
393 my $entry = $files->{$filename};
394 $entry->{tagdate
} =~ m/^(T|D)?(.*)/;
395 if ($1 and $1 eq "D") { warn "$filename checked out by date $1\n" }
396 elsif ($2 eq "") { $branches{"HEAD"}++ }
397 else { $branches{$2}++ }
398 if (scalar(keys(%branches)) > 1 && !$DOUBLEOH7) {
399 die("Modified files checked out from multiple branches:\n "
400 . join("\n ", map("$_: $files->{$_}->{tagdate}",
401 sort(keys(%$files))))
402 . "Sounds scary, so I'm stopping. Want me to continue?\n"
403 . "Run me again with --live-dangerously and tell my authors\n"
407 return (keys(%branches))[0];
410 # Runs a CVS command and outputs the results. Runs the command in dry run mode
411 # if dry run is enabled globally ($DRY_RUN) or for this specific function call;
412 # and dies on error by default, but can be set to merely warn on error.
413 # Returns the return value of the CVS command, its output, and its errors.
415 my ($cmd, $args, $dry_run, $warn_on_err) = @_;
416 # Let callers override dry run setting, since certain information gathering
417 # routines always run in dry run mode no matter what the global setting is.
419 my ($rv, $output, $errors) =
420 system_capture
("cvs",
422 $dry_run && !$CVS_OPTION_n ?
"-n" : (),
427 die "\n$errors\n$ss\n";
429 warn "\n$errors\n$ss\n"
432 print VERBOSE
"\n$output\n$ss\n";
434 return ($rv, $output, $errors);
437 # Returns the appropriate CVS command line argument for specifying a branch.
438 # Usually this is -r <branch name>, but if we're dealing with the special HEAD
439 # branch it's -A instead.
442 return $branch eq "HEAD" ?
"-A" : ("-r", $branch);
445 # Runs a command and captures its output and errors.
446 # Returns the command's exit code, output, and errors.
448 # XXX This should be using in-memory files, but they require that we close
449 # STDOUT and STDERR before reopening them on the in-memory files, and doing
450 # that on STDERR causes CVS to choke with return value 256.
452 my ($command, @args) = @_;
454 my ($rv, $output, $errors);
456 # Back up the original STDOUT and STDERR so we can restore them later.
457 open(OLDOUT
, ">&STDOUT") or die "Can't back up STDOUT to OLDOUT: $!";
458 open(OLDERR
, ">&STDERR") or die "Can't back up STDERR to OLDERR: $!";
459 use vars
qw( *OLDOUT *OLDERR ); # suppress "used only once" warnings
461 # Close and reopen STDOUT and STDERR to in-memory files, which are just
462 # scalars that take output and append it to their value.
463 # XXX Disabled in-memory files in favor of temp files until in-memory issues
467 #open(STDOUT, ">", \$output) or die "Can't open STDOUT to output var: $!";
468 #open(STDERR, ">", \$errors) or die "Can't open STDERR to errors var: $!";
469 my $outfile = tempfile
();
470 my $errfile = tempfile
();
471 # Perl 5.6.1 filehandle duplication doesn't support the three-argument form
472 # of open, so we can't just open(STDOUT, ">&", $outfile); instead we have to
473 # create an alias OUTFILE and then do open(STDOUT, ">&OUTFILE").
474 local *OUTFILE
= *$outfile;
475 local *ERRFILE
= *$errfile;
476 use vars
qw( *OUTFILE *ERRFILE ); # suppress "used only once" warnings
477 open(STDOUT
, ">&OUTFILE") or open(STDOUT
, ">&OLDOUT")
478 and die "Can't dupe STDOUT to output file: $!";
479 open(STDERR
, ">&ERRFILE") or open(STDOUT
, ">&OLDOUT")
480 and open(STDERR
, ">&OLDERR")
481 and die "Can't dupe STDERR to errors file: $!";
484 print VERBOSE
"$command " . join(" ", @args) . "\n";
485 $rv = system($command, @args);
487 # Grab output and errors from the temp files. In a block to localize $/.
488 # XXX None of this would be necessary if in-memory files was working.
491 seek($outfile, 0, 0);
492 seek($errfile, 0, 0);
493 $output = <$outfile>;
494 $errors = <$errfile>;
497 # Restore original STDOUT and STDERR.
500 open(STDOUT
, ">&OLDOUT") or die "Can't restore STDOUT from OLDOUT: $!";
501 open(STDERR
, ">&OLDERR") or die "Can't restore STDERR from OLDERR: $!";
503 return ($rv, $output, $errors);