228 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
#!/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('merge', 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;
 | 
						|
}
 | 
						|
 | 
						|
-d "$rr_dir" || exit(0);
 | 
						|
 | 
						|
read_rr();
 | 
						|
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();
 |