
1 changed files with 0 additions and 284 deletions
@ -1,284 +0,0 @@ |
|||||||
#!/usr/bin/perl |
|
||||||
# |
|
||||||
# REuse REcorded REsolve. This tool records a conflicted automerge |
|
||||||
# result and its hand resolution, and helps to resolve future |
|
||||||
# automerge that results in the same conflict. |
|
||||||
# |
|
||||||
# To enable this feature, create a directory 'rr-cache' under your |
|
||||||
# .git/ directory. |
|
||||||
|
|
||||||
use Digest; |
|
||||||
use File::Path; |
|
||||||
use File::Copy; |
|
||||||
|
|
||||||
my $git_dir = $::ENV{GIT_DIR} || ".git"; |
|
||||||
my $rr_dir = "$git_dir/rr-cache"; |
|
||||||
my $merge_rr = "$git_dir/rr-cache/MERGE_RR"; |
|
||||||
|
|
||||||
my %merge_rr = (); |
|
||||||
|
|
||||||
sub read_rr { |
|
||||||
if (!-f $merge_rr) { |
|
||||||
%merge_rr = (); |
|
||||||
return; |
|
||||||
} |
|
||||||
my $in; |
|
||||||
local $/ = "\0"; |
|
||||||
open $in, "<$merge_rr" or die "$!: $merge_rr"; |
|
||||||
while (<$in>) { |
|
||||||
chomp; |
|
||||||
my ($name, $path) = /^([0-9a-f]{40})\t(.*)$/s; |
|
||||||
$merge_rr{$path} = $name; |
|
||||||
} |
|
||||||
close $in; |
|
||||||
} |
|
||||||
|
|
||||||
sub write_rr { |
|
||||||
my $out; |
|
||||||
open $out, ">$merge_rr" or die "$!: $merge_rr"; |
|
||||||
for my $path (sort keys %merge_rr) { |
|
||||||
my $name = $merge_rr{$path}; |
|
||||||
print $out "$name\t$path\0"; |
|
||||||
} |
|
||||||
close $out; |
|
||||||
} |
|
||||||
|
|
||||||
sub compute_conflict_name { |
|
||||||
my ($path) = @_; |
|
||||||
my @side = (); |
|
||||||
my $in; |
|
||||||
open $in, "<$path" or die "$!: $path"; |
|
||||||
|
|
||||||
my $sha1 = Digest->new("SHA-1"); |
|
||||||
my $hunk = 0; |
|
||||||
while (<$in>) { |
|
||||||
if (/^<<<<<<< .*/) { |
|
||||||
$hunk++; |
|
||||||
@side = ([], undef); |
|
||||||
} |
|
||||||
elsif (/^=======$/) { |
|
||||||
$side[1] = []; |
|
||||||
} |
|
||||||
elsif (/^>>>>>>> .*/) { |
|
||||||
my ($one, $two); |
|
||||||
$one = join('', @{$side[0]}); |
|
||||||
$two = join('', @{$side[1]}); |
|
||||||
if ($two le $one) { |
|
||||||
($one, $two) = ($two, $one); |
|
||||||
} |
|
||||||
$sha1->add($one); |
|
||||||
$sha1->add("\0"); |
|
||||||
$sha1->add($two); |
|
||||||
$sha1->add("\0"); |
|
||||||
@side = (); |
|
||||||
} |
|
||||||
elsif (@side == 0) { |
|
||||||
next; |
|
||||||
} |
|
||||||
elsif (defined $side[1]) { |
|
||||||
push @{$side[1]}, $_; |
|
||||||
} |
|
||||||
else { |
|
||||||
push @{$side[0]}, $_; |
|
||||||
} |
|
||||||
} |
|
||||||
close $in; |
|
||||||
return ($sha1->hexdigest, $hunk); |
|
||||||
} |
|
||||||
|
|
||||||
sub record_preimage { |
|
||||||
my ($path, $name) = @_; |
|
||||||
my @side = (); |
|
||||||
my ($in, $out); |
|
||||||
open $in, "<$path" or die "$!: $path"; |
|
||||||
open $out, ">$name" or die "$!: $name"; |
|
||||||
|
|
||||||
while (<$in>) { |
|
||||||
if (/^<<<<<<< .*/) { |
|
||||||
@side = ([], undef); |
|
||||||
} |
|
||||||
elsif (/^=======$/) { |
|
||||||
$side[1] = []; |
|
||||||
} |
|
||||||
elsif (/^>>>>>>> .*/) { |
|
||||||
my ($one, $two); |
|
||||||
$one = join('', @{$side[0]}); |
|
||||||
$two = join('', @{$side[1]}); |
|
||||||
if ($two le $one) { |
|
||||||
($one, $two) = ($two, $one); |
|
||||||
} |
|
||||||
print $out "<<<<<<<\n"; |
|
||||||
print $out $one; |
|
||||||
print $out "=======\n"; |
|
||||||
print $out $two; |
|
||||||
print $out ">>>>>>>\n"; |
|
||||||
@side = (); |
|
||||||
} |
|
||||||
elsif (@side == 0) { |
|
||||||
print $out $_; |
|
||||||
} |
|
||||||
elsif (defined $side[1]) { |
|
||||||
push @{$side[1]}, $_; |
|
||||||
} |
|
||||||
else { |
|
||||||
push @{$side[0]}, $_; |
|
||||||
} |
|
||||||
} |
|
||||||
close $out; |
|
||||||
close $in; |
|
||||||
} |
|
||||||
|
|
||||||
sub find_conflict { |
|
||||||
my $in; |
|
||||||
local $/ = "\0"; |
|
||||||
my $pid = open($in, '-|'); |
|
||||||
die "$!" unless defined $pid; |
|
||||||
if (!$pid) { |
|
||||||
exec(qw(git ls-files -z -u)) or die "$!: ls-files"; |
|
||||||
} |
|
||||||
my %path = (); |
|
||||||
my @path = (); |
|
||||||
while (<$in>) { |
|
||||||
chomp; |
|
||||||
my ($mode, $sha1, $stage, $path) = |
|
||||||
/^([0-7]+) ([0-9a-f]{40}) ([123])\t(.*)$/s; |
|
||||||
$path{$path} |= (1 << $stage); |
|
||||||
} |
|
||||||
close $in; |
|
||||||
while (my ($path, $status) = each %path) { |
|
||||||
if ($status == 14) { push @path, $path; } |
|
||||||
} |
|
||||||
return @path; |
|
||||||
} |
|
||||||
|
|
||||||
sub merge { |
|
||||||
my ($name, $path) = @_; |
|
||||||
record_preimage($path, "$rr_dir/$name/thisimage"); |
|
||||||
unless (system('git', 'merge-file', map { "$rr_dir/$name/${_}image" } |
|
||||||
qw(this pre post))) { |
|
||||||
my $in; |
|
||||||
open $in, "<$rr_dir/$name/thisimage" or |
|
||||||
die "$!: $name/thisimage"; |
|
||||||
my $out; |
|
||||||
open $out, ">$path" or die "$!: $path"; |
|
||||||
while (<$in>) { print $out $_; } |
|
||||||
close $in; |
|
||||||
close $out; |
|
||||||
return 1; |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
sub garbage_collect_rerere { |
|
||||||
# We should allow specifying these from the command line and |
|
||||||
# that is why the caller gives @ARGV to us, but I am lazy. |
|
||||||
|
|
||||||
my $cutoff_noresolve = 15; # two weeks |
|
||||||
my $cutoff_resolve = 60; # two months |
|
||||||
my @to_remove; |
|
||||||
while (<$rr_dir/*/preimage>) { |
|
||||||
my ($dir) = /^(.*)\/preimage$/; |
|
||||||
my $cutoff = ((-f "$dir/postimage") |
|
||||||
? $cutoff_resolve |
|
||||||
: $cutoff_noresolve); |
|
||||||
my $age = -M "$_"; |
|
||||||
if ($cutoff <= $age) { |
|
||||||
push @to_remove, $dir; |
|
||||||
} |
|
||||||
} |
|
||||||
if (@to_remove) { |
|
||||||
rmtree(\@to_remove); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
-d "$rr_dir" || exit(0); |
|
||||||
|
|
||||||
read_rr(); |
|
||||||
|
|
||||||
if (@ARGV) { |
|
||||||
my $arg = shift @ARGV; |
|
||||||
if ($arg eq 'clear') { |
|
||||||
for my $path (keys %merge_rr) { |
|
||||||
my $name = $merge_rr{$path}; |
|
||||||
if (-d "$rr_dir/$name" && |
|
||||||
! -f "$rr_dir/$name/postimage") { |
|
||||||
rmtree(["$rr_dir/$name"]); |
|
||||||
} |
|
||||||
} |
|
||||||
unlink $merge_rr; |
|
||||||
} |
|
||||||
elsif ($arg eq 'status') { |
|
||||||
for my $path (keys %merge_rr) { |
|
||||||
print $path, "\n"; |
|
||||||
} |
|
||||||
} |
|
||||||
elsif ($arg eq 'diff') { |
|
||||||
for my $path (keys %merge_rr) { |
|
||||||
my $name = $merge_rr{$path}; |
|
||||||
system('diff', ((@ARGV == 0) ? ('-u') : @ARGV), |
|
||||||
'-L', "a/$path", '-L', "b/$path", |
|
||||||
"$rr_dir/$name/preimage", $path); |
|
||||||
} |
|
||||||
} |
|
||||||
elsif ($arg eq 'gc') { |
|
||||||
garbage_collect_rerere(@ARGV); |
|
||||||
} |
|
||||||
else { |
|
||||||
die "$0 unknown command: $arg\n"; |
|
||||||
} |
|
||||||
exit 0; |
|
||||||
} |
|
||||||
|
|
||||||
my %conflict = map { $_ => 1 } find_conflict(); |
|
||||||
|
|
||||||
# MERGE_RR records paths with conflicts immediately after merge |
|
||||||
# failed. Some of the conflicted paths might have been hand resolved |
|
||||||
# in the working tree since then, but the initial run would catch all |
|
||||||
# and register their preimages. |
|
||||||
|
|
||||||
for my $path (keys %conflict) { |
|
||||||
# This path has conflict. If it is not recorded yet, |
|
||||||
# record the pre-image. |
|
||||||
if (!exists $merge_rr{$path}) { |
|
||||||
my ($name, $hunk) = compute_conflict_name($path); |
|
||||||
next unless ($hunk); |
|
||||||
$merge_rr{$path} = $name; |
|
||||||
if (! -d "$rr_dir/$name") { |
|
||||||
mkpath("$rr_dir/$name", 0, 0777); |
|
||||||
print STDERR "Recorded preimage for '$path'\n"; |
|
||||||
record_preimage($path, "$rr_dir/$name/preimage"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# Now some of the paths that had conflicts earlier might have been |
|
||||||
# hand resolved. Others may be similar to a conflict already that |
|
||||||
# was resolved before. |
|
||||||
|
|
||||||
for my $path (keys %merge_rr) { |
|
||||||
my $name = $merge_rr{$path}; |
|
||||||
|
|
||||||
# We could resolve this automatically if we have images. |
|
||||||
if (-f "$rr_dir/$name/preimage" && |
|
||||||
-f "$rr_dir/$name/postimage") { |
|
||||||
if (merge($name, $path)) { |
|
||||||
print STDERR "Resolved '$path' using previous resolution.\n"; |
|
||||||
# Then we do not have to worry about this path |
|
||||||
# anymore. |
|
||||||
delete $merge_rr{$path}; |
|
||||||
next; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# Let's see if we have resolved it. |
|
||||||
(undef, my $hunk) = compute_conflict_name($path); |
|
||||||
next if ($hunk); |
|
||||||
|
|
||||||
print STDERR "Recorded resolution for '$path'.\n"; |
|
||||||
copy($path, "$rr_dir/$name/postimage"); |
|
||||||
# And we do not have to worry about this path anymore. |
|
||||||
delete $merge_rr{$path}; |
|
||||||
} |
|
||||||
|
|
||||||
# Write out the rest. |
|
||||||
write_rr(); |
|
Loading…
Reference in new issue