[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: [PATCH] Allow the merge tool to abort the merge

From: John Gardiner Myers <jgmyers_at_proofpoint.com>
Date: Fri, 02 Jan 2009 09:12:49 -0800

Augie Fackler wrote:
>
> Why is it *necessary* for the svn tool to abort to achieve what John
> wants to do? Wouldn't svn st | egrep '^C' give similar information?
Because the merge tool knows that the WC is going to be reverted.
Continuing the merge will just waste time, creating more work for the
revert to do.

For illustrative purposes, here is the current version of my automated
merge script. The part that sends email still needs work and it still
needs some shakedown

#!/tools/x/bin/perl

# Site-specific configuration settings
our $svn = '/tools/x2/current/bin/svn';
our $repos = 'svn://svn/eng';
our @autoresolvefiles = ( 'src/build/Makefile.inc', 'src/build/versions' );

use strict;
use warnings;
use IO::Select;
use Getopt::Long;
use Data::Dump qw(dump);

# Print shell command arguments, applying (some) necessary quoting
sub printargs
{
    foreach (@_) {
    my $arg = $_;
    $arg = "'$arg'" if $arg =~ /\s/;
    print "$arg ";
    }
    print "\n";
}

# Run a command
# Returns a list containing $?, a string with generated stdout, and a
string with generated stderr
sub runprog
{
    my $options = shift;
    my $cmd = shift;
    my @args = @_;

    die "options hash missing" unless ref $options eq 'HASH';
    die "command missing" unless $cmd;

    my $arg0 = $cmd;
    $arg0 =~ s#.*/##;

    my $out = '';
    my $err = '';
    my ($rout, $wout, $rerr, $werr);

    unless ($options->{interactive}) {
    pipe($rout, $wout);
    pipe ($rerr, $werr);
    }

    printargs($arg0, @args);

    my $pid = fork();
    die "Fork failed: $!\n" unless defined $pid;

    if ($pid == 0) {
    chdir $options->{cwd} if $options->{cwd};
    unless ($options->{interactive}) {
        open STDOUT, ">&".fileno($wout);
        open STDERR, ">&".fileno($werr);
        close $rout;
        close $rerr;
    }
    exit exec $cmd $arg0, @args;
    }

    unless ($options->{interactive}) {
    close $wout;
    close $werr;

    my $s = IO::Select->new($rout, $rerr);

    while ($s->count()) {
        foreach my $fh ($s->can_read) {
        if ($fh eq $rout) {
            if (defined($_ = <$rout>)) {
            print $_ unless $options->{quiet};
            $out .= $_;
            } else {
            $s->remove($rout);
            }
        } else {
            if (defined($_ = <$rerr>)) {
            print STDERR $_;
            $err .= $_;
            } else {
            $s->remove($rerr);
            }
        }
        }
    }
    }

    waitpid $pid, 0;
    die "Command failed with status $?\n" if $? && !$options->{failok};

    return ($?, $out, $err);
}

# Returns the first eligible revision to be merged from a source url to
a destination url.
# If nothing to be merged, will return undef.
sub getfirstrev {
    my ($sourceurl, $desturl) = @_;

    my ($status, $out, $err) = runprog({ quiet => 1 }, $svn,
'mergeinfo', '--show-revs', 'eligible', $sourceurl, $desturl);

    return undef unless $out =~ /^r([0-9]+)/;
    return $1;
}

# Returns the last eligible revision (before $end) to be merged from a
source url to a destination url.
# If nothing to be merged, will return undef.
sub getlastrevforconflict {
    my ($sourceurl, $desturl, $end, $conflict) = @_;

    $conflict =~ s#[^/]+/##; # Chop off wc name

    my ($status, $out, $err) = runprog({ quiet => 1 }, $svn,
'mergeinfo', '--show-revs', 'eligible', "$sourceurl/$conflict",
"$desturl/$conflict");

    my $last;
    while ($out =~ /^r([0-9]+)/mg) {
    $last = $1 unless defined($end) && $1 >= $end;
    }
    return $last;
}

# Create the merge tool and return its filename
sub writemergetool {
    my ($project, $source, $dest) = @_;
    my $mergetool = "mergetool-$project-$source-$dest";
    my $conflictfile = "conflict-$project-$source-$dest";
    open my $out, ">", $mergetool or die "Can't write merge tool: $!\n";
    print $out "#!$^X\n";
    print $out 'our @autoresolvefiles = ', dump(map {"/$_"}
@autoresolvefiles), ";\n";
    print $out 'if (grep {$_ eq substr($ARGV[4], -length($_))}
@autoresolvefiles) {', "\n";
    print $out " print 'Taking $dest version of ', \"\$ARGV[4]'\\n\";\n";
    print $out ' open (my $in, "<", $ARGV[2]) or exit 2;', "\n";
    print $out ' open (my $out, "<", $ARGV[3]) or exit 2;', "\n";
    print $out ' while (read $in, my $buf, 8192) {', "\n";
    print $out ' print $out $buf;', "\n";
    print $out " }\n";
    print $out " exit 0;\n";
    print $out "}\n";
    print $out 'open my $fh, ">", ', dump($conflictfile), " or exit 2;\n";
    print $out 'print $fh $ARGV[4];', "\n";
    print $out 'print STDERR "Conflict in $ARGV[4]\n";', "\n";
    print $out "exit 2;\n";
    chmod 0755, $mergetool;
    return ($mergetool, $conflictfile);
}

# Send email requesting manual merging of conflicts
sub sendconflictmail {
    my ($project, $source, $dest, $sourceurl, $desturl, $rev) = @_;

    my ($status, $logmsg, $err) = runprog({ quiet => 1 }, $svn, 'log',
'-v', '-r', $rev, $sourceurl);
   
    die "Can't find author in log message\n" unless $logmsg =~ /^r$rev
\| (\S+) \| /m;
    my $author = $1;

    print << "EOM"

To: $author
Subject: Merge r$rev $project $source -> $dest

Your assistance is required for merging revision $rev you made for
$project from
$source to $dest. The log message for this commit is as follows:

$logmsg

If no merge is required (the $dest version of these files are correct),
please
exclude that revision from merging by running the following commands:

    % svn co -q --force $desturl merge-$project-$dest
    % svn revert -q -R merge-$project-$dest
    % svn merge --record-only -c $rev $sourceurl merge-$project-$dest
    % svn ci -m "Exclude r$rev from merging" merge-$project-$dest

Then reply to this message stating this has been done.

If a merge is required and the $dest branch is open, please merge the commit
by running the following commands:

    % svn co -q --force $desturl merge-$project-$dest
    % svn revert -q -R merge-$project-$dest
    % svn merge -c $rev $sourceurl merge-$project-$dest
    (resolve conflicts)
    % svn ci -q -m "Merge r$rev from $source" merge-$project-$dest

Then reply to this message stating this has been done.

If a merge is required and the $dest branch is locked down, please merge the
commit into a feature branch by running the following commands:
   
    % svn mkdir --parents $repos/$project/working/$author -m "Create
working directory"
    % svn cp $desturl
$repos/$project/working/$author/merge-$project-$dest-$rev -m "Create
feature branch for merge"
    % svn co -q $repos/$project/working/$author/merge-$project-$dest-$rev
    % svn merge -c $rev $sourceurl merge-$project-$dest-$rev
    (resolve conflicts)
    % svn ci -q -m "Merge r$rev from $source" merge-$project-$dest-$rev

Then reply to this message asking for these changes to be reintegrated.
Please
include the following information needed to reintegrate the changes:

    % svn co -q $repos/$project/working/$author/merge-$project-$dest-$rev
    % svn merge $desturl merge-$project-$dest-$rev
    % svn ci -q -m "Merge changes from $project $dest"
merge-$project-$dest-$rev
    % svn co -q --force $desturl merge-$project-$dest
    % svn revert -q -R merge-$project-$dest
    % svn merge --reintegrate
$repos/$project/working/$author/merge-$project-$dest-$rev
merge-$project-$dest
    % svn ci -q -m "CM merged r$rev $source -> $dest conflicts resolved
by $author" merge-$project-$dest
    % svn rm $repos/$project/working/$author/merge-$project-$dest-$rev
-m "Feature branch has been reintegated"
    % rm -rf merge-$project-$dest-$rev

EOM
;

}

# Do a merge attempt
sub domerge {
    my ($project, $source, $dest, $end) = @_;
    my $loop = 0;

    my $sourceurl = "$repos/";
    $sourceurl .= "$project/" unless $project eq '';
    $sourceurl .= "branches/" unless $source eq 'trunk';
    $sourceurl .= $source;

    my $desturl = "$repos/";
    $desturl .= "$project/" unless $project eq '';
    $desturl .= "branches/" unless $dest eq 'trunk';
    $desturl .= $dest;

    my ($mergetool, $conflictfile) = writemergetool($project, $source,
$dest);
    $ENV{SVN_MERGE} = "./$mergetool";

  again:

    my $firstrev = getfirstrev($sourceurl, $desturl);
    unless ($firstrev) {
    print "Nothing to merge\n";
    return 0;
    }

    if ($end && $firstrev == $end) {
    sendconflictmail($project, $source, $dest, $sourceurl, $desturl, $end);
    return 0;
    }

    if ($end && $firstrev > $end) {
    die "First eligible revision $firstrev is later than end revision
$end.\n";
    }

    runprog({ quiet => 1 }, $svn, 'co', '-q', '--force', $desturl,
"merge-$project-$dest");

  adjust_end:
    runprog({ quiet => 1 }, $svn, 'revert', '-q', '-R',
"merge-$project-$dest");

    my @restrictargs;
    my $logargs = $firstrev.':';
    if ($end) {
    push @restrictargs, '-r', ($firstrev-1).':'.($end-1);
    $logargs .= ($end-1);
    } else {
    $logargs .= 'HEAD';
    }

    print "To view log: ";
    printargs('svn', 'log', '-r', $firstrev.':'.($end ? $end - 1 :
'HEAD'), $sourceurl);

    unlink($conflictfile);
    my ($status) = runprog({ failok => 1 }, $svn, 'merge', '--accept',
'launch', @restrictargs, $sourceurl, "merge-$project-$dest");

    if ($status) {
    die "Merge failed with status $status\n" unless open my $ch, "<",
$conflictfile;
    my $conflict = <$ch>;
    $end = getlastrevforconflict($sourceurl, $desturl, $end, $conflict);
    die "Cannot find conflicting revision for $conflict" unless $end;
    goto adjust_end;
    }
   
    runprog({ }, $svn, 'ci', '-q', '-m', "CM merged $source -> $dest",
"merge-$project-$dest");

    goto again unless !$end || $loop++;

    return 0;
}

my $end;

sub usage {
    print "Usage: $0 [options] project source dest\n";
    print " -end REV Up to but not including source revision REV\n";
    exit 0;
}

GetOptions(
    'e|end=i' => \$end
    ) or usage();

usage() unless scalar @ARGV == 3;
my $project = shift @ARGV;
my $source = shift @ARGV;
my $dest = shift @ARGV;

exit domerge($project, $source, $dest, $end);

------------------------------------------------------
http://subversion.tigris.org/ds/viewMessage.do?dsForumId=462&dsMessageId=999931
Received on 2009-01-02 18:17:45 CET

This is an archived mail posted to the Subversion Dev mailing list.