1313use 5.008;
1414use strict;
1515use warnings;
16+ use Error qw(:try);
1617use File::Basename qw(dirname);
1718use File::Copy;
18- use File::Compare;
1919use File::Find;
2020use File::stat;
2121use File::Path qw(mkpath rmtree);
@@ -88,14 +88,45 @@ sub use_wt_file
8888 my ($repo, $workdir, $file, $sha1, $symlinks) = @_;
8989 my $null_sha1 = '0' x 40;
9090
91- if ($sha1 eq $null_sha1) {
92- return 1;
93- } elsif (not $symlinks) {
91+ if ($sha1 ne $null_sha1 and not $symlinks) {
9492 return 0;
9593 }
9694
9795 my $wt_sha1 = $repo->command_oneline('hash-object', "$workdir/$file");
98- return $sha1 eq $wt_sha1;
96+ my $use = ($sha1 eq $null_sha1) || ($sha1 eq $wt_sha1);
97+ return ($use, $wt_sha1);
98+ }
99+
100+ sub changed_files
101+ {
102+ my ($repo_path, $index, $worktree) = @_;
103+ $ENV{GIT_INDEX_FILE} = $index;
104+ $ENV{GIT_WORK_TREE} = $worktree;
105+ my $must_unset_git_dir = 0;
106+ if (not defined($ENV{GIT_DIR})) {
107+ $must_unset_git_dir = 1;
108+ $ENV{GIT_DIR} = $repo_path;
109+ }
110+
111+ my @refreshargs = qw/update-index --really-refresh -q --unmerged/;
112+ my @gitargs = qw/diff-files --name-only -z/;
113+ try {
114+ Git::command_oneline(@refreshargs);
115+ } catch Git::Error::Command with {};
116+
117+ my $line = Git::command_oneline(@gitargs);
118+ my @files;
119+ if (defined $line) {
120+ @files = split('\0', $line);
121+ } else {
122+ @files = ();
123+ }
124+
125+ delete($ENV{GIT_INDEX_FILE});
126+ delete($ENV{GIT_WORK_TREE});
127+ delete($ENV{GIT_DIR}) if ($must_unset_git_dir);
128+
129+ return map { $_ => 1 } @files;
99130}
100131
101132sub setup_dir_diff
@@ -121,6 +152,7 @@ sub setup_dir_diff
121152 my $null_sha1 = '0' x 40;
122153 my $lindex = '';
123154 my $rindex = '';
155+ my $wtindex = '';
124156 my %submodule;
125157 my %symlink;
126158 my @working_tree = ();
@@ -174,8 +206,12 @@ sub setup_dir_diff
174206 }
175207
176208 if ($rmode ne $null_mode) {
177- if (use_wt_file($repo, $workdir, $dst_path, $rsha1, $symlinks)) {
178- push(@working_tree, $dst_path);
209+ my ($use, $wt_sha1) = use_wt_file($repo, $workdir,
210+ $dst_path, $rsha1,
211+ $symlinks);
212+ if ($use) {
213+ push @working_tree, $dst_path;
214+ $wtindex .= "$rmode $wt_sha1\t$dst_path\0";
179215 } else {
180216 $rindex .= "$rmode $rsha1\t$dst_path\0";
181217 }
@@ -218,6 +254,12 @@ sub setup_dir_diff
218254 $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
219255 exit_cleanup($tmpdir, $rc) if $rc != 0;
220256
257+ $ENV{GIT_INDEX_FILE} = "$tmpdir/wtindex";
258+ ($inpipe, $ctx) =
259+ $repo->command_input_pipe(qw(update-index --info-only -z --index-info));
260+ print($inpipe $wtindex);
261+ $repo->command_close_pipe($inpipe, $ctx);
262+
221263 # If $GIT_DIR was explicitly set just for the update/checkout
222264 # commands, then it should be unset before continuing.
223265 delete($ENV{GIT_DIR}) if ($must_unset_git_dir);
@@ -390,19 +432,34 @@ sub dir_diff
390432 # should be copied back to the working tree.
391433 # Do not copy back files when symlinks are used and the
392434 # external tool did not replace the original link with a file.
435+ #
436+ # These hashes are loaded lazily since they aren't needed
437+ # in the common case of --symlinks and the difftool updating
438+ # files through the symlink.
439+ my %wt_modified;
440+ my %tmp_modified;
441+ my $indices_loaded = 0;
442+
393443 for my $file (@worktree) {
394444 next if $symlinks && -l "$b/$file";
395445 next if ! -f "$b/$file";
396446
397- my $diff = compare("$b/$file", "$workdir/$file");
398- if ($diff == 0) {
399- next;
400- } elsif ($diff == -1) {
401- my $errmsg = "warning: Could not compare ";
402- $errmsg += "'$b/$file' with '$workdir/$file'\n";
447+ if (!$indices_loaded) {
448+ %wt_modified = changed_files($repo->repo_path(),
449+ "$tmpdir/wtindex", "$workdir");
450+ %tmp_modified = changed_files($repo->repo_path(),
451+ "$tmpdir/wtindex", "$b");
452+ $indices_loaded = 1;
453+ }
454+
455+ if (exists $wt_modified{$file} and exists $tmp_modified{$file}) {
456+ my $errmsg = "warning: Both files modified: ";
457+ $errmsg .= "'$workdir/$file' and '$b/$file'.\n";
458+ $errmsg .= "warning: Working tree file has been left.\n";
459+ $errmsg .= "warning:\n";
403460 warn $errmsg;
404461 $error = 1;
405- } elsif ($diff == 1 ) {
462+ } elsif (exists $tmp_modified{$file} ) {
406463 my $mode = stat("$b/$file")->mode;
407464 copy("$b/$file", "$workdir/$file") or
408465 exit_cleanup($tmpdir, 1);
0 commit comments