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>
This commit is contained in:
David Aguilar 2012-07-22 23:05:30 -07:00 committed by Junio C Hamano
parent b965e8f44a
commit 1f22934575
2 changed files with 31 additions and 10 deletions

View File

@ -66,6 +66,14 @@ of the diff post-image. `$MERGED` is the name of the file which is
being compared. `$BASE` is provided for compatibility being compared. `$BASE` is provided for compatibility
with custom merge tool commands and has the same value as `$MERGED`. with custom merge tool commands and has the same value as `$MERGED`.
--symlinks::
--no-symlinks::
'git difftool''s default behavior is create symlinks to the
working tree when run in `--dir-diff` mode.
+
Specifying `--no-symlinks` instructs 'git difftool' to create
copies instead. `--no-symlinks` is the default on Windows.
--tool-help:: --tool-help::
Print a list of diff tools that may be used with `--tool`. Print a list of diff tools that may be used with `--tool`.

View File

@ -92,7 +92,7 @@ sub print_tool_help
sub setup_dir_diff sub setup_dir_diff
{ {
my ($repo, $workdir) = @_; my ($repo, $workdir, $symlinks) = @_;
# Run the diff; exit immediately if no diff found # Run the diff; exit immediately if no diff found
# 'Repository' and 'WorkingCopy' must be explicitly set to insure that # 'Repository' and 'WorkingCopy' must be explicitly set to insure that
@ -209,8 +209,13 @@ sub setup_dir_diff
unless (-d "$rdir/$dir") { unless (-d "$rdir/$dir") {
mkpath("$rdir/$dir") or die $!; mkpath("$rdir/$dir") or die $!;
} }
if ($symlinks) {
symlink("$workdir/$file", "$rdir/$file") or die $!;
} else {
copy("$workdir/$file", "$rdir/$file") or die $!; copy("$workdir/$file", "$rdir/$file") or die $!;
chmod(stat("$workdir/$file")->mode, "$rdir/$file") or die $!; my $mode = stat("$workdir/$file")->mode;
chmod($mode, "$rdir/$file") or die $!;
}
} }
# Changes to submodules require special treatment. This loop writes a # Changes to submodules require special treatment. This loop writes a
@ -271,6 +276,7 @@ sub main
gui => undef, gui => undef,
help => undef, help => undef,
prompt => undef, prompt => undef,
symlinks => $^O ne 'MSWin32' && $^O ne 'msys',
tool_help => undef, tool_help => undef,
); );
GetOptions('g|gui!' => \$opts{gui}, GetOptions('g|gui!' => \$opts{gui},
@ -278,6 +284,8 @@ sub main
'h' => \$opts{help}, 'h' => \$opts{help},
'prompt!' => \$opts{prompt}, 'prompt!' => \$opts{prompt},
'y' => sub { $opts{prompt} = 0; }, 'y' => sub { $opts{prompt} = 0; },
'symlinks' => \$opts{symlinks},
'no-symlinks' => sub { $opts{symlinks} = 0; },
't|tool:s' => \$opts{difftool_cmd}, 't|tool:s' => \$opts{difftool_cmd},
'tool-help' => \$opts{tool_help}, 'tool-help' => \$opts{tool_help},
'x|extcmd:s' => \$opts{extcmd}); 'x|extcmd:s' => \$opts{extcmd});
@ -316,7 +324,7 @@ sub main
# will invoke a separate instance of 'git-difftool--helper' for # will invoke a separate instance of 'git-difftool--helper' for
# each file that changed. # each file that changed.
if (defined($opts{dirdiff})) { if (defined($opts{dirdiff})) {
dir_diff($opts{extcmd}); dir_diff($opts{extcmd}, $opts{symlinks});
} else { } else {
file_diff($opts{prompt}); file_diff($opts{prompt});
} }
@ -324,13 +332,13 @@ sub main
sub dir_diff sub dir_diff
{ {
my ($extcmd) = @_; my ($extcmd, $symlinks) = @_;
my $rc; my $rc;
my $repo = Git->repository(); my $repo = Git->repository();
my $workdir = find_worktree($repo); my $workdir = find_worktree($repo);
my ($a, $b, @working_tree) = setup_dir_diff($repo, $workdir); my ($a, $b, @worktree) = setup_dir_diff($repo, $workdir, $symlinks);
if (defined($extcmd)) { if (defined($extcmd)) {
$rc = system($extcmd, $a, $b); $rc = system($extcmd, $a, $b);
} else { } else {
@ -342,13 +350,18 @@ sub dir_diff
# If the diff including working copy files and those # If the diff including working copy files and those
# files were modified during the diff, then the changes # files were modified during the diff, then the changes
# should be copied back to the working tree # should be copied back to the working tree.
for my $file (@working_tree) { # Do not copy back files when symlinks are used and the
if (-e "$b/$file" && compare("$b/$file", "$workdir/$file")) { # external tool did not replace the original link with a file.
for my $file (@worktree) {
next if $symlinks && -l "$b/$file";
if (-f "$b/$file" && compare("$b/$file", "$workdir/$file")) {
copy("$b/$file", "$workdir/$file") or die $!; copy("$b/$file", "$workdir/$file") or die $!;
chmod(stat("$b/$file")->mode, "$workdir/$file") or die $!; my $mode = stat("$b/$file")->mode;
chmod($mode, "$workdir/$file") or die $!;
} }
} }
exit(0);
} }
sub file_diff sub file_diff