You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by pr...@apache.org on 2013/07/09 10:48:47 UTC
svn commit: r1501136 [10/10] - in /subversion/branches/verify-keep-going: ./
build/ build/ac-macros/ build/generator/ subversion/bindings/javahl/native/
subversion/bindings/javahl/src/org/apache/subversion/javahl/
subversion/bindings/javahl/src/org/apa...
Modified: subversion/branches/verify-keep-going/tools/dist/backport.pl
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/tools/dist/backport.pl?rev=1501136&r1=1501135&r2=1501136&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/tools/dist/backport.pl (original)
+++ subversion/branches/verify-keep-going/tools/dist/backport.pl Tue Jul 9 08:48:43 2013
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -l
+#!/usr/bin/perl
use warnings;
use strict;
use feature qw/switch say/;
@@ -20,65 +20,142 @@ use feature qw/switch say/;
# specific language governing permissions and limitations
# under the License.
+use Digest ();
use Term::ReadKey qw/ReadMode ReadKey/;
+use File::Copy qw/copy move/;
use File::Temp qw/tempfile/;
use POSIX qw/ctermid/;
+############### Start of reading values from environment ###############
+
+# Programs we use.
my $SVN = $ENV{SVN} || 'svn'; # passed unquoted to sh
+my $SHELL = $ENV{SHELL} // '/bin/sh';
my $VIM = 'vim';
-my $STATUS = './STATUS';
-my $BRANCHES = '^/subversion/branches';
+my $EDITOR = $ENV{SVN_EDITOR} // $ENV{VISUAL} // $ENV{EDITOR} // 'ed';
+my $PAGER = $ENV{PAGER} // 'less -F' // 'cat';
-my $YES = $ENV{YES}; # batch mode: eliminate prompts, add sleeps
-my $MAY_COMMIT = qw[false true][0];
-my $DEBUG = qw[false true][0]; # 'set -x', etc
-$DEBUG = 'true' if exists $ENV{DEBUG};
+# Mode flags.
+# svn-role: YES=1 MAY_COMMIT=1
+# conflicts-bot: YES=1 MAY_COMMIT=0
+# interactive: YES=0 MAY_COMMIT=0 (default)
+my $YES = ($ENV{YES} // 0) ? 1 : 0; # batch mode: eliminate prompts, add sleeps
+my $MAY_COMMIT = 'false';
$MAY_COMMIT = 'true' if ($ENV{MAY_COMMIT} // "false") =~ /^(1|yes|true)$/i;
-# derived values
+# Other knobs.
+my $VERBOSE = 0;
+my $DEBUG = (exists $ENV{DEBUG}) ? 'true' : 'false'; # 'set -x', etc
+
+# Username for entering votes.
+my ($AVAILID) = $ENV{AVAILID} // do {
+ my $SVN_A_O_REALM = 'd3c8a345b14f6a1b42251aef8027ab57';
+ open USERNAME, '<', "$ENV{HOME}/.subversion/auth/svn.simple/$SVN_A_O_REALM";
+ 1 until <USERNAME> eq "username\n";
+ <USERNAME>;
+ local $_ = <USERNAME>;
+ chomp;
+ $_
+}
+// warn "Username for commits (of votes/merges) not found";
+
+############## End of reading values from the environment ##############
+
+# Constants.
+my $STATUS = './STATUS';
+my $STATEFILE = './.backports1';
+my $BRANCHES = '^/subversion/branches';
+
+# Globals.
+my %ERRORS = ();
+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';
+
sub usage {
my $basename = $0;
$basename =~ s#.*/##;
print <<EOF;
-Run this from the root of your release branch (e.g., 1.6.x) working copy. Use
-a working copy 'svn revert -R .' can be run on at any time, as this script
-will run revert prior to every merge.
-
-For each entry in STATUS, you will be prompted whether to merge it. The
-merge will not be committed.
+backport.pl: a tool for reviewing and merging STATUS entries. Run this with
+CWD being the root of the stable branch (e.g., 1.8.x). The ./STATUS file
+should be at HEAD.
+
+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.
+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.
+
+There is also a batch mode: when \$YES and \$MAY_COMMIT are defined to '1' i
+the environment, this script will iterate the "Approved:" section, and merge
+and commit each entry therein. If only \$YES is defined, the script will
+merge every nomination (including unapproved and vetoed ones), and complain
+to stderr if it notices any conflicts. These mode are normally used by the
+'svn-role' cron job and/or buildbot, not by human users.
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 digest_string {
+ Digest->new("MD5")->add(@_)->hexdigest
+}
+
sub prompt {
- local $\; # disable 'perl -l' effects
- print "$_[0] "; shift;
+ print $_[0]; shift;
my %args = @_;
+ my $getchar = sub {
+ ReadMode 'cbreak';
+ my $answer = (ReadKey 0);
+ ReadMode 'normal';
+ print $answer;
+ return $answer;
+ };
die "$0: called prompt() in non-interactive mode!" if $YES;
- ReadMode 'cbreak';
- my $answer = (ReadKey 0);
- ReadMode 'restore';
- print $answer, "\n";
+ 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;
}
+
sub merge {
my %entry = @_;
+ $MERGED_SOMETHING++;
my ($logmsg_fh, $logmsg_filename) = tempfile();
my ($mergeargs, $pattern);
@@ -90,26 +167,26 @@ sub merge {
$pattern = sprintf '\V\(%s branch(es)?\|branches\/%s\|Branch\(es\)\?: \*\n\? \*%s\)', $entry{branch}, $entry{branch}, $entry{branch};
if ($SVNvsn >= 1_008_000) {
$mergeargs = "$BRANCHES/$entry{branch}";
- print $logmsg_fh "Merge the $entry{header}:";
+ say $logmsg_fh "Merge the $entry{header}:";
} else {
$mergeargs = "--reintegrate $BRANCHES/$entry{branch}";
- print $logmsg_fh "Reintegrate the $entry{header}:";
+ say $logmsg_fh "Reintegrate the $entry{header}:";
}
- print $logmsg_fh "";
+ say $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 "";
+ say $logmsg_fh "Merge the $entry{header} from trunk:";
+ say $logmsg_fh "";
} else {
- print $logmsg_fh "Merge r$entry{revisions}->[0] from trunk:";
- print $logmsg_fh "";
+ say $logmsg_fh "Merge r$entry{revisions}->[0] from trunk:";
+ 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";
@@ -120,9 +197,11 @@ if $DEBUG; then
set -x
fi
$SVN diff > $backupfile
-cp STATUS STATUS.$$
+if ! $MAY_COMMIT ; then
+ cp STATUS STATUS.$$
+fi
$SVNq revert -R .
-if $MAY_COMMIT ; then
+if ! $MAY_COMMIT ; then
mv STATUS.$$ STATUS
fi
$SVNq up
@@ -140,7 +219,7 @@ fi
if $MAY_COMMIT; then
$VIM -e -s -n -N -i NONE -u NONE -c '/$pattern/normal! dap' -c wq $STATUS
$SVNq commit -F $logmsg_filename
-else
+elif test 1 -ne $YES; then
echo "Would have committed:"
echo '[[['
$SVN status -q
@@ -157,16 +236,22 @@ if $MAY_COMMIT; then
if [ -n "\$YES" ]; then sleep 15; fi
$SVNq rm $BRANCHES/$entry{branch} -m "Remove the '$entry{branch}' branch, $reintegrated_word in r\$reinteg_rev."
if [ -n "\$YES" ]; then sleep 1; fi
-else
- echo "Removing $reintegrated_word '$entry{branch}' branch"
+elif test 1 -ne $YES; then
+ echo "Would remove $reintegrated_word '$entry{branch}' branch"
fi
EOF
open SHELL, '|-', qw#/bin/sh# or die "$! (in '$entry{header}')";
print SHELL $script;
close SHELL or warn "$0: sh($?): $! (in '$entry{header}')";
+ $ERRORS{$entry{id}} = "sh($?): $!" if $?;
+
+ if (-z $backupfile) {
+ unlink $backupfile;
+ } else {
+ warn "Local mods saved to '$backupfile'\n";
+ }
- unlink $backupfile if -z $backupfile;
unlink $logmsg_filename unless $? or $!;
}
@@ -180,7 +265,9 @@ sub sanitize_branch {
# TODO: may need to parse other headers too?
sub parse_entry {
+ my $raw = shift;
my @lines = @_;
+ my $depends;
my (@revisions, @logsummary, $branch, @votes);
# @lines = @_;
@@ -192,19 +279,27 @@ sub parse_entry {
$branch = sanitize_branch $1
if $_[0] =~ /^(\S*) branch$/ or $_[0] =~ m#branches/(\S+)#;
while ($_[0] =~ /^r/) {
+ my $sawrevnum = 0;
while ($_[0] =~ s/^r(\d+)(?:$|[,; ]+)//) {
push @revisions, $1;
+ $sawrevnum++;
}
- shift;
+ $sawrevnum ? shift : last;
}
# 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;
+ # depends
+ # TODO: parse the value of this.
+ $depends = grep /^Depends:/, @_;
+
# branch
while (@_) {
shift and next unless $_[0] =~ s/^\s*Branch(es)?:\s*//;
@@ -212,9 +307,11 @@ sub parse_entry {
}
# Compute a header.
- my $header;
+ my ($header, $id);
$header = "r$revisions[0] group" if @revisions;
- $header = "$branch branch" if $branch;
+ $id = "r$revisions[0]" if @revisions;
+ $header = "$branch branch" if $branch;
+ $id = $branch if $branch;
warn "No header for [@lines]" unless $header;
return (
@@ -222,49 +319,310 @@ sub parse_entry {
logsummary => [@logsummary],
branch => $branch,
header => $header,
+ depends => $depends,
+ id => $id,
votes => [@votes],
entry => [@lines],
+ raw => $raw,
+ digest => digest_string($raw),
);
}
+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) = 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) = @_;
+ my (%approvedcheck, %votescheck);
+ my $raw_approved = "";
+ my @votes;
+ return unless %$approved or %$votes;
+
+ my $had_empty_line;
+
+ $. = 0;
+ open STATUS, "<", $STATUS;
+ open VOTES, ">", "$STATUS.$$.tmp";
+ while (<STATUS>) {
+ $had_empty_line = /\n\n\z/;
+ my $key = digest_string $_;
+
+ $approvedcheck{$key}++ if exists $approved->{$key};
+ $votescheck{$key}++ if exists $votes->{$key};
+
+ unless (exists $votes->{$key}) {
+ (exists $approved->{$key}) ? ($raw_approved .= $_) : (print VOTES);
+ next;
+ }
+
+ my ($vote, $entry) = @{$votes->{$key}};
+ push @votes, [$vote, $entry, undef]; # ->[2] later set to $digest
+
+ if ($vote eq 'edit') {
+ local $_ = $entry->{raw};
+ (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';
+ $votes[$#votes]->[2] = digest_string $_;
+ (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;
+ die "Some vote chunks weren't found: ",
+ map $votes->{$_}->[1]->{id},
+ grep { !$votescheck{$_} } keys %$votes
+ if scalar(keys %$votes) != scalar(keys %votescheck);
+ die "Some approval chunks weren't found: ",
+ map $approved->{$_}->{id},
+ grep { !$approvedcheck{$_} } keys %$approved
+ if scalar(keys %$approved) != scalar(keys %approvedcheck);
+ move "$STATUS.$$.tmp", $STATUS;
+
+ my $logmsg = do {
+ my %allkeys = map { $_ => 1 } keys(%$votes), keys(%$approved);
+ my @sentences = map {
+ exists $votes->{$_}
+ ? (
+ ( $votes->{$_}->[0] eq 'edit'
+ ? "Edit the $votes->{$_}->[1]->{id} entry"
+ : "Vote $votes->{$_}->[0] on the $votes->{$_}->[1]->{header}"
+ )
+ . (exists $approved->{$_} ? ", approving" : "")
+ . "."
+ )
+ : # exists only in $approved
+ "Approve the $approved->{$_}->{header}."
+ } keys %allkeys;
+ (@sentences == 1)
+ ? $sentences[0]
+ : "* STATUS:\n" . join "", map " $_\n", @sentences;
+ };
+
+ system "$SVN diff -- $STATUS";
+ say "Voting '$_->[0]' on $_->[1]->{id}." for @votes;
+ # say $logmsg;
+ if (prompt "Commit these votes? ") {
+ my ($logmsg_fh, $logmsg_filename) = tempfile();
+ print $logmsg_fh $logmsg;
+ close $logmsg_fh;
+ warn "Tempfile name '$logmsg_filename' not shell-safe; "
+ ."refraining from commit.\n"
+ unless $logmsg_filename =~ /^([A-Z0-9._-]|\x2f)+$/i;
+ system("$SVN commit -F $logmsg_filename -- $STATUS") == 0
+ or warn("Committing the votes failed($?): $!") and return;
+ unlink $logmsg_filename;
+
+ $state->{$approved->{$_}->{digest}}++ for keys %$approved;
+ $state->{$_->[2]}++ for @votes;
+ }
+}
+
+sub revert {
+ copy $STATUS, "$STATUS.$$.tmp";
+ system "$SVN revert -q $STATUS";
+ system "$SVN revert -R ./" . ($YES && $MAY_COMMIT ne 'true'
+ ? " -q" : "");
+ move "$STATUS.$$.tmp", $STATUS;
+}
+
+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 if !$YES and $MERGED_SOMETHING and prompt 'Revert? ';
+ (@_ ? exit : return);
+}
+
+sub warning_summary {
+ return unless %ERRORS;
+
+ warn "Warning summary\n";
+ warn "===============\n";
+ warn "\n";
+ for my $header (keys %ERRORS) {
+ warn "$header: $ERRORS{$header}\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;
+}
+
sub handle_entry {
my $in_approved = shift;
- my %entry = parse_entry @_;
+ my $approved = shift;
+ my $votes = shift;
+ my $state = shift;
+ my $raw = shift;
+ my %entry = parse_entry $raw, @_;
my @vetoes = grep { /^ -1:/ } @{$entry{votes}};
if ($YES) {
- merge %entry if $in_approved and not @vetoes;
+ # Run a merge if:
+ unless (@vetoes) {
+ if ($MAY_COMMIT eq 'true' and $in_approved) {
+ # svn-role mode
+ merge %entry;
+ } elsif ($MAY_COMMIT ne 'true') {
+ # Scan-for-conflicts mode
+ merge %entry;
+
+ my $output = `$SVN status`;
+ my (@conflicts) = ($output =~ m#^(?:C|.C|...C).*/(.*)#mg);
+ if (@conflicts and !$entry{depends}) {
+ $ERRORS{$entry{id}} //= "Conflicts merging the $entry{header}: "
+ . (join ', ', @conflicts);
+ say STDERR "Conflicts merging the $entry{header}!";
+ say STDERR "";
+ say STDERR $output;
+ } elsif (!@conflicts and $entry{depends}) {
+ warn "No conflicts merging the $entry{header}, but conflicts were "
+ ."expected ('Depends:' header set)\n";
+ } elsif (@conflicts) {
+ say "Conflicts found merging $entry{id}, as expected.";
+ }
+ revert;
+ }
+ }
+ } elsif ($state->{$entry{digest}}) {
+ print "\n\n";
+ say "Skipping the $entry{header} (remove $STATEFILE to reset):";
+ say $entry{logsummary}->[0], ('[...]' x (0 < $#{$entry{logsummary}}));
} 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;
-
- if (prompt 'Go ahead?') {
- merge %entry;
- MAYBE_DIFF: while (1) {
- given (prompt "Shall I open a subshell? [ydN]", verbose => 1) {
- when (/^y/i) {
- system($ENV{SHELL} // "/bin/sh") == 0
- or warn "Creating an interactive subshell failed ($?): $!"
- }
- when (/^d/) {
- system($SVN, 'diff') == 0
- or warn "diff failed ($?): $!";
- next;
+ # 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>>> The $entry{header}:";
+ say join ", ", map { "r$_" } @{$entry{revisions}} if @{$entry{revisions}};
+ say "$BRANCHES/$entry{branch}" if $entry{branch};
+ 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,±1,±0,q,e,a, ,N] ',
+ verbose => 1, extra => qr/[+-]/) {
+ when (/^y/i) {
+ merge %entry;
+ while (1) {
+ given (prompt "Shall I open a subshell? [ydN] ", verbose => 1) {
+ when (/^y/i) {
+ system($SHELL) == 0
+ or warn "Creating an interactive subshell failed ($?): $!"
+ }
+ when (/^d/) {
+ system("$SVN diff | $PAGER") == 0
+ or warn "diff failed ($?): $!";
+ next;
+ }
}
+ revert;
+ next PROMPT;
+ }
+ # NOTREACHED
+ }
+ when (/^l/i) {
+ if ($entry{branch}) {
+ system "$SVN log --stop-on-copy -v -r 0:HEAD -- "
+ ."$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), ']]]';
}
- last;
+ next PROMPT;
+ }
+ when (/^q/i) {
+ exit_stage_left $state, $approved, $votes;
+ }
+ when (/^a/i) {
+ $approved->{$key} = \%entry;
+ next PROMPT;
+ }
+ when (/^([+-][01])\s*$/i) {
+ $votes->{$key} = [$1, \%entry];
+ say "Your '$1' vote has been recorded." if $VERBOSE;
+ }
+ when (/^e/i) {
+ my $original = $entry{raw};
+ $entry{raw} = edit_string $entry{raw}, $entry{header},
+ trailing_eol => 2;
+ $votes->{$key} = ['edit', \%entry] # marker for the 2nd pass
+ if $original ne $entry{raw};
+ }
+ when (/^N/i) {
+ $state->{$entry{digest}}++;
+ }
+ 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;
}
- # Don't revert. The next merge() call will do that anyway, or maybe the
- # user did in his interactive shell.
}
+ last; } # QUESTION
+ last; } # PROMPT
}
# TODO: merge() changes ./STATUS, which we're reading below, but
@@ -273,35 +631,51 @@ sub handle_entry {
1;
}
+
sub main {
+ my %approved;
+ my %votes;
+ my $state = read_state;
+
usage, exit 0 if @ARGV;
open STATUS, "<", $STATUS or (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 \$MAY_COMMIT mode"
+ if $MAY_COMMIT eq 'true' and `${VIM} --version` !~ /[+]ex_extra/;
# ### TODO: need to run 'revert' here
# ### TODO: both here and in merge(), unlink files that previous merges added
# When running from cron, there shouldn't be local mods. (For interactive
# usage, we preserve local mods to STATUS.)
- die "Local mods to STATUS file $STATUS" if $YES and `$SVN status -q $STATUS`;
+ system("$SVN info $STATUS >/dev/null") == 0
+ or die "$0: svn error; point \$SVN to an appropriate binary";
+
+ 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;
+ }
# Skip most of the file
+ $/ = ""; # paragraph mode
while (<STATUS>) {
last if /^Status of \d+\.\d+/;
}
- $/ = ""; # paragraph mode
+
+ $SIG{INT} = \&maybe_revert 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
@@ -316,15 +690,15 @@ sub main {
when (/^ \*/) {
warn "Too many bullets in $lines[0]" and next
if grep /^ \*/, @lines[1..$#lines];
- handle_entry $in_approved, @lines;
+ handle_entry $in_approved, \%approved, \%votes, $state, $lines, @lines;
}
default {
- warn "Unknown entry '$lines[0]' at line $.\n";
+ warn "Unknown entry '$lines[0]'";
}
}
}
- system $SVN, qw/revert -R ./ if !$YES and prompt 'Revert? ';
+ exit_stage_left $state, \%approved, \%votes;
}
&main