Skip to content

Commit ceb1497

Browse files
davvidgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent a4cd5be commit ceb1497

File tree

1 file changed

+68
-31
lines changed

1 file changed

+68
-31
lines changed

git-difftool.perl

Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use File::Compare;
1919
use File::Find;
2020
use File::stat;
21-
use File::Path qw(mkpath);
21+
use File::Path qw(mkpath rmtree);
2222
use File::Temp qw(tempdir);
2323
use Getopt::Long qw(:config pass_through);
2424
use Git;
@@ -112,6 +112,18 @@ sub print_tool_help
112112
exit(0);
113113
}
114114

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+
115127
sub setup_dir_diff
116128
{
117129
my ($repo, $workdir, $symlinks) = @_;
@@ -128,13 +140,6 @@ sub setup_dir_diff
128140
my $diffrtn = $diffrepo->command_oneline(@gitargs);
129141
exit(0) if (length($diffrtn) == 0);
130142

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-
138143
# Build index info for left and right sides of the diff
139144
my $submodule_mode = '160000';
140145
my $symlink_mode = '120000';
@@ -203,6 +208,13 @@ sub setup_dir_diff
203208
}
204209
}
205210

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+
206218
# If $GIT_DIR is not set prior to calling 'git update-index' and
207219
# 'git checkout-index', then those commands will fail if difftool
208220
# is called from a directory other than the repo root.
@@ -219,16 +231,18 @@ sub setup_dir_diff
219231
$repo->command_input_pipe(qw(update-index -z --index-info));
220232
print($inpipe $lindex);
221233
$repo->command_close_pipe($inpipe, $ctx);
234+
222235
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;
224237

225238
$ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
226239
($inpipe, $ctx) =
227240
$repo->command_input_pipe(qw(update-index -z --index-info));
228241
print($inpipe $rindex);
229242
$repo->command_close_pipe($inpipe, $ctx);
243+
230244
$rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
231-
exit($rc | ($rc >> 8)) if ($rc != 0);
245+
exit_cleanup($tmpdir, $rc) if $rc != 0;
232246

233247
# If $GIT_DIR was explicitly set just for the update/checkout
234248
# commands, then it should be unset before continuing.
@@ -240,42 +254,55 @@ sub setup_dir_diff
240254
for my $file (@working_tree) {
241255
my $dir = dirname($file);
242256
unless (-d "$rdir/$dir") {
243-
mkpath("$rdir/$dir") or die $!;
257+
mkpath("$rdir/$dir") or
258+
exit_cleanup($tmpdir, 1);
244259
}
245260
if ($symlinks) {
246-
symlink("$workdir/$file", "$rdir/$file") or die $!;
261+
symlink("$workdir/$file", "$rdir/$file") or
262+
exit_cleanup($tmpdir, 1);
247263
} else {
248-
copy("$workdir/$file", "$rdir/$file") or die $!;
264+
copy("$workdir/$file", "$rdir/$file") or
265+
exit_cleanup($tmpdir, 1);
266+
249267
my $mode = stat("$workdir/$file")->mode;
250-
chmod($mode, "$rdir/$file") or die $!;
268+
chmod($mode, "$rdir/$file") or
269+
exit_cleanup($tmpdir, 1);
251270
}
252271
}
253272

254273
# Changes to submodules require special treatment. This loop writes a
255274
# temporary file to both the left and right directories to show the
256275
# change in the recorded SHA1 for the submodule.
257276
for my $path (keys %submodule) {
277+
my $ok;
258278
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}");
260281
}
261282
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}");
263285
}
286+
exit_cleanup($tmpdir, 1) if not $ok;
264287
}
265288

266289
# Symbolic links require special treatment. The standard "git diff"
267290
# shows only the link itself, not the contents of the link target.
268291
# This loop replicates that behavior.
269292
for my $path (keys %symlink) {
293+
my $ok;
270294
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});
272297
}
273298
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});
275301
}
302+
exit_cleanup($tmpdir, 1) if not $ok;
276303
}
277304

278-
return ($ldir, $rdir, @working_tree);
305+
return ($ldir, $rdir, $tmpdir, @working_tree);
279306
}
280307

281308
sub write_to_file
@@ -286,16 +313,18 @@ sub write_to_file
286313
# Make sure the path to the file exists
287314
my $dir = dirname($path);
288315
unless (-d "$dir") {
289-
mkpath("$dir") or die $!;
316+
mkpath("$dir") or return 0;
290317
}
291318

292319
# If the file already exists in that location, delete it. This
293320
# is required in the case of symbolic links.
294-
unlink("$path");
321+
unlink($path);
295322

296-
open(my $fh, '>', "$path") or die $!;
323+
open(my $fh, '>', $path) or return 0;
297324
print($fh $value);
298325
close($fh);
326+
327+
return 1;
299328
}
300329

301330
sub main
@@ -366,21 +395,19 @@ sub main
366395
sub dir_diff
367396
{
368397
my ($extcmd, $symlinks) = @_;
369-
370398
my $rc;
399+
my $error = 0;
371400
my $repo = Git->repository();
372-
373401
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+
375405
if (defined($extcmd)) {
376406
$rc = system($extcmd, $a, $b);
377407
} else {
378408
$ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';
379409
$rc = system('git', 'difftool--helper', $a, $b);
380410
}
381-
382-
exit($rc | ($rc >> 8)) if ($rc != 0);
383-
384411
# If the diff including working copy files and those
385412
# files were modified during the diff, then the changes
386413
# should be copied back to the working tree.
@@ -397,13 +424,23 @@ sub dir_diff
397424
my $errmsg = "warning: Could not compare ";
398425
$errmsg += "'$b/$file' with '$workdir/$file'\n";
399426
warn $errmsg;
427+
$error = 1;
400428
} elsif ($diff == 1) {
401-
copy("$b/$file", "$workdir/$file") or die $!;
402429
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);
404435
}
405436
}
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+
}
407444
}
408445

409446
sub file_diff

0 commit comments

Comments
 (0)