3 # REuse REcorded REsolve. This tool records a conflicted automerge
4 # result and its hand resolution, and helps to resolve future
5 # automerge that results in the same conflict.
7 # To enable this feature, create a directory 'rr-cache' under your
14 my $git_dir = $::ENV
{GIT_DIR
} || ".git";
15 my $rr_dir = "$git_dir/rr-cache";
16 my $merge_rr = "$git_dir/rr-cache/MERGE_RR";
27 open $in, "<$merge_rr" or die "$!: $merge_rr";
30 my ($name, $path) = /^([0-9a-f]{40})\t(.*)$/s;
31 $merge_rr{$path} = $name;
38 open $out, ">$merge_rr" or die "$!: $merge_rr";
39 for my $path (sort keys %merge_rr) {
40 my $name = $merge_rr{$path};
41 print $out "$name\t$path\0";
46 sub compute_conflict_name
{
50 open $in, "<$path" or die "$!: $path";
52 my $sha1 = Digest
->new("SHA-1");
62 elsif (/^>>>>>>> .*/) {
64 $one = join('', @
{$side[0]});
65 $two = join('', @
{$side[1]});
67 ($one, $two) = ($two, $one);
78 elsif (defined $side[1]) {
86 return ($sha1->hexdigest, $hunk);
90 my ($path, $name) = @_;
93 open $in, "<$path" or die "$!: $path";
94 open $out, ">$name" or die "$!: $name";
100 elsif (/^=======$/) {
103 elsif (/^>>>>>>> .*/) {
105 $one = join('', @
{$side[0]});
106 $two = join('', @
{$side[1]});
108 ($one, $two) = ($two, $one);
110 print $out "<<<<<<<\n";
112 print $out "=======\n";
114 print $out ">>>>>>>\n";
120 elsif (defined $side[1]) {
121 push @
{$side[1]}, $_;
124 push @
{$side[0]}, $_;
134 my $pid = open($in, '-|');
135 die "$!" unless defined $pid;
137 exec(qw(git ls-files -z -u)) or die "$!: ls-files";
143 my ($mode, $sha1, $stage, $path) =
144 /^([0-7]+) ([0-9a-f]{40}) ([123])\t(.*)$/s;
145 $path{$path} |= (1 << $stage);
148 while (my ($path, $status) = each %path) {
149 if ($status == 14) { push @path, $path; }
155 my ($name, $path) = @_;
156 record_preimage
($path, "$rr_dir/$name/thisimage");
157 unless (system('git', 'merge-file', map { "$rr_dir/$name/${_}image" }
158 qw(this pre post))) {
160 open $in, "<$rr_dir/$name/thisimage" or
161 die "$!: $name/thisimage";
163 open $out, ">$path" or die "$!: $path";
164 while (<$in>) { print $out $_; }
172 sub garbage_collect_rerere
{
173 # We should allow specifying these from the command line and
174 # that is why the caller gives @ARGV to us, but I am lazy.
176 my $cutoff_noresolve = 15; # two weeks
177 my $cutoff_resolve = 60; # two months
179 while (<$rr_dir/*/preimage
>) {
180 my ($dir) = /^(.*)\/preimage
$/;
181 my $cutoff = ((-f
"$dir/postimage")
183 : $cutoff_noresolve);
185 if ($cutoff <= $age) {
186 push @to_remove, $dir;
194 -d
"$rr_dir" || exit(0);
199 my $arg = shift @ARGV;
200 if ($arg eq 'clear') {
201 for my $path (keys %merge_rr) {
202 my $name = $merge_rr{$path};
203 if (-d
"$rr_dir/$name" &&
204 ! -f
"$rr_dir/$name/postimage") {
205 rmtree
(["$rr_dir/$name"]);
210 elsif ($arg eq 'status') {
211 for my $path (keys %merge_rr) {
215 elsif ($arg eq 'diff') {
216 for my $path (keys %merge_rr) {
217 my $name = $merge_rr{$path};
218 system('diff', ((@ARGV == 0) ?
('-u') : @ARGV),
219 '-L', "a/$path", '-L', "b/$path",
220 "$rr_dir/$name/preimage", $path);
223 elsif ($arg eq 'gc') {
224 garbage_collect_rerere
(@ARGV);
227 die "$0 unknown command: $arg\n";
232 my %conflict = map { $_ => 1 } find_conflict
();
234 # MERGE_RR records paths with conflicts immediately after merge
235 # failed. Some of the conflicted paths might have been hand resolved
236 # in the working tree since then, but the initial run would catch all
237 # and register their preimages.
239 for my $path (keys %conflict) {
240 # This path has conflict. If it is not recorded yet,
241 # record the pre-image.
242 if (!exists $merge_rr{$path}) {
243 my ($name, $hunk) = compute_conflict_name
($path);
245 $merge_rr{$path} = $name;
246 if (! -d
"$rr_dir/$name") {
247 mkpath
("$rr_dir/$name", 0, 0777);
248 print STDERR
"Recorded preimage for '$path'\n";
249 record_preimage
($path, "$rr_dir/$name/preimage");
254 # Now some of the paths that had conflicts earlier might have been
255 # hand resolved. Others may be similar to a conflict already that
256 # was resolved before.
258 for my $path (keys %merge_rr) {
259 my $name = $merge_rr{$path};
261 # We could resolve this automatically if we have images.
262 if (-f
"$rr_dir/$name/preimage" &&
263 -f
"$rr_dir/$name/postimage") {
264 if (merge
($name, $path)) {
265 print STDERR
"Resolved '$path' using previous resolution.\n";
266 # Then we do not have to worry about this path
268 delete $merge_rr{$path};
273 # Let's see if we have resolved it.
274 (undef, my $hunk) = compute_conflict_name
($path);
277 print STDERR
"Recorded resolution for '$path'.\n";
278 copy
($path, "$rr_dir/$name/postimage");
279 # And we do not have to worry about this path anymore.
280 delete $merge_rr{$path};
283 # Write out the rest.