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