difftool: Use symlinks when diffing against the worktree

Teach difftool's --dir-diff mode to use symlinks to represent files from the working copy, and make it the default behavior for the non-Windows platforms. Using symlinks is simpler and safer since we do not need to worry about copying files back into the worktree. The old behavior is still available as --no-symlinks. Signed-off-by: David Aguilar <davvid@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

David Aguilar committed Jul 22, 2012 at 23:05 UTC 1f2293457579a5bf22bb9c8324ded22b10705cc2
2 files changed +31 -10
Documentation/git-difftool.txt
+8
@@ -66,6 +66,14 @@ of the diff post-image. `$MERGED` is the name of the file which is
66 being compared. `$BASE` is provided for compatibility
67 with custom merge tool commands and has the same value as `$MERGED`.
68
69 +--symlinks::
70 +--no-symlinks::
71 + 'git difftool''s default behavior is create symlinks to the
72 + working tree when run in `--dir-diff` mode.
73 ++
74 + Specifying `--no-symlinks` instructs 'git difftool' to create
75 + copies instead. `--no-symlinks` is the default on Windows.
76 +
77 --tool-help::
78 Print a list of diff tools that may be used with `--tool`.
79
git-difftool.perl
+23 -10
@@ -92,7 +92,7 @@ sub print_tool_help
92
93 sub setup_dir_diff
94 {
95 - my ($repo, $workdir) = @_;
95 + my ($repo, $workdir, $symlinks) = @_;
96
97 # Run the diff; exit immediately if no diff found
98 # 'Repository' and 'WorkingCopy' must be explicitly set to insure that
@@ -209,8 +209,13 @@ sub setup_dir_diff
209 unless (-d "$rdir/$dir") {
210 mkpath("$rdir/$dir") or die $!;
211 }
212 - copy("$workdir/$file", "$rdir/$file") or die $!;
213 - chmod(stat("$workdir/$file")->mode, "$rdir/$file") or die $!;
212 + if ($symlinks) {
213 + symlink("$workdir/$file", "$rdir/$file") or die $!;
214 + } else {
215 + copy("$workdir/$file", "$rdir/$file") or die $!;
216 + my $mode = stat("$workdir/$file")->mode;
217 + chmod($mode, "$rdir/$file") or die $!;
218 + }
219 }
220
221 # Changes to submodules require special treatment. This loop writes a
@@ -271,6 +276,7 @@ sub main
276 gui => undef,
277 help => undef,
278 prompt => undef,
279 + symlinks => $^O ne 'MSWin32' && $^O ne 'msys',
280 tool_help => undef,
281 );
282 GetOptions('g|gui!' => \$opts{gui},
@@ -278,6 +284,8 @@ sub main
284 'h' => \$opts{help},
285 'prompt!' => \$opts{prompt},
286 'y' => sub { $opts{prompt} = 0; },
287 + 'symlinks' => \$opts{symlinks},
288 + 'no-symlinks' => sub { $opts{symlinks} = 0; },
289 't|tool:s' => \$opts{difftool_cmd},
290 'tool-help' => \$opts{tool_help},
291 'x|extcmd:s' => \$opts{extcmd});
@@ -316,7 +324,7 @@ sub main
324 # will invoke a separate instance of 'git-difftool--helper' for
325 # each file that changed.
326 if (defined($opts{dirdiff})) {
319 - dir_diff($opts{extcmd});
327 + dir_diff($opts{extcmd}, $opts{symlinks});
328 } else {
329 file_diff($opts{prompt});
330 }
@@ -324,13 +332,13 @@ sub main
332
333 sub dir_diff
334 {
327 - my ($extcmd) = @_;
335 + my ($extcmd, $symlinks) = @_;
336
337 my $rc;
338 my $repo = Git->repository();
339
340 my $workdir = find_worktree($repo);
333 - my ($a, $b, @working_tree) = setup_dir_diff($repo, $workdir);
341 + my ($a, $b, @worktree) = setup_dir_diff($repo, $workdir, $symlinks);
342 if (defined($extcmd)) {
343 $rc = system($extcmd, $a, $b);
344 } else {
@@ -342,13 +350,18 @@ sub dir_diff
350
351 # If the diff including working copy files and those
352 # files were modified during the diff, then the changes
345 - # should be copied back to the working tree
346 - for my $file (@working_tree) {
347 - if (-e "$b/$file" && compare("$b/$file", "$workdir/$file")) {
353 + # should be copied back to the working tree.
354 + # Do not copy back files when symlinks are used and the
355 + # external tool did not replace the original link with a file.
356 + for my $file (@worktree) {
357 + next if $symlinks && -l "$b/$file";
358 + if (-f "$b/$file" && compare("$b/$file", "$workdir/$file")) {
359 copy("$b/$file", "$workdir/$file") or die $!;
349 - chmod(stat("$b/$file")->mode, "$workdir/$file") or die $!;
360 + my $mode = stat("$b/$file")->mode;
361 + chmod($mode, "$workdir/$file") or die $!;
362 }
363 }
364 + exit(0);
365 }
366
367 sub file_diff