difftool: Handle compare() returning -1

Keep the temporary directory around when compare() cannot read its input files, which is indicated by -1. Defer tempdir creation to allow an early exit in setup_dir_diff(). Wrap the rest of the entry points in an exit_cleanup() function to handle removing temporary files and error reporting. Print the temporary files' location so that the user can recover them. Signed-off-by: David Aguilar <davvid@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

David Aguilar committed Jul 26, 2012 at 12:36 UTC ceb1497a74e75a0c9bd53716b787d33ad6e2397f
1 file changed +68 -31
git-difftool.perl
+68 -31
@@ -18,7 +18,7 @@ use File::Copy;
18 use File::Compare;
19 use File::Find;
20 use File::stat;
21 -use File::Path qw(mkpath);
21 +use File::Path qw(mkpath rmtree);
22 use File::Temp qw(tempdir);
23 use Getopt::Long qw(:config pass_through);
24 use Git;
@@ -112,6 +112,18 @@ EOF
112 exit(0);
113 }
114
115 +sub exit_cleanup
116 +{
117 + my ($tmpdir, $status) = @_;
118 + my $errno = $!;
119 + rmtree($tmpdir);
120 + if ($status and $errno) {
121 + my ($package, $file, $line) = caller();
122 + warn "$file line $line: $errno\n";
123 + }
124 + exit($status | ($status >> 8));
125 +}
126 +
127 sub setup_dir_diff
128 {
129 my ($repo, $workdir, $symlinks) = @_;
@@ -128,13 +140,6 @@ sub setup_dir_diff
140 my $diffrtn = $diffrepo->command_oneline(@gitargs);
141 exit(0) if (length($diffrtn) == 0);
142
131 - # Setup temp directories
132 - my $tmpdir = tempdir('git-difftool.XXXXX', CLEANUP => 1, TMPDIR => 1);
133 - my $ldir = "$tmpdir/left";
134 - my $rdir = "$tmpdir/right";
135 - mkpath($ldir) or die $!;
136 - mkpath($rdir) or die $!;
137 -
143 # Build index info for left and right sides of the diff
144 my $submodule_mode = '160000';
145 my $symlink_mode = '120000';
@@ -203,6 +208,13 @@ EOF
208 }
209 }
210
211 + # Setup temp directories
212 + my $tmpdir = tempdir('git-difftool.XXXXX', CLEANUP => 0, TMPDIR => 1);
213 + my $ldir = "$tmpdir/left";
214 + my $rdir = "$tmpdir/right";
215 + mkpath($ldir) or exit_cleanup($tmpdir, 1);
216 + mkpath($rdir) or exit_cleanup($tmpdir, 1);
217 +
218 # If $GIT_DIR is not set prior to calling 'git update-index' and
219 # 'git checkout-index', then those commands will fail if difftool
220 # is called from a directory other than the repo root.
@@ -219,16 +231,18 @@ EOF
231 $repo->command_input_pipe(qw(update-index -z --index-info));
232 print($inpipe $lindex);
233 $repo->command_close_pipe($inpipe, $ctx);
234 +
235 my $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
223 - exit($rc | ($rc >> 8)) if ($rc != 0);
236 + exit_cleanup($tmpdir, $rc) if $rc != 0;
237
238 $ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
239 ($inpipe, $ctx) =
240 $repo->command_input_pipe(qw(update-index -z --index-info));
241 print($inpipe $rindex);
242 $repo->command_close_pipe($inpipe, $ctx);
243 +
244 $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
231 - exit($rc | ($rc >> 8)) if ($rc != 0);
245 + exit_cleanup($tmpdir, $rc) if $rc != 0;
246
247 # If $GIT_DIR was explicitly set just for the update/checkout
248 # commands, then it should be unset before continuing.
@@ -240,14 +254,19 @@ EOF
254 for my $file (@working_tree) {
255 my $dir = dirname($file);
256 unless (-d "$rdir/$dir") {
243 - mkpath("$rdir/$dir") or die $!;
257 + mkpath("$rdir/$dir") or
258 + exit_cleanup($tmpdir, 1);
259 }
260 if ($symlinks) {
246 - symlink("$workdir/$file", "$rdir/$file") or die $!;
261 + symlink("$workdir/$file", "$rdir/$file") or
262 + exit_cleanup($tmpdir, 1);
263 } else {
248 - copy("$workdir/$file", "$rdir/$file") or die $!;
264 + copy("$workdir/$file", "$rdir/$file") or
265 + exit_cleanup($tmpdir, 1);
266 +
267 my $mode = stat("$workdir/$file")->mode;
250 - chmod($mode, "$rdir/$file") or die $!;
268 + chmod($mode, "$rdir/$file") or
269 + exit_cleanup($tmpdir, 1);
270 }
271 }
272
@@ -255,27 +274,35 @@ EOF
274 # temporary file to both the left and right directories to show the
275 # change in the recorded SHA1 for the submodule.
276 for my $path (keys %submodule) {
277 + my $ok;
278 if (defined($submodule{$path}{left})) {
259 - write_to_file("$ldir/$path", "Subproject commit $submodule{$path}{left}");
279 + $ok = write_to_file("$ldir/$path",
280 + "Subproject commit $submodule{$path}{left}");
281 }
282 if (defined($submodule{$path}{right})) {
262 - write_to_file("$rdir/$path", "Subproject commit $submodule{$path}{right}");
283 + $ok = write_to_file("$rdir/$path",
284 + "Subproject commit $submodule{$path}{right}");
285 }
286 + exit_cleanup($tmpdir, 1) if not $ok;
287 }
288
289 # Symbolic links require special treatment. The standard "git diff"
290 # shows only the link itself, not the contents of the link target.
291 # This loop replicates that behavior.
292 for my $path (keys %symlink) {
293 + my $ok;
294 if (defined($symlink{$path}{left})) {
271 - write_to_file("$ldir/$path", $symlink{$path}{left});
295 + $ok = write_to_file("$ldir/$path",
296 + $symlink{$path}{left});
297 }
298 if (defined($symlink{$path}{right})) {
274 - write_to_file("$rdir/$path", $symlink{$path}{right});
299 + $ok = write_to_file("$rdir/$path",
300 + $symlink{$path}{right});
301 }
302 + exit_cleanup($tmpdir, 1) if not $ok;
303 }
304
278 - return ($ldir, $rdir, @working_tree);
305 + return ($ldir, $rdir, $tmpdir, @working_tree);
306 }
307
308 sub write_to_file
@@ -286,16 +313,18 @@ sub write_to_file
313 # Make sure the path to the file exists
314 my $dir = dirname($path);
315 unless (-d "$dir") {
289 - mkpath("$dir") or die $!;
316 + mkpath("$dir") or return 0;
317 }
318
319 # If the file already exists in that location, delete it. This
320 # is required in the case of symbolic links.
294 - unlink("$path");
321 + unlink($path);
322
296 - open(my $fh, '>', "$path") or die $!;
323 + open(my $fh, '>', $path) or return 0;
324 print($fh $value);
325 close($fh);
326 +
327 + return 1;
328 }
329
330 sub main
@@ -366,21 +395,19 @@ sub main
395 sub dir_diff
396 {
397 my ($extcmd, $symlinks) = @_;
369 -
398 my $rc;
399 + my $error = 0;
400 my $repo = Git->repository();
372 -
401 my $workdir = find_worktree($repo);
374 - my ($a, $b, @worktree) = setup_dir_diff($repo, $workdir, $symlinks);
402 + my ($a, $b, $tmpdir, @worktree) =
403 + setup_dir_diff($repo, $workdir, $symlinks);
404 +
405 if (defined($extcmd)) {
406 $rc = system($extcmd, $a, $b);
407 } else {
408 $ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';
409 $rc = system('git', 'difftool--helper', $a, $b);
410 }
381 -
382 - exit($rc | ($rc >> 8)) if ($rc != 0);
383 -
411 # If the diff including working copy files and those
412 # files were modified during the diff, then the changes
413 # should be copied back to the working tree.
@@ -397,13 +424,23 @@ sub dir_diff
424 my $errmsg = "warning: Could not compare ";
425 $errmsg += "'$b/$file' with '$workdir/$file'\n";
426 warn $errmsg;
427 + $error = 1;
428 } elsif ($diff == 1) {
401 - copy("$b/$file", "$workdir/$file") or die $!;
429 my $mode = stat("$b/$file")->mode;
403 - chmod($mode, "$workdir/$file") or die $!;
430 + copy("$b/$file", "$workdir/$file") or
431 + exit_cleanup($tmpdir, 1);
432 +
433 + chmod($mode, "$workdir/$file") or
434 + exit_cleanup($tmpdir, 1);
435 }
436 }
406 - exit(0);
437 + if ($error) {
438 + warn "warning: Temporary files exist in '$tmpdir'.\n";
439 + warn "warning: You may want to cleanup or recover these.\n";
440 + exit(1);
441 + } else {
442 + exit_cleanup($tmpdir, $rc);
443 + }
444 }
445
446 sub file_diff