diff options
Diffstat (limited to 'tools/dist')
-rwxr-xr-x | tools/dist/backport.pl | 1235 | ||||
-rw-r--r-- | tools/dist/backport_accept.dump | 550 | ||||
-rw-r--r-- | tools/dist/backport_branches.dump | 642 | ||||
-rw-r--r-- | tools/dist/backport_indented_entry.dump | 522 | ||||
-rw-r--r-- | tools/dist/backport_multirevisions.dump | 534 | ||||
-rwxr-xr-x | tools/dist/backport_tests.py | 578 | ||||
-rw-r--r-- | tools/dist/backport_two_approveds.dump | 961 | ||||
-rwxr-xr-x | tools/dist/dist.sh | 33 | ||||
-rwxr-xr-x | tools/dist/make-deps-tarball.sh | 121 | ||||
-rwxr-xr-x | tools/dist/nightly.sh | 6 | ||||
l--------- | tools/dist/nominate.pl | 1 | ||||
-rwxr-xr-x | tools/dist/release.py | 198 | ||||
-rw-r--r-- | tools/dist/templates/download.ezt | 2 | ||||
-rw-r--r-- | tools/dist/templates/rc-news.ezt | 2 | ||||
-rw-r--r-- | tools/dist/templates/rc-release-ann.ezt | 2 | ||||
-rw-r--r-- | tools/dist/templates/stable-news.ezt | 2 | ||||
-rw-r--r-- | tools/dist/templates/stable-release-ann.ezt | 2 |
17 files changed, 5067 insertions, 324 deletions
diff --git a/tools/dist/backport.pl b/tools/dist/backport.pl index ab5c823..0c5f6be 100755 --- a/tools/dist/backport.pl +++ b/tools/dist/backport.pl @@ -1,8 +1,10 @@ -#!/usr/bin/perl -l +#!/usr/bin/perl use warnings; use strict; use feature qw/switch say/; +#no warnings 'experimental::smartmatch'; + # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -20,252 +22,1259 @@ use feature qw/switch say/; # specific language governing permissions and limitations # under the License. +use Carp qw/croak confess carp cluck/; +use Digest (); use Term::ReadKey qw/ReadMode ReadKey/; +use File::Basename qw/basename dirname/; +use File::Copy qw/copy move/; use File::Temp qw/tempfile/; -use POSIX qw/ctermid/; +use IO::Select (); +use IPC::Open3 qw/open3/; +use POSIX qw/ctermid strftime isprint isspace/; +use Text::Wrap qw/wrap/; +use Tie::File (); + +############### Start of reading values from environment ############### +# Programs we use. +# +# TODO: document which are interpreted by sh and which should point to binary. my $SVN = $ENV{SVN} || 'svn'; # passed unquoted to sh +my $SHELL = $ENV{SHELL} // '/bin/sh'; my $VIM = 'vim'; +my $EDITOR = $ENV{SVN_EDITOR} // $ENV{VISUAL} // $ENV{EDITOR} // 'ed'; +my $PAGER = $ENV{PAGER} // 'less' // 'cat'; + +# Mode flags. +package Mode { + use constant { + AutoCommitApproveds => 1, # used by nightly commits (svn-role) + Conflicts => 2, # used by the hourly conflicts-detection buildbot + Interactive => 3, + }; +}; +my $YES = ($ENV{YES} // "0") =~ /^(1|yes|true)$/i; # batch mode: eliminate prompts, add sleeps +my $MAY_COMMIT = ($ENV{MAY_COMMIT} // "false") =~ /^(1|yes|true)$/i; +my $MODE = ($YES ? ($MAY_COMMIT ? Mode::AutoCommitApproveds : Mode::Conflicts ) + : Mode::Interactive ); + +# Other knobs. +my $VERBOSE = 0; +my $DEBUG = (exists $ENV{DEBUG}); # 'set -x', etc + +# Force all these knobs to be usable via @sh. +my @sh = qw/false true/; +die if grep { ($sh[$_] eq 'true') != !!$_ } $DEBUG, $MAY_COMMIT, $VERBOSE, $YES; + +# Username for entering votes. +my $SVN_A_O_REALM = '<https://svn.apache.org:443> ASF Committers'; +my ($AVAILID) = $ENV{AVAILID} // do { + local $_ = `$SVN auth svn.apache.org:443 2>/dev/null`; # TODO: pass $SVN_A_O_REALM + ($? == 0 && /Auth.*realm: \Q$SVN_A_O_REALM\E\nUsername: (.*)/) ? $1 : undef +} // do { + local $/; # slurp mode + my $fh; + my $dir = "$ENV{HOME}/.subversion/auth/svn.simple/"; + my $filename = Digest->new("MD5")->add($SVN_A_O_REALM)->hexdigest; + open $fh, '<', "$dir/$filename" + and <$fh> =~ /K 8\nusername\nV \d+\n(.*)/ + ? $1 + : undef +}; + +unless (defined $AVAILID) { + unless ($MODE == Mode::Conflicts) { + warn "Username for commits (of votes/merges) not found; " + ."it will be possible to review nominations but not to commit votes " + ."or merges.\n"; + warn "Press the 'any' key to continue...\n"; + die if $MODE == Mode::AutoCommitApproveds; # unattended mode; can't prompt. + ReadMode 'cbreak'; + ReadKey 0; + ReadMode 'restore'; + } +} + +############## End of reading values from the environment ############## + +# Constants. my $STATUS = './STATUS'; +my $STATEFILE = './.backports1'; my $BRANCHES = '^/subversion/branches'; +my $TRUNK = '^/subversion/trunk'; +$ENV{LC_ALL} = "C"; # since we parse 'svn info' output and use isprint() -my $YES = $ENV{YES}; # batch mode: eliminate prompts, add sleeps -my $WET_RUN = qw[false true][1]; # don't commit -my $DEBUG = qw[false true][0]; # 'set -x', etc - -# derived values +# Globals. +my %ERRORS = (); +# TODO: can $MERGED_SOMETHING be removed and references to it replaced by scalar(@MERGES_TODAY) ? +# alternately, does @MERGES_TODAY need to be purged whenever $MERGED_SOMETHING is reset? +# The scalar is only used in interactive runs, but the array is used in +# svn-role batch mode too. +my @MERGES_TODAY; +my $MERGED_SOMETHING = 0; my $SVNq; +# Derived values. +my $SVNvsn = do { + my ($major, $minor, $patch) = `$SVN --version -q` =~ /^(\d+)\.(\d+)\.(\d+)/; + 1e6*$major + 1e3*$minor + $patch; +}; $SVN .= " --non-interactive" if $YES or not defined ctermid; $SVNq = "$SVN -q "; -$SVNq =~ s/-q// if $DEBUG eq 'true'; +$SVNq =~ s/-q// if $DEBUG; -sub usage { - my $basename = $0; - $basename =~ s#.*/##; + +sub backport_usage { + my $basename = basename $0; print <<EOF; -Run this from the root of your release branch (e.g., 1.6.x) working copy. +backport.pl: a tool for reviewing, merging, and voting on STATUS entries. + +Normally, invoke this with CWD being the root of the stable branch (e.g., +1.8.x): -For each entry in STATUS, you will be prompted whether to merge it. + Usage: test -e \$d/STATUS && cd \$d && \\ + backport.pl [PATTERN] + (where \$d is a working copy of branches/1.8.x) -WARNING: -If you accept the prompt, $basename will revert all local changes and will -commit the merge immediately. +Alternatively, invoke this via a symlink named "b" placed at the same directory +as the STATUS file, in which case the CWD doesn't matter (the script will cd): + + Usage: ln -s /path/to/backport.pl \$d/b && \\ + \$d/b [PATTERN] + (where \$d is a working copy of branches/1.8.x) + +In either case, the ./STATUS file should be at HEAD. If it has local mods, +they will be preserved through 'revert' operations but included in 'commit' +operations. + +If PATTERN is provided, only entries which match PATTERN are considered. The +sense of "match" is either substring (fgrep) or Perl regexp (with /msi). + +In interactive mode (the default), you will be prompted once per STATUS entry. +At a prompt, you have the following options: + +y: Run a merge. It will not be committed. + WARNING: This will run 'update' and 'revert -R ./'. +l: Show logs for the entries being nominated. +v: Show the full entry (the prompt only shows an abridged version). +q: Quit the "for each nomination" loop. +±1: Enter a +1 or -1 vote + You will be prompted to commit your vote at the end. +±0: Enter a +0 or -0 vote + You will be prompted to commit your vote at the end. +a: Move the entry to the "Approved changes" section. + When both approving and voting on an entry, approve first: for example, + to enter a third +1 vote, type "a" "+" "1". +e: Edit the entry in $EDITOR. + You will be prompted to commit your edits at the end. +N: Move to the next entry. Cache the entry in '$STATEFILE' and do not + prompt for it again (even across runs) until it is changed. + : Move to the next entry, without adding the current one to the cache. + (That's a space character, ASCII 0x20.) + +After running a merge, you have the following options: + +y: Open a shell. +d: View a diff. +N: Move to the next entry. + +To commit a merge, you have two options: either answer 'y' to the second prompt +to open a shell, and manually run 'svn commit' therein; or set \$MAY_COMMIT=1 +in the environment before running the script, in which case answering 'y' +to the first prompt will not only run the merge but also commit it. + +There are two batch modes. The first mode is used by the nightly svn-role +mergebot. It is enabled by setting \$YES and \$MAY_COMMIT to '1' in the +environment. In this mode, the script will iterate the "Approved changes:" +section and merge and commit each entry therein. To prevent an entry from +being auto-merged, veto it or move it to a new section named "Approved, but +merge manually:". + +The second batch mode is used by the hourly conflicts detector bot. It is +triggered by having \$YES defined in the environment to '1' and \$MAY_COMMIT +undefined. In this mode, the script will locally merge every nomination +(including unapproved and vetoed ones), and complain to stderr if the merge +failed due to a conflict. This mode never commits anything. + +The hourly conflicts detector bot turns red if any entry produced a merge +conflict. When entry A depends on entry B for a clean merge, put a "Depends:" +header on entry A to instruct the bot not to turn red due to A. (The header +is not parsed; only its presence or absence matters.) + +Both batch modes also perform a basic sanity-check on entries that declare +backport branches (via the "Branch:" header): if a backport branch is used, but +at least one of the revisions enumerated in the entry title had not been merged +from $TRUNK to the branch root, the hourly bot will turn red and +nightly bot will skip the entry and email its admins. (The nightly bot does +not email the list on failure, since it doesn't use buildbot.) The 'svn' binary defined by the environment variable \$SVN, or otherwise the 'svn' found in \$PATH, will be used to manage the working copy. EOF } +sub nominate_usage { + my $availid = $AVAILID // "(your username)"; + my $basename = basename $0; + print <<EOF; +nominate.pl: a tool for adding entries to STATUS. + +Usage: $0 "foo r42 bar r43 qux 45." "\$Some_justification" + +Will add: + * r42, r43, r45 + (log message of r42) + Justification: + \$Some_justification + Votes: + +1: $availid +to STATUS. Backport branches are detected automatically. + +The STATUS file in the current directory is used (unless argv[0] is "n", in +which case the STATUS file in the directory of argv[0] is used; the intent +is to create a symlink named "n" in the branch wc root). + +EOF +# TODO: Optionally add a "Notes" section. +# TODO: Look for backport branches named after issues. +# TODO: Do a dry-run merge on added entries. +# TODO: Do a dry-run merge on interactively-edited entries in backport.pl +} + +# If $AVAILID is undefined, warn about it and return true. +# Else return false. +# +# $_[0] is a string for inclusion in generated error messages. +sub warned_cannot_commit { + my $caller_error_string = shift; + return 0 if defined $AVAILID; + + warn "$0: $caller_error_string: unable to determine your username via \$AVAILID or svnauth(1) or ~/.subversion/auth/"; + return 1; +} + +sub digest_string { + Digest->new("MD5")->add(@_)->hexdigest +} + +sub digest_entry($) { + # Canonicalize the number of trailing EOLs to two. This matters when there's + # on empty line after the last entry in Approved, for example. + local $_ = shift; + s/\n*\z// and $_ .= "\n\n"; + digest_string($_) +} + sub prompt { - local $\; # disable 'perl -l' effects - print "Go ahead? "; - - # TODO: this part was written by trial-and-error - ReadMode 'cbreak'; - my $answer = (ReadKey 0); - print $answer, "\n"; - return ($answer =~ /^y/i) ? 1 : 0; + print $_[0]; shift; + my %args = @_; + my $getchar = sub { + my $answer; + do { + ReadMode 'cbreak'; + $answer = (ReadKey 0); + ReadMode 'normal'; + die if $@ or not defined $answer; + # Swallow terminal escape codes (e.g., arrow keys). + unless (isprint $answer or isspace $answer) { + $answer = (ReadKey -1) while defined $answer; + # TODO: provide an indication that the keystroke was sensed and ignored. + } + } until defined $answer and (isprint $answer or isspace $answer); + print $answer; + return $answer; + }; + + die "$0: called prompt() in non-interactive mode!" if $YES; + my $answer = $getchar->(); + $answer .= $getchar->() if exists $args{extra} and $answer =~ $args{extra}; + say "" unless $args{dontprint}; + return $args{verbose} + ? $answer + : ($answer =~ /^y/i) ? 1 : 0; +} + +# Bourne-escape a string. +# Example: +# >>> shell_escape(q[foo'bar]) eq q['foo'\''bar'] +# True +sub shell_escape { + my (@reply) = map { + local $_ = $_; # the LHS $_ is mutable; the RHS $_ may not be. + s/\x27/'\\\x27'/g; + "'$_'" + } @_; + wantarray ? @reply : $reply[0] +} + +sub shell_safe_path_or_url($) { + local $_ = shift; + return (m{^[A-Za-z0-9._:+/-]+$} and !/^-|^[+]/); +} + +# Shell-safety-validating wrapper for File::Temp::tempfile +sub my_tempfile { + my ($fh, $fn) = tempfile(); + croak "Tempfile name '$fn' not shell-safe; aborting" + unless shell_safe_path_or_url $fn; + return ($fh, $fn); +} + +# The first argument is a shell script. Run it and return the shell's +# exit code, and stdout and stderr as references to arrays of lines. +sub run_in_shell($) { + my $script = shift; + my $pid = open3 \*SHELL_IN, \*SHELL_OUT, \*SHELL_ERR, qw#/bin/sh#; + # open3 raises exception when it fails; no need to error check + + print SHELL_IN $script; + close SHELL_IN; + + # Read loop: tee stdout,stderr to arrays. + my $select = IO::Select->new(\*SHELL_OUT, \*SHELL_ERR); + my (@readable, $outlines, $errlines); + while (@readable = $select->can_read) { + for my $fh (@readable) { + my $line = <$fh>; + $select->remove($fh) if eof $fh or not defined $line; + next unless defined $line; + + if ($fh == \*SHELL_OUT) { + push @$outlines, $line; + print STDOUT $line; + } + if ($fh == \*SHELL_ERR) { + push @$errlines, $line; + print STDERR $line; + } + } + } + waitpid $pid, 0; # sets $? + return $?, $outlines, $errlines; } + +# EXPECTED_ERROR_P is subref called with EXIT_CODE, OUTLINES, ERRLINES, +# expected to return TRUE if the error should be considered fatal (cause +# backport.pl to exit non-zero) or not. It may be undef for default behaviour. sub merge { - my %entry = @_; + my %entry = %{ +shift }; + my $expected_error_p = shift // sub { 0 }; # by default, errors are unexpected + my $parno = $entry{parno} - scalar grep { $_->{parno} < $entry{parno} } @MERGES_TODAY; - my ($logmsg_fh, $logmsg_filename) = tempfile(); - my ($mergeargs, $pattern); + my ($logmsg_fh, $logmsg_filename) = my_tempfile(); + my (@mergeargs); - my $backupfile = "backport_pl.$$.tmp"; + my $shell_escaped_branch = shell_escape($entry{branch}) + if defined($entry{branch}); if ($entry{branch}) { - # NOTE: This doesn't escape the branch into the pattern. - $pattern = sprintf '\V\(%s branch(es)?\|branches\/%s\|Branch(es)?:\n *%s\)', $entry{branch}, $entry{branch}, $entry{branch}; - $mergeargs = "--reintegrate $BRANCHES/$entry{branch}"; - print $logmsg_fh "Reintegrate the $entry{header}:"; - print $logmsg_fh ""; - } elsif (@{$entry{revisions}}) { - $pattern = '^ [*] \V' . 'r' . $entry{revisions}->[0]; - $mergeargs = join " ", (map { "-c$_" } @{$entry{revisions}}), '^/subversion/trunk'; - if (@{$entry{revisions}} > 1) { - print $logmsg_fh "Merge the $entry{header} from trunk:"; - print $logmsg_fh ""; + if ($SVNvsn >= 1_008_000) { + @mergeargs = shell_escape "$BRANCHES/$entry{branch}"; + say $logmsg_fh "Merge $entry{header}:"; } else { - print $logmsg_fh "Merge r$entry{revisions}->[0] from trunk:"; - print $logmsg_fh ""; + @mergeargs = shell_escape qw/--reintegrate/, "$BRANCHES/$entry{branch}"; + say $logmsg_fh "Reintegrate $entry{header}:"; } + say $logmsg_fh ""; + } elsif (@{$entry{revisions}}) { + @mergeargs = shell_escape( + ($entry{accept} ? "--accept=$entry{accept}" : ()), + (map { "-c$_" } @{$entry{revisions}}), + '--', + '^/subversion/trunk', + ); + say $logmsg_fh + "Merge $entry{header} from trunk", + $entry{accept} ? ", with --accept=$entry{accept}" : "", + ":"; + say $logmsg_fh ""; } else { die "Don't know how to call $entry{header}"; } - print $logmsg_fh $_ for @{$entry{entry}}; + say $logmsg_fh $_ for @{$entry{entry}}; close $logmsg_fh or die "Can't close $logmsg_filename: $!"; + my $reintegrated_word = ($SVNvsn >= 1_008_000) ? "merged" : "reintegrated"; my $script = <<"EOF"; #!/bin/sh set -e -if $DEBUG; then +if $sh[$DEBUG]; then set -x fi -$SVN diff > $backupfile -$SVNq revert -R . $SVNq up -$SVNq merge $mergeargs -$VIM -e -s -n -N -i NONE -u NONE -c '/$pattern/normal! dap' -c wq $STATUS -if $WET_RUN; then +$SVNq merge @mergeargs +if [ "`$SVN status -q | wc -l`" -eq 1 ]; then + if [ -n "`$SVN diff | perl -lne 'print if s/^(Added|Deleted|Modified): //' | grep -vx svn:mergeinfo`" ]; then + # This check detects STATUS entries that name non-^/subversion/ revnums. + # ### Q: What if we actually commit a mergeinfo fix to trunk and then want + # ### to backport it? + # ### A: We don't merge it using the script. + echo "Bogus merge: includes only svn:mergeinfo changes!" >&2 + exit 2 + fi +fi +if $sh[$MAY_COMMIT]; then + # Remove the approved entry. The sentinel is important when the entry being + # removed is the very last one in STATUS, and in that case it has two effects: + # (1) keeps STATUS from ending in a run of multiple empty lines; + # (2) makes the \x{7d}k motion behave the same as in all other cases. + # + # Use a tempfile because otherwise backport_main() would see the "sentinel paragraph". + # Since backport_main() has an open descriptor, it will continue to see + # the STATUS inode that existed when control flow entered backport_main(); + # since we replace the file on disk, when this block of code runs in the + # next iteration, it will see the new contents. + cp $STATUS $STATUS.t + (echo; echo; echo "sentinel paragraph") >> $STATUS.t + $VIM -e -s -n -N -i NONE -u NONE -c ':0normal! $parno\x{7d}kdap' -c wq $STATUS.t + $VIM -e -s -n -N -i NONE -u NONE -c '\$normal! dap' -c wq $STATUS.t + mv $STATUS.t $STATUS $SVNq commit -F $logmsg_filename -else - echo "Committing:" +elif ! $sh[$YES]; then + echo "Would have committed:" + echo '[[[' $SVN status -q + echo 'M STATUS (not shown in the diff)' cat $logmsg_filename + echo ']]]' fi EOF + if ($MAY_COMMIT) { + # STATUS has been edited and the change has been committed + push @MERGES_TODAY, \%entry; + } + $script .= <<"EOF" if $entry{branch}; reinteg_rev=\`$SVN info $STATUS | sed -ne 's/Last Changed Rev: //p'\` -if $WET_RUN; then +if $sh[$MAY_COMMIT]; then # Sleep to avoid out-of-order commit notifications - if [ -n "\$YES" ]; then sleep 15; fi - $SVNq rm $BRANCHES/$entry{branch} -m "Remove the '$entry{branch}' branch, reintegrated in r\$reinteg_rev." - if [ -n "\$YES" ]; then sleep 1; fi -else - echo "Removing reintegrated '$entry{branch}' branch" + if $sh[$YES]; then sleep 15; fi + $SVNq rm $BRANCHES/$shell_escaped_branch -m "Remove the '"$shell_escaped_branch"' branch, $reintegrated_word in r\$reinteg_rev." + if $sh[$YES]; then sleep 1; fi +elif ! $sh[$YES]; then + echo "Would remove $reintegrated_word '"$shell_escaped_branch"' branch" fi EOF - open SHELL, '|-', qw#/bin/sh# or die $!; - print SHELL $script; - close SHELL or warn "$0: sh($?): $!"; + # Include the time so it's easier to find the interesting backups. + my $backupfile = strftime "backport_pl.%Y%m%d-%H%M%S.$$.tmp", localtime; + die if -s $backupfile; + system("$SVN diff > $backupfile") == 0 + or die "Saving a backup diff ($backupfile) failed ($?): $!"; + if (-z $backupfile) { + unlink $backupfile; + } else { + warn "Local mods saved to '$backupfile'\n"; + } + + # If $MAY_COMMIT, then $script will edit STATUS anyway. + revert(verbose => 0, discard_STATUS => $MAY_COMMIT); + + $MERGED_SOMETHING++; + my ($exit_code, $outlines, $errlines) = run_in_shell $script; + unless ($! == 0) { + die "system() failed to spawn subshell ($!); aborting"; + } + unless ($exit_code == 0) { + warn "$0: subshell exited with code $exit_code (in '$entry{header}') " + ."(maybe due to 'set -e'?)"; + + # If we're committing, don't attempt to guess the problem and gracefully + # continue; just abort. + if ($MAY_COMMIT) { + die "Lost track of paragraph numbers; aborting"; + } - unlink $backupfile if -z $backupfile; - unlink $logmsg_filename unless $? or $!; + # Record the error, unless the caller wants not to. + $ERRORS{$entry{id}} = [\%entry, "subshell exited with code $exit_code"] + unless $expected_error_p->($exit_code, $outlines, $errlines); + } + + unlink $logmsg_filename unless $exit_code; } +# Input formats: +# "1.8.x-r42", +# "branches/1.8.x-r42", +# "branches/1.8.x-r42/", +# "subversion/branches/1.8.x-r42", +# "subversion/branches/1.8.x-r42/", +# "^/subversion/branches/1.8.x-r42", +# "^/subversion/branches/1.8.x-r42/", +# Return value: +# "1.8.x-r42" +# Works for any branch name that doesn't include slashes. sub sanitize_branch { local $_ = shift; - s#.*/##; s/^\s*//; s/\s*$//; + s#/*$##; + s#.*/##; return $_; } +sub logsummarysummary { + my $entry = shift; + join "", + $entry->{logsummary}->[0], ('[...]' x (0 < $#{$entry->{logsummary}})) +} + # TODO: may need to parse other headers too? sub parse_entry { + my $raw = shift; + my $parno = shift; my @lines = @_; + my $depends; + my $accept; my (@revisions, @logsummary, $branch, @votes); # @lines = @_; - # strip first three spaces - $_[0] =~ s/^ \* / /; - s/^ // for @_; + # strip spaces to match up with the indention + $_[0] =~ s/^( *)\* //; + my $indentation = ' ' x (length($1) + 2); + s/^$indentation// for @_; + + # Ignore trailing spaces: it is not significant on any field, and makes the + # regexes simpler. + s/\s*$// for @_; # revisions - $branch = sanitize_branch $1 if $_[0] =~ /^(\S*) branch$/; - while ($_[0] =~ /^r/) { - while ($_[0] =~ s/^r(\d+)(?:$|[,; ]+)//) { - push @revisions, $1; - } + $branch = sanitize_branch $1 + and shift + if $_[0] =~ /^(\S*) branch$/ or $_[0] =~ m#branches/(\S+)#; + while ($_[0] =~ /^(?:r?\d+[,; ]*)+$/) { + push @revisions, ($_[0] =~ /(\d+)/g); shift; } # summary - push @logsummary, shift until $_[0] =~ /^\s*\w+:/ or not defined $_[0]; + do { + push @logsummary, shift + } until $_[0] =~ /^\s*[][\w]+:/ or not defined $_[0]; # votes unshift @votes, pop until $_[-1] =~ /^\s*Votes:/ or not defined $_[-1]; pop; - # branch + # depends, branch, notes + # Ignored headers: Changes[*] while (@_) { - shift and next unless $_[0] =~ s/^\s*Branch(es)?:\s*//; - $branch = sanitize_branch (shift || shift || die "Branch header found without value"); + given (shift) { + when (/^Depends:/) { + $depends++; + } + if (s/^Branch:\s*//) { + $branch = sanitize_branch ($_ || shift || die "Branch header found without value"); + } + if (s/^Notes:\s*//) { + my $notes = $_; + $notes .= shift while @_ and $_[0] !~ /^\w/; + my %accepts = map { $_ => 1 } ($notes =~ /--accept[ =]([a-z-]+)/g); + given (scalar keys %accepts) { + when (0) { } + when (1) { $accept = [keys %accepts]->[0]; } + default { + warn "Too many --accept values at '", + logsummarysummary({ logsummary => [@logsummary] }), + "'"; + } + } + } + } } # Compute a header. - my $header; - $header = "r$revisions[0] group" if @revisions; - $header = "$branch branch" if $branch; - warn "No header for [@lines]" unless $header; + my ($header, $id); + if ($branch) { + $header = "the $branch branch"; + $id = $branch; + } elsif (@revisions == 1) { + $header = "r$revisions[0]"; + $id = "r$revisions[0]"; + } elsif (@revisions) { + $header = "the r$revisions[0] group"; + $id = "r$revisions[0]"; + } else { + die "Entry '$raw' has neither revisions nor branch"; + } + my $header_start = ($header =~ /^the/ ? ucfirst($header) : $header); + + warn "Entry has both branch '$branch' and --accept=$accept specified\n" + if $branch and $accept; return ( revisions => [@revisions], logsummary => [@logsummary], branch => $branch, header => $header, + header_start => $header_start, + depends => $depends, + id => $id, votes => [@votes], entry => [@lines], + accept => $accept, + raw => $raw, + digest => digest_entry($raw), + parno => $parno, # $. from backport_main() ); } +sub edit_string { + # Edits $_[0] in an editor. + # $_[1] is used in error messages. + die "$0: called edit_string() in non-interactive mode!" if $YES; + my $string = shift; + my $name = shift; + my %args = @_; + my $trailing_eol = $args{trailing_eol}; + my ($fh, $fn) = my_tempfile(); + print $fh $string; + $fh->flush or die $!; + system("$EDITOR -- $fn") == 0 + or warn "\$EDITOR failed editing $name: $! ($?); " + ."edit results ($fn) ignored."; + my $rv = `cat $fn`; + $rv =~ s/\n*\z// and $rv .= ("\n" x $trailing_eol) if defined $trailing_eol; + $rv; +} + +sub vote { + my ($state, $approved, $votes) = @_; + # TODO: use votesarray instead of votescheck + my (%approvedcheck, %votescheck); + my $raw_approved = ""; + my @votesarray; + return unless %$approved or %$votes; + + # If $AVAILID is undef, we can only process 'edit' pseudovotes; handle_entry() is + # supposed to prevent numeric (±1,±0) votes from getting to this point. + die "Assertion failed" if not defined $AVAILID + and grep { $_ ne 'edit' } map { $_->[0] } values %$votes; + + my $had_empty_line; + + $. = 0; + open STATUS, "<", $STATUS; + open VOTES, ">", "$STATUS.$$.tmp"; + while (<STATUS>) { + $had_empty_line = /\n\n\z/; + my $key = digest_entry $_; + + $approvedcheck{$key}++ if exists $approved->{$key}; + $votescheck{$key}++ if exists $votes->{$key}; + + unless (exists $votes->{$key} or exists $approved->{$key}) { + print VOTES; + next; + } + + unless (exists $votes->{$key}) { + push @votesarray, { + entry => $approved->{$key}, + approval => 1, + digest => $key, + }; + $raw_approved .= $_; + next; + } + + # We have a vote, and potentially an approval. + + my ($vote, $entry) = @{$votes->{$key}}; + push @votesarray, { + entry => $entry, + vote => $vote, + approval => (exists $approved->{$key}), + digest => $key, + }; + + if ($vote eq 'edit') { + local $_ = $entry->{raw}; + $votesarray[-1]->{digest} = digest_entry $_; + (exists $approved->{$key}) ? ($raw_approved .= $_) : (print VOTES); + next; + } + + s/^(\s*\Q$vote\E:.*)/"$1, $AVAILID"/me + or s/(.*\w.*?\n)/"$1 $vote: $AVAILID\n"/se; + $_ = edit_string $_, $entry->{header}, trailing_eol => 2 + if $vote ne '+1'; + $votesarray[-1]->{digest} = digest_entry $_; + (exists $approved->{$key}) ? ($raw_approved .= $_) : (print VOTES); + } + close STATUS; + print VOTES "\n" if $raw_approved and !$had_empty_line; + print VOTES $raw_approved; + close VOTES; + warn "Some vote chunks weren't found: ", + join ',', + map $votes->{$_}->[1]->{id}, + grep { !$votescheck{$_} } keys %$votes + if scalar(keys %$votes) != scalar(keys %votescheck); + warn "Some approval chunks weren't found: ", + join ',', + map $approved->{$_}->{id}, + grep { !$approvedcheck{$_} } keys %$approved + if scalar(keys %$approved) != scalar(keys %approvedcheck); + prompt "Press the 'any' key to continue...\n", dontprint => 1 + if scalar(keys %$approved) != scalar(keys %approvedcheck) + or scalar(keys %$votes) != scalar(keys %votescheck); + move "$STATUS.$$.tmp", $STATUS; + + my $logmsg = do { + my @sentences = map { + my $words_vote = ", approving" x $_->{approval}; + my $words_edit = " and approve" x $_->{approval}; + exists $_->{vote} + ? ( + ( $_->{vote} eq 'edit' + ? "Edit$words_edit the $_->{entry}->{id} entry" + : "Vote $_->{vote} on $_->{entry}->{header}$words_vote" + ) + . "." + ) + : # exists only in $approved + "Approve $_->{entry}->{header}." + } @votesarray; + (@sentences == 1) + ? $sentences[0] + : "* STATUS:\n" . join "", map " $_\n", @sentences; + }; + + system "$SVN diff -- $STATUS"; + printf "[[[\n%s%s]]]\n", $logmsg, ("\n" x ($logmsg !~ /\n\z/)); + if (prompt "Commit these votes? ") { + my ($logmsg_fh, $logmsg_filename) = my_tempfile(); + print $logmsg_fh $logmsg; + close $logmsg_fh; + system("$SVN commit -F $logmsg_filename -- $STATUS") == 0 + or warn("Committing the votes failed($?): $!") and return; + unlink $logmsg_filename; + + # Add to state votes that aren't '+0' or 'edit' + $state->{$_->{digest}}++ for grep + +{ qw/-1 t -0 t +1 t/ }->{$_->{vote}}, + @votesarray; + } +} + +sub check_local_mods_to_STATUS { + if (`$SVN status -q $STATUS`) { + die "Local mods to STATUS file $STATUS" if $YES; + warn "Local mods to STATUS file $STATUS"; + system "$SVN diff -- $STATUS"; + prompt "Press the 'any' key to continue...\n", dontprint => 1; + return 1; + } + return 0; +} + +sub renormalize_STATUS { + my $vimscript = <<'EOVIM'; +:"" Strip trailing whitespace before entries and section headers, but not +:"" inside entries (e.g., multi-paragraph Notes: fields). +:"" +:"" Since an entry is always followed by another entry, section header, or EOF, +:"" there is no need to separately strip trailing whitespace from lines following +:"" entries. +:%s/\v\s+\n(\s*\n)*\ze(\s*[*]|\w)/\r\r/g + +:"" Ensure there is exactly one blank line around each entry and header. +:"" +:"" First, inject a new empty line above and below each entry and header; then, +:"" squeeze runs of empty lines together. +:0/^=/,$ g/^ *[*]/normal! O +:g/^=/normal! o +:g/^=/-normal! O +: +:%s/\n\n\n\+/\r\r/g + +:"" Save. +:wq +EOVIM + open VIM, '|-', $VIM, qw/-e -s -n -N -i NONE -u NONE --/, $STATUS + or die "Can't renormalize STATUS: $!"; + print VIM $vimscript; + close VIM or warn "$0: renormalize_STATUS failed ($?): $!)"; + + system("$SVN commit -m '* STATUS: Whitespace changes only.' -- $STATUS") == 0 + or die "$0: Can't renormalize STATUS ($?): $!" + if $MAY_COMMIT; +} + +sub revert { + my %args = @_; + die "Bug: \$args{verbose} undefined" unless exists $args{verbose}; + die "Bug: unknown argument" if grep !/^(?:verbose|discard_STATUS)$/, keys %args; + + copy $STATUS, "$STATUS.$$.tmp" unless $args{discard_STATUS}; + system("$SVN revert -q $STATUS") == 0 + or die "revert failed ($?): $!"; + system("$SVN revert -R ./" . (" -q" x !$args{verbose})) == 0 + or die "revert failed ($?): $!"; + move "$STATUS.$$.tmp", $STATUS unless $args{discard_STATUS}; + $MERGED_SOMETHING = 0; +} + +sub maybe_revert { + # This is both a SIGINT handler, and the tail end of main() in normal runs. + # @_ is 'INT' in the former case and () in the latter. + delete $SIG{INT} unless @_; + revert verbose => 1 if !$YES and $MERGED_SOMETHING and prompt 'Revert? '; + (@_ ? exit : return); +} + +sub signal_handler { + my $sig = shift; + + # Clean up after prompt() + ReadMode 'normal'; + + # Fall back to default action + delete $SIG{$sig}; + kill $sig, $$; +} + +sub warning_summary { + return unless %ERRORS; + + warn "Warning summary\n"; + warn "===============\n"; + warn "\n"; + for my $id (keys %ERRORS) { + my $title = logsummarysummary $ERRORS{$id}->[0]; + warn "$id ($title): $ERRORS{$id}->[1]\n"; + } +} + +sub read_state { + # die "$0: called read_state() in non-interactive mode!" if $YES; + + open my $fh, '<', $STATEFILE or do { + return {} if $!{ENOENT}; + die "Can't read statefile: $!"; + }; + + my %rv; + while (<$fh>) { + chomp; + $rv{$_}++; + } + return \%rv; +} + +sub write_state { + my $state = shift; + open STATE, '>', $STATEFILE or warn("Can't write state: $!"), return; + say STATE for keys %$state; + close STATE; +} + +sub exit_stage_left { + my $state = shift; + maybe_revert; + warning_summary if $YES; + vote $state, @_; + write_state $state; + exit scalar keys %ERRORS; +} + +# Given an ENTRY, check whether all ENTRY->{revisions} have been merged +# into ENTRY->{branch}, if it has one. If revisions are missing, record +# a warning in $ERRORS. Return TRUE If the entry passed the validation +# and FALSE otherwise. +sub validate_branch_contains_named_revisions { + my %entry = @_; + return 1 unless defined $entry{branch}; + my %present; + + return "Why are you running so old versions?" # true in boolean context + if $SVNvsn < 1_005_000; # doesn't have the 'mergeinfo' subcommand + + my $shell_escaped_branch = shell_escape($entry{branch}); + %present = do { + my @present = `$SVN mergeinfo --show-revs=merged -- $TRUNK $BRANCHES/$shell_escaped_branch`; + chomp @present; + @present = map /(\d+)/g, @present; + map +($_ => 1), @present; + }; + + my @absent = grep { not exists $present{$_} } @{$entry{revisions}}; + + if (@absent) { + $ERRORS{$entry{id}} //= [\%entry, + sprintf("Revisions '%s' nominated but not included in branch", + (join ", ", map { "r$_" } @absent)), + ]; + } + return @absent ? 0 : 1; +} + sub handle_entry { - my %entry = parse_entry @_; - my @vetoes = grep { /^ -1:/ } @{$entry{votes}}; + my $in_approved = shift; + my $approved = shift; + my $votes = shift; + my $state = shift; + my $raw = shift; + my $parno = shift; + my $skip = shift; + my %entry = parse_entry $raw, $parno, @_; + my @vetoes = grep /^\s*-1:/, @{$entry{votes}}; + + my $match = defined($skip) ? ($raw =~ /\Q$skip\E/ or $raw =~ /$skip/msi) : 0 + unless $YES; if ($YES) { - merge %entry unless @vetoes; + # Run a merge if: + unless (@vetoes) { + if ($MAY_COMMIT and $in_approved) { + # svn-role mode + merge \%entry if validate_branch_contains_named_revisions %entry; + } elsif (!$MAY_COMMIT) { + # Scan-for-conflicts mode + + # First, sanity-check the entry. We ignore the result; even if it + # failed, we do want to check for conflicts, in the remainder of this + # block. + validate_branch_contains_named_revisions %entry; + + # E155015 is SVN_ERR_WC_FOUND_CONFLICT + my $expected_error_p = sub { + my ($exit_code, $outlines, $errlines) = @_; + ($exit_code == 0) + or + (grep /svn: E155015:/, @$errlines) + }; + merge \%entry, ($entry{depends} ? $expected_error_p : undef); + + my $output = `$SVN status`; + + # Pre-1.6 svn's don't have the 7th column, so fake it. + $output =~ s/^(......)/$1 /mg if $SVNvsn < 1_006_000; + + my (@conflicts) = ($output =~ m#^(?:C......|.C.....|......C)\s(.*)#mg); + if (@conflicts and !$entry{depends}) { + $ERRORS{$entry{id}} //= [\%entry, + sprintf "Conflicts on %s%s%s", + '[' x !!$#conflicts, + (join ', ', + map { basename $_ } + @conflicts), + ']' x !!$#conflicts, + ]; + say STDERR "Conflicts merging $entry{header}!"; + say STDERR ""; + say STDERR $output; + system "$SVN diff -- " . join ' ', shell_escape @conflicts; + } elsif (!@conflicts and $entry{depends}) { + # Not a warning since svn-role may commit the dependency without + # also committing the dependent in the same pass. + print "No conflicts merging $entry{header}, but conflicts were " + ."expected ('Depends:' header set)\n"; + } elsif (@conflicts) { + say "Conflicts found merging $entry{header}, as expected."; + } + revert verbose => 0; + } + } + } elsif (defined($skip) ? not $match : $state->{$entry{digest}}) { + print "\n\n"; + my $reason = defined($skip) ? "doesn't match pattern" + : "remove $STATEFILE to reset"; + say "Skipping $entry{header} ($reason):"; + say logsummarysummary \%entry; + } elsif ($match or not defined $skip) { + # This loop is just a hack because 'goto' panics. The goto should be where + # the "next PROMPT;" is; there's a "last;" at the end of the loop body. + PROMPT: while (1) { + say ""; + say "\n>>> $entry{header_start}:"; + say join ", ", map { "r$_" } @{$entry{revisions}} if @{$entry{revisions}}; + say "$BRANCHES/$entry{branch}" if $entry{branch}; + say "--accept=$entry{accept}" if $entry{accept}; + say ""; + say for @{$entry{logsummary}}; + say ""; + say for @{$entry{votes}}; + say ""; + say "Vetoes found!" if @vetoes; + + # See above for why the while(1). + QUESTION: while (1) { + my $key = $entry{digest}; + given (prompt 'Run a merge? [y,l,v,±1,±0,q,e,a, ,N] ', + verbose => 1, extra => qr/[+-]/) { + when (/^y/i) { + #validate_branch_contains_named_revisions %entry; + merge \%entry; + while (1) { + given (prompt "Shall I open a subshell? [ydN] ", verbose => 1) { + when (/^y/i) { + # TODO: if $MAY_COMMIT, save the log message to a file (say, + # backport.logmsg in the wcroot). + system($SHELL) == 0 + or warn "Creating an interactive subshell failed ($?): $!" + } + when (/^d/) { + system("$SVN diff | $PAGER") == 0 + or warn "diff failed ($?): $!"; + next; + } + when (/^N/i) { + # fall through. + } + default { + next; + } + } + revert verbose => 1; + next PROMPT; + } + # NOTREACHED + } + when (/^l/i) { + if ($entry{branch}) { + system "$SVN log --stop-on-copy -v -g -r 0:HEAD -- " + .shell_escape("$BRANCHES/$entry{branch}")." " + ."| $PAGER"; + } elsif (@{$entry{revisions}}) { + system "$SVN log ".(join ' ', map { "-r$_" } @{$entry{revisions}}) + ." -- ^/subversion | $PAGER"; + } else { + die "Assertion failed: entry has neither branch nor revisions:\n", + '[[[', (join ';;', %entry), ']]]'; + } + next PROMPT; + } + when (/^v/i) { + say ""; + say for @{$entry{entry}}; + say ""; + next QUESTION; + } + when (/^q/i) { + exit_stage_left $state, $approved, $votes; + } + when (/^a/i) { + $approved->{$key} = \%entry; + next PROMPT; + } + when (/^([+-][01])\s*$/i) { + next QUESTION if warned_cannot_commit "Entering a vote failed"; + $votes->{$key} = [$1, \%entry]; + say "Your '$1' vote has been recorded." if $VERBOSE; + last PROMPT; + } + when (/^e/i) { + prompt "Press the 'any' key to continue...\n" + if warned_cannot_commit "Committing this edit later on may fail"; + my $original = $entry{raw}; + $entry{raw} = edit_string $entry{raw}, $entry{header}, + trailing_eol => 2; + # TODO: parse the edited entry (empty lines, logsummary+votes, etc.) + $votes->{$key} = ['edit', \%entry] # marker for the 2nd pass + if $original ne $entry{raw}; + last PROMPT; + } + when (/^N/i) { + $state->{$entry{digest}}++; + last PROMPT; + } + when (/^\x20/) { + last PROMPT; # Fall off the end of the given/when block. + } + default { + say "Please use one of the options in brackets (q to quit)!"; + next QUESTION; + } + } + last; } # QUESTION + last; } # PROMPT } else { - print ""; - print "\n>>> The $entry{header}:"; - print join ", ", map { "r$_" } @{$entry{revisions}}; - print "$BRANCHES/$entry{branch}" if $entry{branch}; - print ""; - print for @{$entry{logsummary}}; - print ""; - print for @{$entry{votes}}; - print ""; - print "Vetoes found!" if @vetoes; - - merge %entry if prompt; + # NOTREACHED + die "Unreachable code reached."; } - # TODO: merge() changes ./STATUS, which we're reading below, but - # on my system the loop in main() doesn't seem to care. - 1; } -sub main { - usage, exit 0 if @ARGV; + +sub backport_main { + my %approved; + my %votes; + my $state = read_state; + my $renormalize; - open STATUS, "<", $STATUS or (usage, exit 1); + if (@ARGV && $ARGV[0] eq '--renormalize') { + $renormalize = 1; + shift; + } + + backport_usage, exit 0 if @ARGV > ($YES ? 0 : 1) or grep /^--help$/, @ARGV; + backport_usage, exit 0 if grep /^(?:-h|-\?|--help|help)$/, @ARGV; + my $skip = shift; # maybe undef + # assert not defined $skip if $YES; + + open STATUS, "<", $STATUS or (backport_usage, exit 1); # Because we use the ':normal' command in Vim... - die "A vim with the +ex_extra feature is required" - if `${VIM} --version` !~ /[+]ex_extra/; + die "A vim with the +ex_extra feature is required for --renormalize and " + ."\$MAY_COMMIT modes" + if ($renormalize or $MAY_COMMIT) and `${VIM} --version` !~ /[+]ex_extra/; # ### TODO: need to run 'revert' here # ### TODO: both here and in merge(), unlink files that previous merges added - die "Local mods to STATUS file $STATUS" if `$SVN status -q $STATUS`; + # When running from cron, there shouldn't be local mods. (For interactive + # usage, we preserve local mods to STATUS.) + system("$SVN info $STATUS >/dev/null") == 0 + or die "$0: svn error; point \$SVN to an appropriate binary"; + + check_local_mods_to_STATUS; + renormalize_STATUS if $renormalize; # Skip most of the file + $/ = ""; # paragraph mode while (<STATUS>) { - last if /^Approved changes/; - } - while (<STATUS>) { - last unless /^=+$/; + last if /^Status of \d+\.\d+/; } - $/ = ""; # paragraph mode + $SIG{INT} = \&maybe_revert unless $YES; + $SIG{TERM} = \&signal_handler unless $YES; + + my $in_approved = 0; while (<STATUS>) { + my $lines = $_; my @lines = split /\n/; given ($lines[0]) { # Section header when (/^[A-Z].*:$/i) { - print "\n\n=== $lines[0]" unless $YES; + say "\n\n=== $lines[0]" unless $YES; + $in_approved = $lines[0] =~ /^Approved changes/; + } + # Comment + when (/^[#\x5b]/i) { + next; } # Separator after section header when (/^=+$/i) { break; } # Backport entry? - when (/^ \*/) { + when (/^ *\*/) { warn "Too many bullets in $lines[0]" and next - if grep /^ \*/, @lines[1..$#lines]; - handle_entry @lines; + if grep /^ *\*/, @lines[1..$#lines]; + handle_entry $in_approved, \%approved, \%votes, $state, $lines, $., + $skip, + @lines; } default { - warn "Unknown entry '$lines[0]' at $ARGV:$.\n"; + warn "Unknown entry '$lines[0]'"; } } } + + exit_stage_left $state, \%approved, \%votes; } -&main +sub nominate_main { + my $had_local_mods; + + local $Text::Wrap::columns = 79; + + $had_local_mods = check_local_mods_to_STATUS; + + # Argument parsing. + nominate_usage, exit 0 if @ARGV != 2; + my (@revnums) = (+shift) =~ /(\d+)/g; + my $justification = shift; + + die "Unable to proceed." if warned_cannot_commit "Nominating failed"; + + @revnums = sort { $a <=> $b } keys %{{ map { $_ => 1 } @revnums }}; + die "No revision numbers specified" unless @revnums; + + # Determine whether a backport branch exists + my ($URL) = `$SVN info` =~ /^URL: (.*)$/m; + die "Can't retrieve URL of cwd" unless $URL; + + die unless shell_safe_path_or_url $URL; + system "$SVN info -- $URL-r$revnums[0] 2>/dev/null"; + my $branch = ($? == 0) ? basename("$URL-r$revnums[0]") : undef; + + # Construct entry. + my $logmsg = `$SVN propget --revprop -r $revnums[0] --strict svn:log '^/'`; + die "Can't fetch log message of r$revnums[0]: $!" unless $logmsg; + + unless ($logmsg =~ s/^(.*?)\n\n.*/$1/s) { + # "* file\n (symbol): Log message." + + # Strip before and after the first symbol's log message. + $logmsg =~ s/^.*?: //s; + $logmsg =~ s/^ \x28.*//ms; + + # Undo line wrapping. (We'll re-do it later.) + $logmsg =~ s/\s*\n\s+/ /g; + } + + my @lines; + warn "Wrapping [$logmsg]\n"; + push @lines, wrap " * ", ' 'x3, join ', ', map "r$_", @revnums; + push @lines, wrap ' 'x3, ' 'x3, split /\n/, $logmsg; + push @lines, " Justification:"; + push @lines, wrap ' 'x5, ' 'x5, $justification; + push @lines, " Branch: $branch" if defined $branch; + push @lines, " Votes:"; + push @lines, " +1: $AVAILID"; + push @lines, ""; + my $raw = join "", map "$_\n", @lines; + + # Open the file in line-mode (not paragraph-mode). + my @STATUS; + tie @STATUS, "Tie::File", $STATUS, recsep => "\n"; + my ($index) = grep { $STATUS[$_] =~ /^Veto/ } (0..$#STATUS); + die "Couldn't find where to add an entry" unless $index; + + # Add an empty line if needed. + if ($STATUS[$index-1] =~ /\S/) { + splice @STATUS, $index, 0, ""; + $index++; + } + + # Add the entry. + splice @STATUS, $index, 0, @lines; + + # Save. + untie @STATUS; + + # Done! + system "$SVN diff -- $STATUS"; + if (prompt "Commit this nomination? ") { + system "$SVN commit -m 'Nominate r$revnums[0].' -- $STATUS"; + exit $?; + } + elsif (!$had_local_mods or prompt "Revert STATUS (destroying local mods)? ") { + # TODO: we could be smarter and just un-splice the lines we'd added. + system "$SVN revert -- $STATUS"; + exit $?; + } + + exit 0; +} + +# Dispatch to the appropriate main(). +given (basename($0)) { + when (/^b$|backport/) { + chdir dirname $0 or die "Can't chdir: $!" if /^b$/; + &backport_main(@ARGV); + } + when (/^n$|nominate/) { + chdir dirname $0 or die "Can't chdir: $!" if /^n$/; + &nominate_main(@ARGV); + } + default { + &backport_main(@ARGV); + } +} diff --git a/tools/dist/backport_accept.dump b/tools/dist/backport_accept.dump new file mode 100644 index 0000000..9532dc3 --- /dev/null +++ b/tools/dist/backport_accept.dump @@ -0,0 +1,550 @@ +SVN-fs-dump-format-version: 2 + +UUID: 76cee987-25c9-4d6c-ad40-000000000003 + +Revision-number: 0 +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + +Revision-number: 1 +Prop-content-length: 83 +Content-length: 83 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 27 +Log message for revision 1. +PROPS-END + +Node-path: A +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E/alpha +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: d1fa4a3ced98961674a441930a51f2d3 +Text-content-sha1: b347d1da69df9a6a70433ceeaa0d46c8483e8c03 +Content-length: 36 + +PROPS-END +This is the file 'alpha'. + + +Node-path: A/B/E/beta +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 67c756078f24f946f6ec2d00d02f50e1 +Text-content-sha1: d001710ac8e622c6d1fe59b1e265a3908acdd2a3 +Content-length: 35 + +PROPS-END +This is the file 'beta'. + + +Node-path: A/B/F +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/lambda +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 27 +Text-content-md5: 911c7a8d869b8c1e566f57da54d889c6 +Text-content-sha1: 784a9298366863da2b65ebf82b4e1123755a2421 +Content-length: 37 + +PROPS-END +This is the file 'lambda'. + + +Node-path: A/C +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G/pi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: adddfc3e6b605b5f90ceeab11b4e8ab6 +Text-content-sha1: 411e258dc14b42701fdc29b75f653e93f8686415 +Content-length: 33 + +PROPS-END +This is the file 'pi'. + + +Node-path: A/D/G/rho +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 82f2211cf4ab22e3555fc7b835fbc604 +Text-content-sha1: 56388a031dffbf9df7c32e1f299b1d5d7ef60881 +Content-length: 34 + +PROPS-END +This is the file 'rho'. + + +Node-path: A/D/G/tau +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 9936e2716e469bb686deb98c280ead58 +Text-content-sha1: 62e8c07d56bee94ea4577e80414fa8805aaf0175 +Content-length: 34 + +PROPS-END +This is the file 'tau'. + + +Node-path: A/D/H +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/H/chi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 8f5ebad6d1f7775c2682e54417cbe4d3 +Text-content-sha1: abeac1bf62099ab66b44779198dc19f40e3244f4 +Content-length: 34 + +PROPS-END +This is the file 'chi'. + + +Node-path: A/D/H/omega +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: fe4ec8bdd3d2056db4f55b474a10fadc +Text-content-sha1: c06e671bf15a6af55086176a0931d3b5034c82e6 +Content-length: 36 + +PROPS-END +This is the file 'omega'. + + +Node-path: A/D/H/psi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: e81f8f68ba50e749c200cb3c9ce5d2b1 +Text-content-sha1: 9c438bde39e8ccbbd366df2638e3cb6700950204 +Content-length: 34 + +PROPS-END +This is the file 'psi'. + + +Node-path: A/D/gamma +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: 412138bd677d64cd1c32fafbffe6245d +Text-content-sha1: 74b75d7f2e1a0292f17d5a57c570bd89783f5d1c +Content-length: 36 + +PROPS-END +This is the file 'gamma'. + + +Node-path: A/mu +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: baf78ae06a2d5b7d9554c5f1280d3fa8 +Text-content-sha1: b4d00c56351d1a752e24d839d41a362d8da4a4c7 +Content-length: 33 + +PROPS-END +This is the file 'mu'. + + +Node-path: iota +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-content-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 +Content-length: 35 + +PROPS-END +This is the file 'iota'. + + +Revision-number: 2 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +Create trunk +PROPS-END + +Node-path: subversion +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/tags +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk/A +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: A + + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: iota +Text-copy-source-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-copy-source-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 + + +Node-path: A +Node-action: delete + + +Node-path: iota +Node-action: delete + + +Revision-number: 3 +Prop-content-length: 87 +Content-length: 87 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 31 +Create branch, with STATUS file +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: subversion/trunk + + +Node-path: branch/STATUS +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 4 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +First change +PROPS-END + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + +Revision-number: 5 +Prop-content-length: 69 +Content-length: 69 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 13 +Second change +PROPS-END + +Node-path: subversion/trunk/A/mu +Node-kind: file +Node-action: change +Text-content-length: 37 +Text-content-md5: eab751301b4e650c83324dfef4aad667 +Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99 +Content-length: 37 + +This is the file 'mu'. +Second change + + +Revision-number: 6 +Prop-content-length: 82 +Content-length: 82 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 26 +Conflicting change on iota +PROPS-END + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 53 +Text-content-md5: 0c42f8c8b103bf00045cdf514238cfab +Text-content-sha1: 440ad0a1673258aea8ba78fef0845e182757f8f9 +Content-length: 53 + +This is the file 'iota'. +Conflicts with first change + + +Revision-number: 7 +Prop-content-length: 67 +Content-length: 67 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 11 +Nominate r4 +PROPS-END + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 284 +Text-content-md5: f1f6d73c681587eba4082139a9f2b724 +Text-content-sha1: 251bb84036790a810b1f4cc7f7a4e64c6a54ce9b +Content-length: 284 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + +* r4 + default logsummary + Notes: Merge with --accept=theirs-conflict. + Votes: + +1: jrandom + + + +Revision-number: 8 +Prop-content-length: 206 +Content-length: 206 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 150 +Merge r4 from trunk, with --accept=theirs-conflict: + +* r4 + default logsummary + Notes: Merge with --accept=theirs-conflict. + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 54 +Content-length: 54 + +K 13 +svn:mergeinfo +V 19 +/subversion/trunk:4 +PROPS-END + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 185 +Text-content-md5: 6f71fec92afeaa5c1ebe02349f548ca9 +Text-content-sha1: eece02003d9c51610249e3fdd0d4e191e02ba3b7 +Content-length: 185 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + diff --git a/tools/dist/backport_branches.dump b/tools/dist/backport_branches.dump new file mode 100644 index 0000000..de6c800 --- /dev/null +++ b/tools/dist/backport_branches.dump @@ -0,0 +1,642 @@ +SVN-fs-dump-format-version: 2 + +UUID: 76cee987-25c9-4d6c-ad40-000000000004 + +Revision-number: 0 +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + +Revision-number: 1 +Prop-content-length: 83 +Content-length: 83 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 27 +Log message for revision 1. +PROPS-END + +Node-path: A +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E/alpha +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: d1fa4a3ced98961674a441930a51f2d3 +Text-content-sha1: b347d1da69df9a6a70433ceeaa0d46c8483e8c03 +Content-length: 36 + +PROPS-END +This is the file 'alpha'. + + +Node-path: A/B/E/beta +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 67c756078f24f946f6ec2d00d02f50e1 +Text-content-sha1: d001710ac8e622c6d1fe59b1e265a3908acdd2a3 +Content-length: 35 + +PROPS-END +This is the file 'beta'. + + +Node-path: A/B/F +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/lambda +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 27 +Text-content-md5: 911c7a8d869b8c1e566f57da54d889c6 +Text-content-sha1: 784a9298366863da2b65ebf82b4e1123755a2421 +Content-length: 37 + +PROPS-END +This is the file 'lambda'. + + +Node-path: A/C +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G/pi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: adddfc3e6b605b5f90ceeab11b4e8ab6 +Text-content-sha1: 411e258dc14b42701fdc29b75f653e93f8686415 +Content-length: 33 + +PROPS-END +This is the file 'pi'. + + +Node-path: A/D/G/rho +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 82f2211cf4ab22e3555fc7b835fbc604 +Text-content-sha1: 56388a031dffbf9df7c32e1f299b1d5d7ef60881 +Content-length: 34 + +PROPS-END +This is the file 'rho'. + + +Node-path: A/D/G/tau +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 9936e2716e469bb686deb98c280ead58 +Text-content-sha1: 62e8c07d56bee94ea4577e80414fa8805aaf0175 +Content-length: 34 + +PROPS-END +This is the file 'tau'. + + +Node-path: A/D/H +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/H/chi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 8f5ebad6d1f7775c2682e54417cbe4d3 +Text-content-sha1: abeac1bf62099ab66b44779198dc19f40e3244f4 +Content-length: 34 + +PROPS-END +This is the file 'chi'. + + +Node-path: A/D/H/omega +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: fe4ec8bdd3d2056db4f55b474a10fadc +Text-content-sha1: c06e671bf15a6af55086176a0931d3b5034c82e6 +Content-length: 36 + +PROPS-END +This is the file 'omega'. + + +Node-path: A/D/H/psi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: e81f8f68ba50e749c200cb3c9ce5d2b1 +Text-content-sha1: 9c438bde39e8ccbbd366df2638e3cb6700950204 +Content-length: 34 + +PROPS-END +This is the file 'psi'. + + +Node-path: A/D/gamma +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: 412138bd677d64cd1c32fafbffe6245d +Text-content-sha1: 74b75d7f2e1a0292f17d5a57c570bd89783f5d1c +Content-length: 36 + +PROPS-END +This is the file 'gamma'. + + +Node-path: A/mu +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: baf78ae06a2d5b7d9554c5f1280d3fa8 +Text-content-sha1: b4d00c56351d1a752e24d839d41a362d8da4a4c7 +Content-length: 33 + +PROPS-END +This is the file 'mu'. + + +Node-path: iota +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-content-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 +Content-length: 35 + +PROPS-END +This is the file 'iota'. + + +Revision-number: 2 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +Create trunk +PROPS-END + +Node-path: subversion +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/tags +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk/A +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: A + + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: iota +Text-copy-source-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-copy-source-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 + + +Node-path: A +Node-action: delete + + +Node-path: iota +Node-action: delete + + +Revision-number: 3 +Prop-content-length: 87 +Content-length: 87 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 31 +Create branch, with STATUS file +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: subversion/trunk + + +Node-path: branch/STATUS +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 4 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +First change +PROPS-END + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + +Revision-number: 5 +Prop-content-length: 69 +Content-length: 69 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 13 +Second change +PROPS-END + +Node-path: subversion/trunk/A/mu +Node-kind: file +Node-action: change +Text-content-length: 37 +Text-content-md5: eab751301b4e650c83324dfef4aad667 +Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99 +Content-length: 37 + +This is the file 'mu'. +Second change + + +Revision-number: 6 +Prop-content-length: 82 +Content-length: 82 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 26 +Conflicting change on iota +PROPS-END + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 52 +Text-content-md5: 2309abeef2762865a65aef15a23bd613 +Text-content-sha1: d3339d12dee6df117675e9abf30ebfa1a1dde889 +Content-length: 52 + +This is the file 'iota'. +Conflicts with first change + +Revision-number: 7 +Prop-content-length: 80 +Content-length: 80 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 24 +Create a backport branch +PROPS-END + +Node-path: subversion/branches/r4 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 6 +Node-copyfrom-path: branch + + +Revision-number: 8 +Prop-content-length: 85 +Content-length: 85 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 29 +Conflict resolution via mkdir +PROPS-END + +Node-path: subversion/branches/r4 +Node-kind: dir +Node-action: change +Prop-content-length: 54 +Content-length: 54 + +K 13 +svn:mergeinfo +V 19 +/subversion/trunk:4 +PROPS-END + + +Node-path: subversion/branches/r4/A_resolved +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/branches/r4/iota +Node-kind: file +Node-action: change +Text-content-length: 9 +Text-content-md5: 1d0413d4da6866dae63f902165786614 +Text-content-sha1: e2cb0815ec8f0a8b36c6aa910c1f894ec1487da3 +Content-length: 9 + +resolved + + +Revision-number: 9 +Prop-content-length: 67 +Content-length: 67 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 11 +Nominate r4 +PROPS-END + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 256 +Text-content-md5: 76f9bca3ededa2eb3c196ef0bbc9ee1b +Text-content-sha1: 283a9f7ec716dc64b5ec8e5e1d9739d55e34b2d5 +Content-length: 256 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + * r4 + default logsummary + Branch: r4 + Votes: + +1: jrandom + + + +Revision-number: 10 +Prop-content-length: 146 +Content-length: 146 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 91 +Merge the r4 branch: + + * r4 + default logsummary + Branch: r4 + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 82 +Content-length: 82 + +K 13 +svn:mergeinfo +V 47 +/subversion/branches/r4:7-9 +/subversion/trunk:4 +PROPS-END + + +Node-path: branch/A_resolved +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 9 +Node-copyfrom-path: subversion/branches/r4/A_resolved + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 185 +Text-content-md5: 6f71fec92afeaa5c1ebe02349f548ca9 +Text-content-sha1: eece02003d9c51610249e3fdd0d4e191e02ba3b7 +Content-length: 185 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 9 +Text-content-md5: 1d0413d4da6866dae63f902165786614 +Text-content-sha1: e2cb0815ec8f0a8b36c6aa910c1f894ec1487da3 +Content-length: 9 + +resolved + + +Revision-number: 11 +Prop-content-length: 93 +Content-length: 93 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 38 +Remove the 'r4' branch, merged in r10. +PROPS-END + +Node-path: subversion/branches/r4 +Node-action: delete + + diff --git a/tools/dist/backport_indented_entry.dump b/tools/dist/backport_indented_entry.dump new file mode 100644 index 0000000..bbc501d --- /dev/null +++ b/tools/dist/backport_indented_entry.dump @@ -0,0 +1,522 @@ +SVN-fs-dump-format-version: 2 + +UUID: 76cee987-25c9-4d6c-ad40-000000000001 + +Revision-number: 0 +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + +Revision-number: 1 +Prop-content-length: 83 +Content-length: 83 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 27 +Log message for revision 1. +PROPS-END + +Node-path: A +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E/alpha +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: d1fa4a3ced98961674a441930a51f2d3 +Text-content-sha1: b347d1da69df9a6a70433ceeaa0d46c8483e8c03 +Content-length: 36 + +PROPS-END +This is the file 'alpha'. + + +Node-path: A/B/E/beta +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 67c756078f24f946f6ec2d00d02f50e1 +Text-content-sha1: d001710ac8e622c6d1fe59b1e265a3908acdd2a3 +Content-length: 35 + +PROPS-END +This is the file 'beta'. + + +Node-path: A/B/F +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/lambda +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 27 +Text-content-md5: 911c7a8d869b8c1e566f57da54d889c6 +Text-content-sha1: 784a9298366863da2b65ebf82b4e1123755a2421 +Content-length: 37 + +PROPS-END +This is the file 'lambda'. + + +Node-path: A/C +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G/pi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: adddfc3e6b605b5f90ceeab11b4e8ab6 +Text-content-sha1: 411e258dc14b42701fdc29b75f653e93f8686415 +Content-length: 33 + +PROPS-END +This is the file 'pi'. + + +Node-path: A/D/G/rho +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 82f2211cf4ab22e3555fc7b835fbc604 +Text-content-sha1: 56388a031dffbf9df7c32e1f299b1d5d7ef60881 +Content-length: 34 + +PROPS-END +This is the file 'rho'. + + +Node-path: A/D/G/tau +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 9936e2716e469bb686deb98c280ead58 +Text-content-sha1: 62e8c07d56bee94ea4577e80414fa8805aaf0175 +Content-length: 34 + +PROPS-END +This is the file 'tau'. + + +Node-path: A/D/H +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/H/chi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 8f5ebad6d1f7775c2682e54417cbe4d3 +Text-content-sha1: abeac1bf62099ab66b44779198dc19f40e3244f4 +Content-length: 34 + +PROPS-END +This is the file 'chi'. + + +Node-path: A/D/H/omega +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: fe4ec8bdd3d2056db4f55b474a10fadc +Text-content-sha1: c06e671bf15a6af55086176a0931d3b5034c82e6 +Content-length: 36 + +PROPS-END +This is the file 'omega'. + + +Node-path: A/D/H/psi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: e81f8f68ba50e749c200cb3c9ce5d2b1 +Text-content-sha1: 9c438bde39e8ccbbd366df2638e3cb6700950204 +Content-length: 34 + +PROPS-END +This is the file 'psi'. + + +Node-path: A/D/gamma +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: 412138bd677d64cd1c32fafbffe6245d +Text-content-sha1: 74b75d7f2e1a0292f17d5a57c570bd89783f5d1c +Content-length: 36 + +PROPS-END +This is the file 'gamma'. + + +Node-path: A/mu +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: baf78ae06a2d5b7d9554c5f1280d3fa8 +Text-content-sha1: b4d00c56351d1a752e24d839d41a362d8da4a4c7 +Content-length: 33 + +PROPS-END +This is the file 'mu'. + + +Node-path: iota +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-content-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 +Content-length: 35 + +PROPS-END +This is the file 'iota'. + + +Revision-number: 2 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +Create trunk +PROPS-END + +Node-path: subversion +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/tags +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk/A +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: A + + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: iota +Text-copy-source-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-copy-source-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 + + +Node-path: A +Node-action: delete + + +Node-path: iota +Node-action: delete + + +Revision-number: 3 +Prop-content-length: 87 +Content-length: 87 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 31 +Create branch, with STATUS file +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: subversion/trunk + + +Node-path: branch/STATUS +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 4 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +First change +PROPS-END + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + +Revision-number: 5 +Prop-content-length: 69 +Content-length: 69 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 13 +Second change +PROPS-END + +Node-path: subversion/trunk/A/mu +Node-kind: file +Node-action: change +Text-content-length: 37 +Text-content-md5: eab751301b4e650c83324dfef4aad667 +Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99 +Content-length: 37 + +This is the file 'mu'. +Second change + + +Revision-number: 6 +Prop-content-length: 67 +Content-length: 67 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 11 +Nominate r4 +PROPS-END + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 238 +Text-content-md5: d746b12362ddd59c13d39f291710b25b +Text-content-sha1: aafcdde209c276ffd2d63d6cd4c4b5ab35b36c27 +Content-length: 238 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + +* r4 + default logsummary + Votes: + +1: jrandom + + + +Revision-number: 7 +Prop-content-length: 128 +Content-length: 128 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 73 +Merge r4 from trunk: + +* r4 + default logsummary + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 54 +Content-length: 54 + +K 13 +svn:mergeinfo +V 19 +/subversion/trunk:4 +PROPS-END + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 185 +Text-content-md5: 6f71fec92afeaa5c1ebe02349f548ca9 +Text-content-sha1: eece02003d9c51610249e3fdd0d4e191e02ba3b7 +Content-length: 185 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + diff --git a/tools/dist/backport_multirevisions.dump b/tools/dist/backport_multirevisions.dump new file mode 100644 index 0000000..d04c850 --- /dev/null +++ b/tools/dist/backport_multirevisions.dump @@ -0,0 +1,534 @@ +SVN-fs-dump-format-version: 2 + +UUID: 76cee987-25c9-4d6c-ad40-000000000005 + +Revision-number: 0 +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + +Revision-number: 1 +Prop-content-length: 83 +Content-length: 83 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 27 +Log message for revision 1. +PROPS-END + +Node-path: A +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E/alpha +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: d1fa4a3ced98961674a441930a51f2d3 +Text-content-sha1: b347d1da69df9a6a70433ceeaa0d46c8483e8c03 +Content-length: 36 + +PROPS-END +This is the file 'alpha'. + + +Node-path: A/B/E/beta +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 67c756078f24f946f6ec2d00d02f50e1 +Text-content-sha1: d001710ac8e622c6d1fe59b1e265a3908acdd2a3 +Content-length: 35 + +PROPS-END +This is the file 'beta'. + + +Node-path: A/B/F +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/lambda +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 27 +Text-content-md5: 911c7a8d869b8c1e566f57da54d889c6 +Text-content-sha1: 784a9298366863da2b65ebf82b4e1123755a2421 +Content-length: 37 + +PROPS-END +This is the file 'lambda'. + + +Node-path: A/C +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G/pi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: adddfc3e6b605b5f90ceeab11b4e8ab6 +Text-content-sha1: 411e258dc14b42701fdc29b75f653e93f8686415 +Content-length: 33 + +PROPS-END +This is the file 'pi'. + + +Node-path: A/D/G/rho +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 82f2211cf4ab22e3555fc7b835fbc604 +Text-content-sha1: 56388a031dffbf9df7c32e1f299b1d5d7ef60881 +Content-length: 34 + +PROPS-END +This is the file 'rho'. + + +Node-path: A/D/G/tau +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 9936e2716e469bb686deb98c280ead58 +Text-content-sha1: 62e8c07d56bee94ea4577e80414fa8805aaf0175 +Content-length: 34 + +PROPS-END +This is the file 'tau'. + + +Node-path: A/D/H +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/H/chi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 8f5ebad6d1f7775c2682e54417cbe4d3 +Text-content-sha1: abeac1bf62099ab66b44779198dc19f40e3244f4 +Content-length: 34 + +PROPS-END +This is the file 'chi'. + + +Node-path: A/D/H/omega +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: fe4ec8bdd3d2056db4f55b474a10fadc +Text-content-sha1: c06e671bf15a6af55086176a0931d3b5034c82e6 +Content-length: 36 + +PROPS-END +This is the file 'omega'. + + +Node-path: A/D/H/psi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: e81f8f68ba50e749c200cb3c9ce5d2b1 +Text-content-sha1: 9c438bde39e8ccbbd366df2638e3cb6700950204 +Content-length: 34 + +PROPS-END +This is the file 'psi'. + + +Node-path: A/D/gamma +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: 412138bd677d64cd1c32fafbffe6245d +Text-content-sha1: 74b75d7f2e1a0292f17d5a57c570bd89783f5d1c +Content-length: 36 + +PROPS-END +This is the file 'gamma'. + + +Node-path: A/mu +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: baf78ae06a2d5b7d9554c5f1280d3fa8 +Text-content-sha1: b4d00c56351d1a752e24d839d41a362d8da4a4c7 +Content-length: 33 + +PROPS-END +This is the file 'mu'. + + +Node-path: iota +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-content-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 +Content-length: 35 + +PROPS-END +This is the file 'iota'. + + +Revision-number: 2 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +Create trunk +PROPS-END + +Node-path: subversion +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/tags +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk/A +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: A + + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: iota +Text-copy-source-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-copy-source-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 + + +Node-path: A +Node-action: delete + + +Node-path: iota +Node-action: delete + + +Revision-number: 3 +Prop-content-length: 87 +Content-length: 87 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 31 +Create branch, with STATUS file +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: subversion/trunk + + +Node-path: branch/STATUS +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 4 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +First change +PROPS-END + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + +Revision-number: 5 +Prop-content-length: 69 +Content-length: 69 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 13 +Second change +PROPS-END + +Node-path: subversion/trunk/A/mu +Node-kind: file +Node-action: change +Text-content-length: 37 +Text-content-md5: eab751301b4e650c83324dfef4aad667 +Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99 +Content-length: 37 + +This is the file 'mu'. +Second change + + +Revision-number: 6 +Prop-content-length: 73 +Content-length: 73 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 17 +Nominate a group. +PROPS-END + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 246 +Text-content-md5: 50068058cd9700828164f97c8bc9e44e +Text-content-sha1: 02f8ed7e3256e1eabd302b8f5b6e35000e2d4ce8 +Content-length: 246 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + * r4, r5 + default logsummary + Votes: + +1: jrandom + + + +Revision-number: 7 +Prop-content-length: 146 +Content-length: 146 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 91 +Merge the r4 group from trunk: + + * r4, r5 + default logsummary + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 56 +Content-length: 56 + +K 13 +svn:mergeinfo +V 21 +/subversion/trunk:4-5 +PROPS-END + + +Node-path: branch/A/mu +Node-kind: file +Node-action: change +Text-content-length: 37 +Text-content-md5: eab751301b4e650c83324dfef4aad667 +Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99 +Content-length: 37 + +This is the file 'mu'. +Second change + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 185 +Text-content-md5: 6f71fec92afeaa5c1ebe02349f548ca9 +Text-content-sha1: eece02003d9c51610249e3fdd0d4e191e02ba3b7 +Content-length: 185 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + diff --git a/tools/dist/backport_tests.py b/tools/dist/backport_tests.py new file mode 100755 index 0000000..e2b4862 --- /dev/null +++ b/tools/dist/backport_tests.py @@ -0,0 +1,578 @@ +#!/usr/bin/env python +# py:encoding=utf-8 +# +# backport_tests.py: Test backport.pl +# +# Subversion is a tool for revision control. +# See http://subversion.apache.org for more information. +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### + +# General modules +import contextlib +import functools +import os +import re +import sys + +@contextlib.contextmanager +def chdir(dir): + try: + saved_dir = os.getcwd() + os.chdir(dir) + yield + finally: + os.chdir(saved_dir) + +# Our testing module +# HACK: chdir to cause svntest.main.svn_binary to be set correctly +sys.path.insert(0, os.path.abspath('../../subversion/tests/cmdline')) +with chdir('../../subversion/tests/cmdline'): + import svntest + +# (abbreviations) +Skip = svntest.testcase.Skip_deco +SkipUnless = svntest.testcase.SkipUnless_deco +XFail = svntest.testcase.XFail_deco +Issues = svntest.testcase.Issues_deco +Issue = svntest.testcase.Issue_deco +Wimp = svntest.testcase.Wimp_deco + +###################################################################### +# Helper functions + +BACKPORT_PL = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'backport.pl')) +STATUS = 'branch/STATUS' + +class BackportTest(object): + """Decorator. See self.__call__().""" + + def __init__(self, uuid): + """The argument is the UUID embedded in the dump file. + If the argument is None, then there is no dump file.""" + self.uuid = uuid + + def __call__(self, test_func): + """Return a decorator that: builds TEST_FUNC's sbox, creates + ^/subversion/trunk, and calls TEST_FUNC, then compare its output to the + expected dump file named after TEST_FUNC.""" + + # .wraps() propagates the wrappee's docstring to the wrapper. + @functools.wraps(test_func) + def wrapped_test_func(sbox): + expected_dump_file = './%s.dump' % (test_func.func_name,) + + sbox.build() + + # r2: prepare ^/subversion/ tree + sbox.simple_mkdir('subversion', 'subversion/trunk') + sbox.simple_mkdir('subversion/tags', 'subversion/branches') + sbox.simple_move('A', 'subversion/trunk') + sbox.simple_move('iota', 'subversion/trunk') + sbox.simple_commit(message='Create trunk') + + # r3: branch + sbox.simple_copy('subversion/trunk', 'branch') + sbox.simple_append('branch/STATUS', '') + sbox.simple_add('branch/STATUS') + sbox.simple_commit(message='Create branch, with STATUS file') + + # r4: random change on trunk + sbox.simple_append('subversion/trunk/iota', 'First change\n') + sbox.simple_commit(message='First change') + + # r5: random change on trunk + sbox.simple_append('subversion/trunk/A/mu', 'Second change\n') + sbox.simple_commit(message='Second change') + + # Do the work. + test_func(sbox) + + # Verify it. + verify_backport(sbox, expected_dump_file, self.uuid) + return wrapped_test_func + +def make_entry(revisions=None, logsummary=None, notes=None, branch=None, + depends=None, votes=None): + assert revisions + if logsummary is None: + logsummary = "default logsummary" + if votes is None: + votes = {+1 : ['jrandom']} + + entry = { + 'revisions': revisions, + 'logsummary': logsummary, + 'notes': notes, + 'branch': branch, + 'depends': depends, + 'votes': votes, + } + + return entry + +def serialize_entry(entry): + return ''.join([ + + # revisions, + ' * %s\n' + % (", ".join("r%ld" % revision for revision in entry['revisions'])), + + # logsummary + ' %s\n' % (entry['logsummary'],), + + # notes + ' Notes: %s\n' % (entry['notes'],) if entry['notes'] else '', + + # branch + ' Branch: %s\n' % (entry['branch'],) if entry['branch'] else '', + + # depends + ' Depends: %s\n' % (entry['depends'],) if entry['depends'] else '', + + # votes + ' Votes:\n', + ''.join(' ' + '%s: %s\n' % ({1: '+1', 0: '+0', -1: '-1', -0: '-0'}[vote], + ", ".join(entry['votes'][vote])) + for vote in entry['votes']), + + '\n', # empty line after entry + ]) + +def serialize_STATUS(approveds, + serialize_entry=serialize_entry): + """Construct and return the contents of a STATUS file. + + APPROVEDS is an iterable of ENTRY dicts. The dicts are defined + to have the following keys: 'revisions', a list of revision numbers (ints); + 'logsummary'; and 'votes', a dict mapping ±1/±0 (int) to list of voters. + """ + + strings = [] + strings.append("Status of 1.8.x:\n\n") + + strings.append("Candidate changes:\n") + strings.append("==================\n\n") + + strings.append("Random new subheading:\n") + strings.append("======================\n\n") + + strings.append("Veto-blocked changes:\n") + strings.append("=====================\n\n") + + strings.append("Approved changes:\n") + strings.append("=================\n\n") + + strings.extend(map(serialize_entry, approveds)) + + return "".join(strings) + +def run_backport(sbox, error_expected=False, extra_env=[]): + """Run backport.pl. EXTRA_ENV is a list of key=value pairs (str) to set in + the child's environment. ERROR_EXPECTED is propagated to run_command().""" + # TODO: if the test is run in verbose mode, pass DEBUG=1 in the environment, + # and pass error_expected=True to run_command() to not croak on + # stderr output from the child (because it uses 'sh -x'). + args = [ + '/usr/bin/env', + 'SVN=' + svntest.main.svn_binary, + 'YES=1', 'MAY_COMMIT=1', 'AVAILID=jrandom', + ] + list(extra_env) + [ + 'perl', BACKPORT_PL, + ] + with chdir(sbox.ospath('branch')): + return svntest.main.run_command(args[0], error_expected, False, *(args[1:])) + +def verify_backport(sbox, expected_dump_file, uuid): + """Compare the contents of the SBOX repository with EXPECTED_DUMP_FILE. + Set the UUID of SBOX to UUID beforehand. + Based on svnsync_tests.py:verify_mirror.""" + + if uuid is None: + # There is no expected dump file. + return + + # Remove some SVNSync-specific housekeeping properties from the + # mirror repository in preparation for the comparison dump. + svntest.actions.enable_revprop_changes(sbox.repo_dir) + for revnum in range(0, 1+int(sbox.youngest())): + svntest.actions.run_and_verify_svnadmin([], [], + "delrevprop", "-r", revnum, sbox.repo_dir, "svn:date") + + # Create a dump file from the mirror repository. + dest_dump = open(expected_dump_file).readlines() + svntest.actions.run_and_verify_svnadmin(None, [], + 'setuuid', '--', sbox.repo_dir, uuid) + src_dump = svntest.actions.run_and_verify_dump(sbox.repo_dir) + + svntest.verify.compare_dump_files( + "Dump files", "DUMP", src_dump, dest_dump) + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + +#---------------------------------------------------------------------- +@BackportTest('76cee987-25c9-4d6c-ad40-000000000001') +def backport_indented_entry(sbox): + "parsing of entries with nonstandard indentation" + + # r6: nominate r4 + approved_entries = [ + make_entry([4]), + ] + def reindenting_serialize_entry(*args, **kwargs): + entry = serialize_entry(*args, **kwargs) + return ('\n' + entry).replace('\n ', '\n')[1:] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries, + serialize_entry=reindenting_serialize_entry)) + sbox.simple_commit(message='Nominate r4') + + # Run it. + run_backport(sbox) + + +#---------------------------------------------------------------------- +@BackportTest('76cee987-25c9-4d6c-ad40-000000000002') +def backport_two_approveds(sbox): + "backport with two approveds" + + # r6: Enter votes + approved_entries = [ + make_entry([4]), + make_entry([5]), + ] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries)) + sbox.simple_commit(message='Nominate r4. Nominate r5.') + + # r7, r8: Run it. + run_backport(sbox) + + # Now back up and do three entries. + # r9: revert r7, r8 + svntest.actions.run_and_verify_svnlook(["8\n"], [], + 'youngest', sbox.repo_dir) + sbox.simple_update() + svntest.main.run_svn(None, 'merge', '-r8:6', + '^/branch', sbox.ospath('branch')) + sbox.simple_commit(message='Revert the merges.') + + # r10: Another change on trunk. + # (Note that this change must be merged after r5.) + sbox.simple_rm('subversion/trunk/A') + sbox.simple_commit(message='Third change on trunk.') + + # r11: Nominate r10. + sbox.simple_append(STATUS, serialize_entry(make_entry([10]))) + sbox.simple_commit(message='Nominate r10.') + + # r12, r13, r14: Run it. + run_backport(sbox) + + + +#---------------------------------------------------------------------- +@BackportTest('76cee987-25c9-4d6c-ad40-000000000003') +def backport_accept(sbox): + "test --accept parsing" + + # r6: conflicting change on branch + sbox.simple_append('branch/iota', 'Conflicts with first change\n') + sbox.simple_commit(message="Conflicting change on iota") + + # r7: nominate r4 with --accept (because of r6) + approved_entries = [ + make_entry([4], notes="Merge with --accept=theirs-conflict."), + ] + def reindenting_serialize_entry(*args, **kwargs): + entry = serialize_entry(*args, **kwargs) + return ('\n' + entry).replace('\n ', '\n')[1:] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries, + serialize_entry=reindenting_serialize_entry)) + sbox.simple_commit(message='Nominate r4') + + # Run it. + run_backport(sbox) + + +#---------------------------------------------------------------------- +@BackportTest('76cee987-25c9-4d6c-ad40-000000000004') +def backport_branches(sbox): + "test branches" + + # r6: conflicting change on branch + sbox.simple_append('branch/iota', 'Conflicts with first change') + sbox.simple_commit(message="Conflicting change on iota") + + # r7: backport branch + sbox.simple_update() + sbox.simple_copy('branch', 'subversion/branches/r4') + sbox.simple_commit(message='Create a backport branch') + + # r8: merge into backport branch + sbox.simple_update() + svntest.main.run_svn(None, 'merge', '--record-only', '-c4', + '^/subversion/trunk', sbox.ospath('subversion/branches/r4')) + sbox.simple_mkdir('subversion/branches/r4/A_resolved') + sbox.simple_append('subversion/branches/r4/iota', "resolved\n", truncate=1) + sbox.simple_commit(message='Conflict resolution via mkdir') + + # r9: nominate r4 with branch + approved_entries = [ + make_entry([4], branch="r4") + ] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries)) + sbox.simple_commit(message='Nominate r4') + + # Run it. + run_backport(sbox) + + # This also serves as the 'success mode' part of backport_branch_contains(). + + +#---------------------------------------------------------------------- +@BackportTest('76cee987-25c9-4d6c-ad40-000000000005') +def backport_multirevisions(sbox): + "test multirevision entries" + + # r6: nominate r4,r5 + approved_entries = [ + make_entry([4,5]) + ] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries)) + sbox.simple_commit(message='Nominate a group.') + + # Run it. + run_backport(sbox) + + +#---------------------------------------------------------------------- +@BackportTest(None) # would be 000000000006 +def backport_conflicts_detection(sbox): + "test the conflicts detector" + + # r6: conflicting change on branch + sbox.simple_append('branch/iota', 'Conflicts with first change\n') + sbox.simple_commit(message="Conflicting change on iota") + + # r7: nominate r4, but without the requisite --accept + approved_entries = [ + make_entry([4], notes="This will conflict."), + ] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries)) + sbox.simple_commit(message='Nominate r4') + + # Run it. + exit_code, output, errput = run_backport(sbox, True, + # Choose conflicts mode: + ["MAY_COMMIT=0"]) + + # Verify the conflict is detected. + expected_output = svntest.verify.RegexOutput( + 'Index: iota', + match_all=False, + ) + expected_errput = ( + r'(?ms)' # re.MULTILINE | re.DOTALL + r'.*Warning summary.*' + r'^r4 [(]default logsummary[)]: Conflicts on iota.*' + ) + expected_errput = svntest.verify.RegexListOutput( + [ + r'Warning summary', + r'===============', + r'r4 [(]default logsummary[)]: Conflicts on iota', + ], + match_all=False) + svntest.verify.verify_outputs(None, output, errput, + expected_output, expected_errput) + svntest.verify.verify_exit_code(None, exit_code, 1) + + ## Now, let's test the "Depends:" annotation silences the error. + + # Re-nominate. + approved_entries = [ + make_entry([4], depends="World peace."), + ] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries), truncate=True) + sbox.simple_commit(message='Re-nominate r4') + + # Detect conflicts. + exit_code, output, errput = run_backport(sbox, extra_env=["MAY_COMMIT=0"]) + + # Verify stdout. (exit_code and errput were verified by run_backport().) + svntest.verify.verify_outputs(None, output, errput, + "Conflicts found.*, as expected.", []) + + +#---------------------------------------------------------------------- +@BackportTest(None) # would be 000000000007 +def backport_branch_contains(sbox): + "branch must contain the revisions" + + # r6: conflicting change on branch + sbox.simple_append('branch/iota', 'Conflicts with first change') + sbox.simple_commit(message="Conflicting change on iota") + + # r7: backport branch + sbox.simple_update() + sbox.simple_copy('branch', 'subversion/branches/r4') + sbox.simple_commit(message='Create a backport branch') + + # r8: merge into backport branch + sbox.simple_update() + svntest.main.run_svn(None, 'merge', '--record-only', '-c4', + '^/subversion/trunk', sbox.ospath('subversion/branches/r4')) + sbox.simple_mkdir('subversion/branches/r4/A_resolved') + sbox.simple_append('subversion/branches/r4/iota', "resolved\n", truncate=1) + sbox.simple_commit(message='Conflict resolution via mkdir') + + # r9: nominate r4,r5 with branch that contains not all of them + approved_entries = [ + make_entry([4,5], branch="r4") + ] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries)) + sbox.simple_commit(message='Nominate r4') + + # Run it. + exit_code, output, errput = run_backport(sbox, error_expected=True) + + # Verify the error message. + expected_errput = svntest.verify.RegexOutput( + ".*Revisions 'r5' nominated but not included in branch", + match_all=False, + ) + svntest.verify.verify_outputs(None, output, errput, + [], expected_errput) + svntest.verify.verify_exit_code(None, exit_code, 1) + + # Verify no commit occurred. + svntest.actions.run_and_verify_svnlook(["9\n"], [], + 'youngest', sbox.repo_dir) + + # Verify the working copy has been reverted. + svntest.actions.run_and_verify_svn([], [], 'status', '-q', + sbox.repo_dir) + + # The sibling test backport_branches() verifies the success mode. + + + + +#---------------------------------------------------------------------- +@BackportTest(None) # would be 000000000008 +def backport_double_conflict(sbox): + "two-revisioned entry with two conflicts" + + # r6: conflicting change on branch + sbox.simple_append('branch/iota', 'Conflicts with first change') + sbox.simple_commit(message="Conflicting change on iota") + + # r7: further conflicting change to same file + sbox.simple_update() + sbox.simple_append('subversion/trunk/iota', 'Third line\n') + sbox.simple_commit(message="iota's third line") + + # r8: nominate + approved_entries = [ + make_entry([4,7], depends="World peace.") + ] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries)) + sbox.simple_commit(message='Nominate the r4 group') + + # Run it, in conflicts mode. + exit_code, output, errput = run_backport(sbox, True, ["MAY_COMMIT=0"]) + + # Verify the failure mode: "merge conflict" error on stderr, but backport.pl + # itself exits with code 0, since conflicts were confined to Depends:-ed + # entries. + # + # The error only happens with multi-pass merges where the first pass + # conflicts and the second pass touches the conflict victim. + # + # The error would be: + # subversion/libsvn_client/merge.c:5499: (apr_err=SVN_ERR_WC_FOUND_CONFLICT) + # svn: E155015: One or more conflicts were produced while merging r3:4 + # into '/tmp/stw/working_copies/backport_tests-8/branch' -- resolve all + # conflicts and rerun the merge to apply the remaining unmerged revisions + # ... + # Warning summary + # =============== + # + # r4 (default logsummary): subshell exited with code 256 + # And backport.pl would exit with exit code 1. + + expected_output = 'Conflicts found.*, as expected.' + expected_errput = svntest.verify.RegexOutput( + ".*svn: E155015:.*", # SVN_ERR_WC_FOUND_CONFLICT + match_all=False, + ) + svntest.verify.verify_outputs(None, output, errput, + expected_output, expected_errput) + svntest.verify.verify_exit_code(None, exit_code, 0) + if any("Warning summary" in line for line in errput): + raise svntest.verify.SVNUnexpectedStderr(errput) + + ## Now, let's ensure this does get detected if not silenced. + # r9: Re-nominate + approved_entries = [ + make_entry([4,7]) # no depends= + ] + sbox.simple_append(STATUS, serialize_STATUS(approved_entries), truncate=True) + sbox.simple_commit(message='Re-nominate the r4 group') + + exit_code, output, errput = run_backport(sbox, True, ["MAY_COMMIT=0"]) + + # [1-9]\d+ matches non-zero exit codes + expected_errput = r'r4 .*: subshell exited with code (?:[1-9]\d+)' + svntest.verify.verify_exit_code(None, exit_code, 1) + svntest.verify.verify_outputs(None, output, errput, + svntest.verify.AnyOutput, expected_errput) + + + +#---------------------------------------------------------------------- + +######################################################################## +# Run the tests + +# list all tests here, starting with None: +test_list = [ None, + backport_indented_entry, + backport_two_approveds, + backport_accept, + backport_branches, + backport_multirevisions, + backport_conflicts_detection, + backport_branch_contains, + backport_double_conflict, + # When adding a new test, include the test number in the last + # 6 bytes of the UUID. + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. diff --git a/tools/dist/backport_two_approveds.dump b/tools/dist/backport_two_approveds.dump new file mode 100644 index 0000000..c4349b2 --- /dev/null +++ b/tools/dist/backport_two_approveds.dump @@ -0,0 +1,961 @@ +SVN-fs-dump-format-version: 2 + +UUID: 76cee987-25c9-4d6c-ad40-000000000002 + +Revision-number: 0 +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + +Revision-number: 1 +Prop-content-length: 83 +Content-length: 83 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 27 +Log message for revision 1. +PROPS-END + +Node-path: A +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/E/alpha +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: d1fa4a3ced98961674a441930a51f2d3 +Text-content-sha1: b347d1da69df9a6a70433ceeaa0d46c8483e8c03 +Content-length: 36 + +PROPS-END +This is the file 'alpha'. + + +Node-path: A/B/E/beta +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 67c756078f24f946f6ec2d00d02f50e1 +Text-content-sha1: d001710ac8e622c6d1fe59b1e265a3908acdd2a3 +Content-length: 35 + +PROPS-END +This is the file 'beta'. + + +Node-path: A/B/F +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/B/lambda +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 27 +Text-content-md5: 911c7a8d869b8c1e566f57da54d889c6 +Text-content-sha1: 784a9298366863da2b65ebf82b4e1123755a2421 +Content-length: 37 + +PROPS-END +This is the file 'lambda'. + + +Node-path: A/C +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/G/pi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: adddfc3e6b605b5f90ceeab11b4e8ab6 +Text-content-sha1: 411e258dc14b42701fdc29b75f653e93f8686415 +Content-length: 33 + +PROPS-END +This is the file 'pi'. + + +Node-path: A/D/G/rho +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 82f2211cf4ab22e3555fc7b835fbc604 +Text-content-sha1: 56388a031dffbf9df7c32e1f299b1d5d7ef60881 +Content-length: 34 + +PROPS-END +This is the file 'rho'. + + +Node-path: A/D/G/tau +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 9936e2716e469bb686deb98c280ead58 +Text-content-sha1: 62e8c07d56bee94ea4577e80414fa8805aaf0175 +Content-length: 34 + +PROPS-END +This is the file 'tau'. + + +Node-path: A/D/H +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: A/D/H/chi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: 8f5ebad6d1f7775c2682e54417cbe4d3 +Text-content-sha1: abeac1bf62099ab66b44779198dc19f40e3244f4 +Content-length: 34 + +PROPS-END +This is the file 'chi'. + + +Node-path: A/D/H/omega +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: fe4ec8bdd3d2056db4f55b474a10fadc +Text-content-sha1: c06e671bf15a6af55086176a0931d3b5034c82e6 +Content-length: 36 + +PROPS-END +This is the file 'omega'. + + +Node-path: A/D/H/psi +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 24 +Text-content-md5: e81f8f68ba50e749c200cb3c9ce5d2b1 +Text-content-sha1: 9c438bde39e8ccbbd366df2638e3cb6700950204 +Content-length: 34 + +PROPS-END +This is the file 'psi'. + + +Node-path: A/D/gamma +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 26 +Text-content-md5: 412138bd677d64cd1c32fafbffe6245d +Text-content-sha1: 74b75d7f2e1a0292f17d5a57c570bd89783f5d1c +Content-length: 36 + +PROPS-END +This is the file 'gamma'. + + +Node-path: A/mu +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 23 +Text-content-md5: baf78ae06a2d5b7d9554c5f1280d3fa8 +Text-content-sha1: b4d00c56351d1a752e24d839d41a362d8da4a4c7 +Content-length: 33 + +PROPS-END +This is the file 'mu'. + + +Node-path: iota +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 25 +Text-content-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-content-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 +Content-length: 35 + +PROPS-END +This is the file 'iota'. + + +Revision-number: 2 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +Create trunk +PROPS-END + +Node-path: subversion +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/tags +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: subversion/trunk/A +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: A + + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: iota +Text-copy-source-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-copy-source-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 + + +Node-path: A +Node-action: delete + + +Node-path: iota +Node-action: delete + + +Revision-number: 3 +Prop-content-length: 87 +Content-length: 87 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 31 +Create branch, with STATUS file +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: subversion/trunk + + +Node-path: branch/STATUS +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709 +Content-length: 10 + +PROPS-END + + +Revision-number: 4 +Prop-content-length: 68 +Content-length: 68 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 12 +First change +PROPS-END + +Node-path: subversion/trunk/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + +Revision-number: 5 +Prop-content-length: 69 +Content-length: 69 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 13 +Second change +PROPS-END + +Node-path: subversion/trunk/A/mu +Node-kind: file +Node-action: change +Text-content-length: 37 +Text-content-md5: eab751301b4e650c83324dfef4aad667 +Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99 +Content-length: 37 + +This is the file 'mu'. +Second change + + +Revision-number: 6 +Prop-content-length: 82 +Content-length: 82 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 26 +Nominate r4. Nominate r5. +PROPS-END + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 298 +Text-content-md5: 4ebc11d7e1ec3a5cb75d3cfdcf0c1399 +Text-content-sha1: 86dd246b9072d6baeaac50f58ee2fa6444f6f889 +Content-length: 298 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + * r4 + default logsummary + Votes: + +1: jrandom + + * r5 + default logsummary + Votes: + +1: jrandom + + + +Revision-number: 7 +Prop-content-length: 132 +Content-length: 132 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 77 +Merge r4 from trunk: + + * r4 + default logsummary + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 54 +Content-length: 54 + +K 13 +svn:mergeinfo +V 19 +/subversion/trunk:4 +PROPS-END + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 241 +Text-content-md5: cd8d55451e22cd8f83599bc64e67b515 +Text-content-sha1: 6b54b54b2711d0de2f252f34c26f2ac8f222ce35 +Content-length: 241 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + * r5 + default logsummary + Votes: + +1: jrandom + + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + +Revision-number: 8 +Prop-content-length: 132 +Content-length: 132 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 77 +Merge r5 from trunk: + + * r5 + default logsummary + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 56 +Content-length: 56 + +K 13 +svn:mergeinfo +V 21 +/subversion/trunk:4-5 +PROPS-END + + +Node-path: branch/A/mu +Node-kind: file +Node-action: change +Text-content-length: 37 +Text-content-md5: eab751301b4e650c83324dfef4aad667 +Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99 +Content-length: 37 + +This is the file 'mu'. +Second change + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 185 +Text-content-md5: 6f71fec92afeaa5c1ebe02349f548ca9 +Text-content-sha1: eece02003d9c51610249e3fdd0d4e191e02ba3b7 +Content-length: 185 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + +Revision-number: 9 +Prop-content-length: 74 +Content-length: 74 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 18 +Revert the merges. +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: branch/A/mu +Node-kind: file +Node-action: change +Text-content-length: 23 +Text-content-md5: baf78ae06a2d5b7d9554c5f1280d3fa8 +Text-content-sha1: b4d00c56351d1a752e24d839d41a362d8da4a4c7 +Content-length: 23 + +This is the file 'mu'. + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 298 +Text-content-md5: 4ebc11d7e1ec3a5cb75d3cfdcf0c1399 +Text-content-sha1: 86dd246b9072d6baeaac50f58ee2fa6444f6f889 +Content-length: 298 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + * r4 + default logsummary + Votes: + +1: jrandom + + * r5 + default logsummary + Votes: + +1: jrandom + + + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 25 +Text-content-md5: 2d18c5e57e84c5b8a5e9a6e13fa394dc +Text-content-sha1: 2c0aa9014a0cd07f01795a333d82485ef6d083e2 +Content-length: 25 + +This is the file 'iota'. + + +Revision-number: 10 +Prop-content-length: 78 +Content-length: 78 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 22 +Third change on trunk. +PROPS-END + +Node-path: subversion/trunk/A +Node-action: delete + + +Revision-number: 11 +Prop-content-length: 69 +Content-length: 69 + +K 10 +svn:author +V 7 +jrandom +K 7 +svn:log +V 13 +Nominate r10. +PROPS-END + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 355 +Text-content-md5: cc8dd910efc8d555f5dc51e5c331b403 +Text-content-sha1: c67ec7e762d8f7dfa6d2b876e540a6038781171f +Content-length: 355 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + * r4 + default logsummary + Votes: + +1: jrandom + + * r5 + default logsummary + Votes: + +1: jrandom + + * r10 + default logsummary + Votes: + +1: jrandom + + + +Revision-number: 12 +Prop-content-length: 132 +Content-length: 132 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 77 +Merge r4 from trunk: + + * r4 + default logsummary + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 54 +Content-length: 54 + +K 13 +svn:mergeinfo +V 19 +/subversion/trunk:4 +PROPS-END + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 298 +Text-content-md5: 41e1f764781ee0b7874dc92607e9b9f6 +Text-content-sha1: 19e57ad83073cc50d86033ab0f03d3b8574c68fc +Content-length: 298 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + * r5 + default logsummary + Votes: + +1: jrandom + + * r10 + default logsummary + Votes: + +1: jrandom + + +Node-path: branch/iota +Node-kind: file +Node-action: change +Text-content-length: 38 +Text-content-md5: 67f471c2ecc2c9e561d122d6e6b0f847 +Text-content-sha1: 750accb6e7f880a1d05ce725c19eb60183bb4b26 +Content-length: 38 + +This is the file 'iota'. +First change + + +Revision-number: 13 +Prop-content-length: 132 +Content-length: 132 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 77 +Merge r5 from trunk: + + * r5 + default logsummary + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 56 +Content-length: 56 + +K 13 +svn:mergeinfo +V 21 +/subversion/trunk:4-5 +PROPS-END + + +Node-path: branch/A/mu +Node-kind: file +Node-action: change +Text-content-length: 37 +Text-content-md5: eab751301b4e650c83324dfef4aad667 +Text-content-sha1: ab36cad564c7c50dec5ac1eb0bf879cf4e3a5f99 +Content-length: 37 + +This is the file 'mu'. +Second change + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 242 +Text-content-md5: 30f964a922fe4e9f01b25a274c0a8efb +Text-content-sha1: f1180ea711cbbbbfb2af52cac509da15313ca319 +Content-length: 242 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + * r10 + default logsummary + Votes: + +1: jrandom + + +Revision-number: 14 +Prop-content-length: 134 +Content-length: 134 + +K 10 +svn:author +V 6 +daniel +K 7 +svn:log +V 79 +Merge r10 from trunk: + + * r10 + default logsummary + Votes: + +1: jrandom + +PROPS-END + +Node-path: branch +Node-kind: dir +Node-action: change +Prop-content-length: 59 +Content-length: 59 + +K 13 +svn:mergeinfo +V 24 +/subversion/trunk:4-5,10 +PROPS-END + + +Node-path: branch/STATUS +Node-kind: file +Node-action: change +Text-content-length: 185 +Text-content-md5: 6f71fec92afeaa5c1ebe02349f548ca9 +Text-content-sha1: eece02003d9c51610249e3fdd0d4e191e02ba3b7 +Content-length: 185 + +Status of 1.8.x: + +Candidate changes: +================== + +Random new subheading: +====================== + +Veto-blocked changes: +===================== + +Approved changes: +================= + + +Node-path: branch/A +Node-action: delete + + diff --git a/tools/dist/dist.sh b/tools/dist/dist.sh index 03d5c39..676db68 100755 --- a/tools/dist/dist.sh +++ b/tools/dist/dist.sh @@ -22,7 +22,7 @@ # USAGE: ./dist.sh -v VERSION -r REVISION -pr REPOS-PATH # [-alpha ALPHA_NUM|-beta BETA_NUM|-rc RC_NUM|pre PRE_NUM] -# [-apr PATH-TO-APR ] [-apru PATH-TO-APR-UTIL] +# [-apr PATH-TO-APR ] [-apru PATH-TO-APR-UTIL] # [-apri PATH-TO-APR-ICONV] [-neon PATH-TO-NEON] # [-serf PATH-TO-SERF] [-zlib PATH-TO-ZLIB] # [-sqlite PATH-TO-SQLITE] [-zip] [-sign] @@ -47,13 +47,13 @@ # working copy, so you may wish to create a dist-resources directory # containing the apr/, apr-util/, neon/, serf/, zlib/ and sqlite/ # dependencies, and run dist.sh from that. -# +# # When building alpha, beta or rc tarballs pass the appropriate flag # followed by a number. For example "-alpha 5", "-beta 3", "-rc 2". -# +# # If neither an -alpha, -beta, -pre or -rc option is specified, a release # tarball will be built. -# +# # To build a Windows zip file package, additionally pass -zip and the # path to apr-iconv with -apri. @@ -119,7 +119,7 @@ if [ -n "$ALPHA" ] && [ -n "$BETA" ] && [ -n "$NIGHTLY" ] && [ -n "$PRE" ] || exit 1 elif [ -n "$ALPHA" ] ; then VER_TAG="Alpha $ALPHA" - VER_NUMTAG="-alpha$ALPHA" + VER_NUMTAG="-alpha$ALPHA" elif [ -n "$BETA" ] ; then VER_TAG="Beta $BETA" VER_NUMTAG="-beta$BETA" @@ -183,20 +183,6 @@ if [ $? -ne 0 ] && [ -z "$ZIP" ]; then exit 1 fi -# Default to 'wget', but allow 'curl' to be used if available. -HTTP_FETCH=wget -HTTP_FETCH_OUTPUT="-O" -type wget > /dev/null 2>&1 -if [ $? -ne 0 ]; then - type curl > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo "Neither curl or wget found." - exit 2 - fi - HTTP_FETCH=curl - HTTP_FETCH_OUTPUT="-o" -fi - DISTNAME="subversion-${VERSION}${VER_NUMTAG}" DIST_SANDBOX=.dist_sandbox DISTPATH="$DIST_SANDBOX/$DISTNAME" @@ -306,6 +292,15 @@ if [ -z "$ZIP" ] ; then (cd "$DISTPATH" && ./autogen.sh --release) || exit 1 fi +# Generate the .pot file, for use by translators. +echo "Running po-update.sh in sandbox, to create subversion.pot..." +# Can't use the po-update.sh in the packaged export since it might have CRLF +# line endings, in which case it won't run. So first we export it again. +${svn:-svn} export -q -r "$REVISION" \ + "http://svn.apache.org/repos/asf/subversion/$REPOS_PATH/tools/po/po-update.sh" \ + --username none --password none "$DIST_SANDBOX/po-update.sh" +(cd "$DISTPATH" && ../po-update.sh pot) || exit 1 + # Pre-translate the various sql-derived header files echo "Generating SQL-derived headers..." for f in `find "$DISTPATH/subversion" -name '*.sql'`; do diff --git a/tools/dist/make-deps-tarball.sh b/tools/dist/make-deps-tarball.sh deleted file mode 100755 index 318adc6..0000000 --- a/tools/dist/make-deps-tarball.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/sh -# -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -# -set -e - -APR=apr-1.4.6 -APR_UTIL=apr-util-1.4.1 -NEON=neon-0.29.6 -SERF=serf-0.3.1 -ZLIB=zlib-1.2.7 -SQLITE_VERSION=3071400 -SQLITE=sqlite-amalgamation-$SQLITE_VERSION - -HTTPD=httpd-2.2.22 -HTTPD_OOPS= -APR_ICONV=apr-iconv-1.2.1 -APR_ICONV_OOPS= - -WIN32_APR_VIA_HTTPD=1 - -BASEDIR=`pwd` -TEMPDIR=$BASEDIR/temp - -APACHE_MIRROR=http://archive.apache.org/dist - -create_deps() { - SVN_VERSION="$1" - set -x - - mkdir -p $TEMPDIR - cd $TEMPDIR - wget -qnc $APACHE_MIRROR/apr/$APR.tar.bz2 - wget -qnc $APACHE_MIRROR/apr/$APR_UTIL.tar.bz2 - if [ -n "$WIN32_APR_VIA_HTTPD" ]; then - wget -qnc $APACHE_MIRROR/httpd/$HTTPD-win32-src$HTTPD_OOPS.zip - else - wget -qnc $APACHE_MIRROR/apr/$APR-win32-src.zip - wget -qnc $APACHE_MIRROR/apr/$APR_UTIL-win32-src.zip - wget -qnc $APACHE_MIRROR/apr/$APR_ICONV-win32-src$APR_ICONV_OOPS.zip - fi - wget -qnc http://webdav.org/neon/$NEON.tar.gz - wget -qnc http://serf.googlecode.com/files/$SERF.tar.bz2 - wget -qnc http://www.zlib.net/$ZLIB.tar.bz2 - wget -qnc http://www.sqlite.org/$SQLITE.zip - - mkdir $BASEDIR/unix-dependencies - cd $BASEDIR/unix-dependencies - tar zxf $TEMPDIR/$NEON.tar.gz - tar jxf $TEMPDIR/$ZLIB.tar.bz2 - tar jxf $TEMPDIR/$SERF.tar.bz2 - unzip -q $TEMPDIR/$SQLITE.zip - mv $NEON neon - mv $ZLIB zlib - mv $SERF serf - mv $SQLITE sqlite-amalgamation - tar jxf $TEMPDIR/$APR.tar.bz2 - tar jxf $TEMPDIR/$APR_UTIL.tar.bz2 - mv $APR apr - mv $APR_UTIL apr-util - cd $TEMPDIR - - mkdir $BASEDIR/win32-dependencies - cd $BASEDIR/win32-dependencies - tar zxf $TEMPDIR/$NEON.tar.gz - tar jxf $TEMPDIR/$ZLIB.tar.bz2 - tar jxf $TEMPDIR/$SERF.tar.bz2 - unzip -q $TEMPDIR/$SQLITE.zip - mv $NEON neon - mv $ZLIB zlib - mv $SERF serf - mv $SQLITE sqlite-amalgamation - if [ -n "$WIN32_APR_VIA_HTTPD" ]; then - unzip -q $TEMPDIR/$HTTPD-win32-src$HTTPD_OOPS.zip - for i in apr apr-util apr-iconv; do - mv $HTTPD/srclib/$i . - done - rm -rf $HTTPD - else - unzip -q $TEMPDIR/$APR-win32-src.zip - unzip -q $TEMPDIR/$APR_UTIL-win32-src.zip - unzip -q $TEMPDIR/$APR_ICONV-win32-src$APR_ICONV_OOPS.zip - mv $APR apr - mv $APR_UTIL apr-util - mv $APR_ICONV apr-iconv - fi - - cd $BASEDIR - mv unix-dependencies subversion-$SVN_VERSION - tar jcf subversion-deps-$SVN_VERSION.tar.bz2 subversion-$SVN_VERSION - tar zcf subversion-deps-$SVN_VERSION.tar.gz subversion-$SVN_VERSION - rm -rf subversion-$SVN_VERSION - mv win32-dependencies subversion-$SVN_VERSION - zip -qr subversion-deps-$SVN_VERSION.zip subversion-$SVN_VERSION - rm -rf subversion-$SVN_VERSION -} - -if [ -z "$1" ]; then - echo "Please provide a Subversion release number." - echo "Example: ./`basename $0` 1.6.19" - exit 1 -fi - -create_deps "$1" diff --git a/tools/dist/nightly.sh b/tools/dist/nightly.sh index 0f2f991..b167ab3 100755 --- a/tools/dist/nightly.sh +++ b/tools/dist/nightly.sh @@ -54,7 +54,7 @@ head=`$svn info $repo/trunk | grep '^Revision' | cut -d ' ' -f 2` # Get the latest versions of the rolling scripts for i in release.py dist.sh -do +do $svn export --force -r $head $repo/trunk/tools/dist/$i@$head $dir/$i done # We also need ezt @@ -63,11 +63,11 @@ $svn export --force -r $head $repo/trunk/build/generator/ezt.py@$head $dir/ezt.p # Create the environment cd roll echo '----------------building environment------------------' -../release.py --base-dir ${abscwd}/roll build-env trunk-nightly +../release.py --verbose --base-dir ${abscwd}/roll build-env trunk-nightly # Roll the tarballs echo '-------------------rolling tarball--------------------' -../release.py --base-dir ${abscwd}/roll roll --branch trunk trunk-nightly $head +../release.py --verbose --base-dir ${abscwd}/roll roll --branch trunk trunk-nightly $head cd .. # Create the information page diff --git a/tools/dist/nominate.pl b/tools/dist/nominate.pl new file mode 120000 index 0000000..411377e --- /dev/null +++ b/tools/dist/nominate.pl @@ -0,0 +1 @@ +backport.pl
\ No newline at end of file diff --git a/tools/dist/release.py b/tools/dist/release.py index bc80549..30a1f0b 100755 --- a/tools/dist/release.py +++ b/tools/dist/release.py @@ -66,16 +66,42 @@ except ImportError: import ezt +try: + subprocess.check_output +except AttributeError: + def check_output(cmd): + proc = subprocess.Popen(['svn', 'list', dist_dev_url], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout, stderr) = proc.communicate() + rc = proc.wait() + if rc or stderr: + logging.error('%r failed with stderr %r', cmd, stderr) + raise subprocess.CalledProcessError(rc, cmd) + return stdout + subprocess.check_output = check_output + del check_output + # Our required / recommended release tool versions by release branch tool_versions = { 'trunk' : { - 'autoconf' : '2.68', - 'libtool' : '2.4', - 'swig' : '2.0.4', + 'autoconf' : '2.69', + 'libtool' : '2.4.3', + 'swig' : '3.0.0', + }, + '1.9' : { + 'autoconf' : '2.69', + 'libtool' : '2.4.3', + 'swig' : '3.0.0' + }, + '1.8' : { + 'autoconf' : '2.69', + 'libtool' : '2.4.3', + 'swig' : '2.0.9', }, '1.7' : { 'autoconf' : '2.68', - 'libtool' : '2.4', + 'libtool' : '2.4.3', 'swig' : '2.0.4', }, '1.6' : { @@ -85,6 +111,9 @@ tool_versions = { }, } +# The version that is our current recommended release +recommended_release = '1.8' + # Some constants repos = 'http://svn.apache.org/repos/asf/subversion' secure_repos = 'https://svn.apache.org/repos/asf/subversion' @@ -99,7 +128,7 @@ extns = ['zip', 'tar.gz', 'tar.bz2'] # Utility functions class Version(object): - regex = re.compile('(\d+).(\d+).(\d+)(?:-(?:(rc|alpha|beta)(\d+)))?') + regex = re.compile(r'(\d+).(\d+).(\d+)(?:-(?:(rc|alpha|beta)(\d+)))?') def __init__(self, ver_str): # Special case the 'trunk-nightly' version @@ -135,6 +164,18 @@ class Version(object): def is_prerelease(self): return self.pre != None + def is_recommended(self): + return self.branch == recommended_release + + def get_download_anchor(self): + if self.is_prerelease(): + return 'pre-releases' + else: + if self.is_recommended(): + return 'recommended-release' + else: + return 'supported-releases' + def __lt__(self, that): if self.major < that.major: return True if self.major > that.major: return False @@ -155,7 +196,7 @@ class Version(object): else: return self.pre_num < that.pre_num - def __str(self): + def __str__(self): if self.pre: if self.pre == 'nightly': return 'nightly' @@ -168,11 +209,7 @@ class Version(object): def __repr__(self): - return "Version('%s')" % self.__str() - - def __str__(self): - return self.__str() - + return "Version(%s)" % repr(str(self)) def get_prefix(base_dir): return os.path.join(base_dir, 'prefix') @@ -183,6 +220,13 @@ def get_tempdir(base_dir): def get_deploydir(base_dir): return os.path.join(base_dir, 'deploy') +def get_target(args): + "Return the location of the artifacts" + if args.target: + return args.target + else: + return get_deploydir(args.base_dir) + def get_tmpldir(): return os.path.join(os.path.abspath(sys.path[0]), 'templates') @@ -194,8 +238,7 @@ def get_tmplfile(filename): return urllib2.urlopen(repos + '/trunk/tools/dist/templates/' + filename) def get_nullfile(): - # This is certainly not cross platform - return open('/dev/null', 'w') + return open(os.path.devnull, 'w') def run_script(verbose, script): if verbose: @@ -371,12 +414,7 @@ def compare_changes(repos, branch, revision): mergeinfo_cmd = ['svn', 'mergeinfo', '--show-revs=eligible', repos + '/trunk/CHANGES', repos + '/' + branch + '/' + 'CHANGES'] - proc = subprocess.Popen(mergeinfo_cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - rc = proc.wait() - if stderr: - raise RuntimeError('svn mergeinfo failed: %s' % stderr) + stdout = subprocess.check_output(mergeinfo_cmd) if stdout: # Treat this as a warning since we are now putting entries for future # minor releases in CHANGES on trunk. @@ -463,15 +501,11 @@ def sign_candidates(args): def sign_file(filename): asc_file = open(filename + '.asc', 'a') logging.info("Signing %s" % filename) - proc = subprocess.Popen(['gpg', '-ba', '-o', '-', filename], - stdout=asc_file) - proc.wait() + proc = subprocess.check_call(['gpg', '-ba', '-o', '-', filename], + stdout=asc_file) asc_file.close() - if args.target: - target = args.target - else: - target = get_deploydir(args.base_dir) + target = get_target(args) for e in extns: filename = os.path.join(target, 'subversion-%s.%s' % (args.version, e)) @@ -488,17 +522,17 @@ def sign_candidates(args): def post_candidates(args): 'Post candidate artifacts to the dist development directory.' + target = get_target(args) + logging.info('Importing tarballs to %s' % dist_dev_url) svn_cmd = ['svn', 'import', '-m', 'Add %s candidate release artifacts' % args.version.base, '--auto-props', '--config-option', 'config:auto-props:*.asc=svn:eol-style=native;svn:mime-type=text/plain', - get_deploydir(args.base_dir), dist_dev_url] + target, dist_dev_url] if (args.username): svn_cmd += ['--username', args.username] - proc = subprocess.Popen(svn_cmd) - (stdout, stderr) = proc.communicate() - proc.wait() + subprocess.check_call(svn_cmd) #---------------------------------------------------------------------- # Create tag @@ -513,6 +547,7 @@ def create_tag(args): else: branch = secure_repos + '/branches/%d.%d.x' % (args.version.major, args.version.minor) + target = get_target(args) tag = secure_repos + '/tags/' + str(args.version) @@ -521,13 +556,63 @@ def create_tag(args): if (args.username): svnmucc_cmd += ['--username', args.username] svnmucc_cmd += ['cp', str(args.revnum), branch, tag] - svnmucc_cmd += ['put', os.path.join(get_deploydir(args.base_dir), - 'svn_version.h.dist'), + svnmucc_cmd += ['put', os.path.join(target, 'svn_version.h.dist' + '-' + + str(args.version)), tag + '/subversion/include/svn_version.h'] # don't redirect stdout/stderr since svnmucc might ask for a password - proc = subprocess.Popen(svnmucc_cmd) - proc.wait() + subprocess.check_call(svnmucc_cmd) + + if not args.version.is_prerelease(): + logging.info('Bumping revisions on the branch') + def replace_in_place(fd, startofline, flat, spare): + """In file object FD, replace FLAT with SPARE in the first line + starting with STARTOFLINE.""" + + fd.seek(0, os.SEEK_SET) + lines = fd.readlines() + for i, line in enumerate(lines): + if line.startswith(startofline): + lines[i] = line.replace(flat, spare) + break + else: + raise RuntimeError('Definition of %r not found' % startofline) + + fd.seek(0, os.SEEK_SET) + fd.writelines(lines) + fd.truncate() # for current callers, new value is never shorter. + + new_version = Version('%d.%d.%d' % + (args.version.major, args.version.minor, + args.version.patch + 1)) + + def file_object_for(relpath): + fd = tempfile.NamedTemporaryFile() + url = branch + '/' + relpath + fd.url = url + subprocess.check_call(['svn', 'cat', '%s@%d' % (url, args.revnum)], + stdout=fd) + return fd + + svn_version_h = file_object_for('subversion/include/svn_version.h') + replace_in_place(svn_version_h, '#define SVN_VER_PATCH ', + str(args.version.patch), str(new_version.patch)) + + STATUS = file_object_for('STATUS') + replace_in_place(STATUS, 'Status of ', + str(args.version), str(new_version)) + + svn_version_h.seek(0, os.SEEK_SET) + STATUS.seek(0, os.SEEK_SET) + subprocess.check_call(['svnmucc', '-r', str(args.revnum), + '-m', 'Post-release housekeeping: ' + 'bump the %s branch to %s.' + % (branch.split('/')[-1], str(new_version)), + 'put', svn_version_h.name, svn_version_h.url, + 'put', STATUS.name, STATUS.url, + ]) + del svn_version_h + del STATUS #---------------------------------------------------------------------- # Clean dist @@ -535,13 +620,7 @@ def create_tag(args): def clean_dist(args): 'Clean the distribution directory of all but the most recent artifacts.' - proc = subprocess.Popen(['svn', 'list', dist_release_url], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - proc.wait() - if stderr: - raise RuntimeError(stderr) + stdout = subprocess.check_output(['svn', 'list', dist_release_url]) filenames = stdout.split('\n') tar_gz_archives = [] @@ -570,8 +649,7 @@ def clean_dist(args): svnmucc_cmd += ['rm', dist_release_url + '/' + filename] # don't redirect stdout/stderr since svnmucc might ask for a password - proc = subprocess.Popen(svnmucc_cmd) - proc.wait() + subprocess.check_call(svnmucc_cmd) #---------------------------------------------------------------------- # Move to dist @@ -579,13 +657,7 @@ def clean_dist(args): def move_to_dist(args): 'Move candidate artifacts to the distribution directory.' - proc = subprocess.Popen(['svn', 'list', dist_dev_url], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - proc.wait() - if stderr: - raise RuntimeError(stderr) + stdout = subprocess.check_output(['svn', 'list', dist_dev_url]) filenames = [] for entry in stdout.split('\n'): @@ -603,8 +675,7 @@ def move_to_dist(args): # don't redirect stdout/stderr since svnmucc might ask for a password logging.info('Moving release artifacts to %s' % dist_release_url) - proc = subprocess.Popen(svnmucc_cmd) - proc.wait() + subprocess.check_call(svnmucc_cmd) #---------------------------------------------------------------------- # Write announcements @@ -613,9 +684,10 @@ def write_news(args): 'Write text for the Subversion website.' data = { 'date' : datetime.date.today().strftime('%Y%m%d'), 'date_pres' : datetime.date.today().strftime('%Y-%m-%d'), - 'major-minor' : '%d.%d' % (args.version.major, args.version.minor), + 'major-minor' : args.version.branch, 'version' : str(args.version), 'version_base' : args.version.base, + 'anchor': args.version.get_download_anchor(), } if args.version.is_prerelease(): @@ -631,10 +703,7 @@ def write_news(args): def get_sha1info(args, replace=False): 'Return a list of sha1 info for the release' - if args.target: - target = args.target - else: - target = get_deploydir(args.base_dir) + target = get_target(args) sha1s = glob.glob(os.path.join(target, 'subversion*-%s*.sha1' % args.version)) @@ -665,9 +734,9 @@ def write_announcement(args): data = { 'version' : str(args.version), 'sha1info' : sha1info, 'siginfo' : siginfo, - 'major-minor' : '%d.%d' % (args.version.major, - args.version.minor), + 'major-minor' : args.version.branch, 'major-minor-patch' : args.version.base, + 'anchor' : args.version.get_download_anchor(), } if args.version.is_prerelease(): @@ -708,10 +777,7 @@ def get_siginfo(args, quiet=False): import _gnupg as gnupg gpg = gnupg.GPG() - if args.target: - target = args.target - else: - target = get_deploydir(args.base_dir) + target = get_target(args) good_sigs = {} fingerprints = {} @@ -842,6 +908,9 @@ def main(): help='''The release label, such as '1.7.0-alpha1'.''') subparser.add_argument('--username', help='''Username for ''' + dist_repos + '''.''') + subparser.add_argument('--target', + help='''The full path to the directory containing + release artifacts.''') # Setup the parser for the create-tag subcommand subparser = subparsers.add_parser('create-tag', @@ -855,6 +924,9 @@ def main(): help='''The branch to base the release on.''') subparser.add_argument('--username', help='''Username for ''' + secure_repos + '''.''') + subparser.add_argument('--target', + help='''The full path to the directory containing + release artifacts.''') # The clean-dist subcommand subparser = subparsers.add_parser('clean-dist', diff --git a/tools/dist/templates/download.ezt b/tools/dist/templates/download.ezt index 601818d..d5fcb54 100644 --- a/tools/dist/templates/download.ezt +++ b/tools/dist/templates/download.ezt @@ -1,4 +1,4 @@ -<p style="font-size: 150%; text-align: center;">Subversion [version]</p> +<p style="font-size: 150%; text-align: center;">Apache Subversion [version]</p> <table class="centered"> <tr> <th>File</th> diff --git a/tools/dist/templates/rc-news.ezt b/tools/dist/templates/rc-news.ezt index 959735c..704899a 100644 --- a/tools/dist/templates/rc-news.ezt +++ b/tools/dist/templates/rc-news.ezt @@ -16,7 +16,7 @@ in the [version_base] release.</p> <p>To get this release from the nearest mirror, please visit our - <a href="/download/#pre-releases">download page</a>.</p> + <a href="/download/#[anchor]">download page</a>.</p> </div> <!-- #news-[date] --> diff --git a/tools/dist/templates/rc-release-ann.ezt b/tools/dist/templates/rc-release-ann.ezt index f9af5c1..b3085f7 100644 --- a/tools/dist/templates/rc-release-ann.ezt +++ b/tools/dist/templates/rc-release-ann.ezt @@ -1,7 +1,7 @@ I'm happy to announce the release of Apache Subversion [version]. Please choose the mirror closest to you by visiting: - http://subversion.apache.org/download/#pre-releases + http://subversion.apache.org/download/#[anchor] The SHA1 checksums are: diff --git a/tools/dist/templates/stable-news.ezt b/tools/dist/templates/stable-news.ezt index aee573f..63ee9da 100644 --- a/tools/dist/templates/stable-news.ezt +++ b/tools/dist/templates/stable-news.ezt @@ -13,7 +13,7 @@ >change log</a> for more information about this release.</p> <p>To get this release from the nearest mirror, please visit our - <a href="/download/#recommended-release">download page</a>.</p> + <a href="/download/#[anchor]">download page</a>.</p> </div> <!-- #news-[date] --> diff --git a/tools/dist/templates/stable-release-ann.ezt b/tools/dist/templates/stable-release-ann.ezt index c865a84..a6ffa9a 100644 --- a/tools/dist/templates/stable-release-ann.ezt +++ b/tools/dist/templates/stable-release-ann.ezt @@ -1,7 +1,7 @@ I'm happy to announce the release of Apache Subversion [version]. Please choose the mirror closest to you by visiting: - http://subversion.apache.org/download/#recommended-release + http://subversion.apache.org/download/#[anchor] The SHA1 checksums are: |