1818use File::Compare;
1919use File::Find;
2020use File::stat ;
21- use File::Path qw( mkpath) ;
21+ use File::Path qw( mkpath rmtree ) ;
2222use File::Temp qw( tempdir) ;
2323use Getopt::Long qw( :config pass_through) ;
2424use 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+
115127sub 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
281308sub 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
301330sub main
@@ -366,21 +395,19 @@ sub main
366395sub 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
409446sub file_diff
0 commit comments