 c817faabd7
			
		
	
	c817faabd7
	
	
	
		
			
			It is handy to have a copy readily available for checking regressions. Signed-off-by: Junio C Hamano <gitster@pobox.com>
		
			
				
	
	
		
			285 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			285 lines
		
	
	
		
			6.0 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('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();
 |