{"thread":{"id":"30296","subject":"[PATCH v14 8/9] difftool: teach difftool to handle directory diffs","startedAt":"2012-04-20T16:56:28Z","lastAt":"2012-04-21T23:07:36Z","messageCount":3,"participants":["Tim Henigan","Junio C Hamano","David Aguilar"],"isPatch":true,"patchVersion":14,"patchTotal":9},"messages":[{"id":"189767","messageId":"1334940988-8940-1-git-send-email-tim.henigan@gmail.com","threadId":"30296","inReplyTo":null,"subject":"[PATCH v14 8/9] difftool: teach difftool to handle directory diffs","fromName":"Tim Henigan","fromEmail":"tim.henigan@gmail.com","sentAt":"2012-04-20T16:56:28Z","receivedAt":"2012-04-20T16:56:28Z","isPatch":true,"sender":{"key":"tim.henigan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42022?v=4"},"body":"When 'difftool' is called to compare a range of commits that modify\nmore than one file, it opens a separate instance of the diff tool for\neach file that changed.\n\nThe new '--dir-diff' option copies all the modified files to a temporary\nlocation and runs a directory diff on them in a single instance of the\ndiff tool.\n\nSigned-off-by: Tim Henigan <tim.henigan@gmail.com>\n---\n\nThis replaces 7fbdc6c on the th/difftool-diffall branch [1].\n\nChanges in v14:\n\n  - Fixed use of unitialized vars in the find_worktree function. A warning\n    was seen when run on msysgit, but not other platforms.\n\n  - Added a helper function (write_to_file) to replace repeated logic.\n\n  - Fixed handling of symbolic links. The diff shown in the external tool\n    now matches what is seen in the output of 'git diff'.\n\n  - Added support for the diff format used for renames and copies. In the\n    external tool, these will still appear as delete/add pairs.\n\n  - Added logic to detect and abort if a combined diff ('--cc' or '-c')\n    is commanded at the same time as directory diff ('--dir-diff').\n\n[1]: https://github.com/gitster/git/tree/th/difftool-diffall\n\n\n Documentation/git-difftool.txt |    6 +\n git-difftool--helper.sh        |   19 ++-\n git-difftool.perl              |  261 +++++++++++++++++++++++++++++++++++++---\n t/t7800-difftool.sh            |   39 ++++++\n 4 files changed, 299 insertions(+), 26 deletions(-)\n\ndiff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt\nindex fe38f66..aba5e76 100644\n--- a/Documentation/git-difftool.txt\n+++ b/Documentation/git-difftool.txt\n@@ -19,6 +19,12 @@ linkgit:git-diff[1].\n \n OPTIONS\n -------\n+-d::\n+--dir-diff::\n+\tCopy the modified files to a temporary location and perform\n+\ta directory diff on them. This mode never prompts before\n+\tlaunching the diff tool.\n+\n -y::\n --no-prompt::\n \tDo not prompt before launching a diff tool.\ndiff --git a/git-difftool--helper.sh b/git-difftool--helper.sh\nindex e6558d1..3d0fe0c 100755\n--- a/git-difftool--helper.sh\n+++ b/git-difftool--helper.sh\n@@ -73,9 +73,16 @@ then\n \tfi\n fi\n \n-# Launch the merge tool on each path provided by 'git diff'\n-while test $# -gt 6\n-do\n-\tlaunch_merge_tool \"$1\" \"$2\" \"$5\"\n-\tshift 7\n-done\n+if test -n \"$GIT_DIFFTOOL_DIRDIFF\"\n+then\n+\tLOCAL=\"$1\"\n+\tREMOTE=\"$2\"\n+\trun_merge_tool \"$merge_tool\" false\n+else\n+\t# Launch the merge tool on each path provided by 'git diff'\n+\twhile test $# -gt 6\n+\tdo\n+\t\tlaunch_merge_tool \"$1\" \"$2\" \"$5\"\n+\t\tshift 7\n+\tdone\n+fi\ndiff --git a/git-difftool.perl b/git-difftool.perl\nindex aba3d2f..7211f1e 100755\n--- a/git-difftool.perl\n+++ b/git-difftool.perl\n@@ -1,21 +1,31 @@\n-#!/usr/bin/env perl\n+#!/usr/bin/perl\n # Copyright (c) 2009, 2010 David Aguilar\n+# Copyright (c) 2012 Tim Henigan\n #\n # This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n # git-difftool--helper script.\n #\n # This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n-# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL\n-# are exported for use by git-difftool--helper.\n+# The GIT_DIFF* variables are exported for use by git-difftool--helper.\n #\n # Any arguments that are unknown to this script are forwarded to 'git diff'.\n \n use 5.008;\n use strict;\n use warnings;\n+use File::Basename qw(dirname);\n+use File::Copy;\n+use File::stat;\n+use File::Path qw(mkpath);\n+use File::Temp qw(tempdir);\n use Getopt::Long qw(:config pass_through);\n use Git;\n \n+my @working_tree;\n+my $rc;\n+my $repo = Git->repository();\n+my $repo_path = $repo->repo_path();\n+\n sub usage\n {\n \tmy $exitcode = shift;\n@@ -24,15 +34,202 @@ usage: git difftool [-t|--tool=<tool>]\n                     [-x|--extcmd=<cmd>]\n                     [-g|--gui] [--no-gui]\n                     [--prompt] [-y|--no-prompt]\n+                    [-d|--dir-diff]\n                     ['git diff' options]\n USAGE\n \texit($exitcode);\n }\n \n+sub find_worktree\n+{\n+\t# Git->repository->wc_path() does not honor changes to the working\n+\t# tree location made by $ENV{GIT_WORK_TREE} or the 'core.worktree'\n+\t# config variable.\n+\tmy $worktree;\n+\tmy $env_worktree = $ENV{GIT_WORK_TREE};\n+\tmy $core_worktree = Git::config('core.worktree');\n+\n+\tif (defined($env_worktree) and (length($env_worktree) > 0)) {\n+\t\t$worktree = $env_worktree;\n+\t} elsif (defined($core_worktree) and (length($core_worktree) > 0)) {\n+\t\t$worktree = $core_worktree;\n+\t} else {\n+\t\t$worktree = $repo->wc_path();\n+\t}\n+\n+\treturn $worktree;\n+}\n+\n+my $workdir = find_worktree();\n+\n+sub setup_dir_diff\n+{\n+\t# Run the diff; exit immediately if no diff found\n+\t# 'Repository' and 'WorkingCopy' must be explicitly set to insure that\n+\t# if $GIT_DIR and $GIT_WORK_TREE are set in ENV, they are actually used\n+\t# by Git->repository->command*.\n+\tmy $diffrepo = Git->repository(Repository => $repo_path, WorkingCopy => $workdir);\n+\tmy $diffrtn = $diffrepo->command_oneline('diff', '--raw', '--no-abbrev', '-z', @ARGV);\n+\texit(0) if (length($diffrtn) == 0);\n+\n+\tif ($diffrtn =~ /::/) {\n+\t\tprint \"Combined diff formats ('-c' and '--cc') are not supported in directory diff mode.\\n\";\n+\t\texit(1);\n+\t}\n+\n+\t# Setup temp directories\n+\tmy $tmpdir = tempdir('git-diffall.XXXXX', CLEANUP => 1, TMPDIR => 1);\n+\tmy $ldir = \"$tmpdir/left\";\n+\tmy $rdir = \"$tmpdir/right\";\n+\tmkpath($ldir) or die $!;\n+\tmkpath($rdir) or die $!;\n+\n+\t# Build index info for left and right sides of the diff\n+\tmy $submodule_mode = \"160000\";\n+\tmy $symlink_mode = \"120000\";\n+\tmy $null_mode = \"0\" x 6;\n+\tmy $null_sha1 = \"0\" x 40;\n+\tmy $lindex = \"\";\n+\tmy $rindex = \"\";\n+\tmy %submodule;\n+\tmy %symlink;\n+\tmy @rawdiff = split(':', $diffrtn);\n+\n+\tfor (my $i=1; $i<@rawdiff; $i++) {\n+\t\tmy @fields = split('\\0', $rawdiff[$i]);\n+\t\tmy @stat = split(' ', splice(@fields, 0, 1));\n+\n+\t\tmy ($lmode, $rmode, $lsha1, $rsha1, $status) = @stat;\n+\t\tmy ($src_path, $dst_path) = @fields;\n+\n+\t\tif (not defined($dst_path)) {\n+\t\t\t$dst_path = $src_path;\n+\t\t}\n+\n+\t\tif (($lmode eq $submodule_mode) or ($rmode eq $submodule_mode)) {\n+\t\t\t$submodule{$src_path}{left} = $lsha1;\n+\t\t\tif ($lsha1 ne $rsha1) {\n+\t\t\t\t$submodule{$dst_path}{right} = $rsha1;\n+\t\t\t} else {\n+\t\t\t\t$submodule{$dst_path}{right} = \"$rsha1-dirty\";\n+\t\t\t}\n+\t\t\tnext;\n+\t\t}\n+\n+\t\tif ($lmode eq $symlink_mode) {\n+\t\t\t$symlink{$src_path}{left} = $diffrepo->command_oneline('show', \"$lsha1\");\n+\t\t}\n+\n+\t\tif ($rmode eq $symlink_mode) {\n+\t\t\t$symlink{$dst_path}{right} = $diffrepo->command_oneline('show', \"$rsha1\");\n+\t\t}\n+\n+\t\tif ($lmode ne $null_mode) {\n+\t\t\t$lindex .= \"$lmode $lsha1\\t$src_path\\0\";\n+\t\t}\n+\n+\t\tif ($rmode ne $null_mode) {\n+\t\t\tif ($rsha1 ne $null_sha1) {\n+\t\t\t\t$rindex .= \"$rmode $rsha1\\t$dst_path\\0\";\n+\t\t\t} else {\n+\t\t\t\tpush(@working_tree, $dst_path);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t# If $GIT_DIR is not set prior to calling 'git update-index' and\n+\t# 'git checkout-index', then those commands will fail if difftool\n+\t# is called from a directory other than the repo root.\n+\tmy $must_unset_git_dir = 0;\n+\tif (not defined($ENV{GIT_DIR})) {\n+\t\t$must_unset_git_dir = 1;\n+\t\t$ENV{GIT_DIR} = $repo_path;\n+\t}\n+\n+\t# Populate the left and right directories based on each index file\n+\tmy ($inpipe, $ctx);\n+\t$ENV{GIT_INDEX_FILE} = \"$tmpdir/lindex\";\n+\t($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);\n+\tprint($inpipe $lindex);\n+\t$repo->command_close_pipe($inpipe, $ctx);\n+\t$rc = system('git', 'checkout-index', '--all', \"--prefix=$ldir/\");\n+\texit($rc | ($rc >> 8)) if ($rc != 0);\n+\n+\t$ENV{GIT_INDEX_FILE} = \"$tmpdir/rindex\";\n+\t($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);\n+\tprint($inpipe $rindex);\n+\t$repo->command_close_pipe($inpipe, $ctx);\n+\t$rc = system('git', 'checkout-index', '--all', \"--prefix=$rdir/\");\n+\texit($rc | ($rc >> 8)) if ($rc != 0);\n+\n+\t# If $GIT_DIR was explicitly set just for the update/checkout\n+\t# commands, then it should be unset before continuing.\n+\tdelete($ENV{GIT_DIR}) if ($must_unset_git_dir);\n+\tdelete($ENV{GIT_INDEX_FILE});\n+\n+\t# Changes in the working tree need special treatment since they are\n+\t# not part of the index\n+\tfor my $file (@working_tree) {\n+\t\tmy $dir = dirname($file);\n+\t\tunless (-d \"$rdir/$dir\") {\n+\t\t\tmkpath(\"$rdir/$dir\") or die $!;\n+\t\t}\n+\t\tcopy(\"$workdir/$file\", \"$rdir/$file\") or die $!;\n+\t\tchmod(stat(\"$workdir/$file\")->mode, \"$rdir/$file\") or die $!;\n+\t}\n+\n+\t# Changes to submodules require special treatment. This loop writes a\n+\t# temporary file to both the left and right directories to show the\n+\t# change in the recorded SHA1 for the submodule.\n+\tfor my $path (keys %submodule) {\n+\t\tif (defined($submodule{$path}{left})) {\n+\t\t\twrite_to_file(\"$ldir/$path\", \"Subproject commit $submodule{$path}{left}\");\n+\t\t}\n+\t\tif (defined($submodule{$path}{right})) {\n+\t\t\twrite_to_file(\"$rdir/$path\", \"Subproject commit $submodule{$path}{right}\");\n+\t\t}\n+\t}\n+\n+\t# Symbolic links require special treatment. The standard \"git diff\"\n+\t# shows only the link itself, not the contents of the link target.\n+\t# This loop replicates that behavior.\n+\tfor my $path (keys %symlink) {\n+\t\tif (defined($symlink{$path}{left})) {\n+\t\t\twrite_to_file(\"$ldir/$path\", $symlink{$path}{left});\n+\t\t}\n+\t\tif (defined($symlink{$path}{right})) {\n+\t\t\twrite_to_file(\"$rdir/$path\", $symlink{$path}{right});\n+\t\t}\n+\t}\n+\n+\treturn ($ldir, $rdir);\n+}\n+\n+sub write_to_file\n+{\n+\tmy $path = shift;\n+\tmy $value = shift;\n+\n+\t# Make sure the path to the file exists\n+\tmy $dir = dirname($path);\n+\tunless (-d \"$dir\") {\n+\t\tmkpath(\"$dir\") or die $!;\n+\t}\n+\n+\t# If the file already exists in that location, delete it.  This\n+\t# is required in the case of symbolic links.\n+\tunlink(\"$path\");\n+\n+\topen(my $fh, \">\", \"$path\") or die $!;\n+\tprint($fh $value);\n+\tclose($fh);\n+}\n+\n # parse command-line options. all unrecognized options and arguments\n # are passed through to the 'git diff' command.\n-my ($difftool_cmd, $extcmd, $gui, $help, $prompt);\n+my ($difftool_cmd, $dirdiff, $extcmd, $gui, $help, $prompt);\n GetOptions('g|gui!' => \\$gui,\n+\t'd|dir-diff' => \\$dirdiff,\n \t'h' => \\$help,\n \t'prompt!' => \\$prompt,\n \t'y' => sub { $prompt = 0; },\n@@ -65,22 +262,46 @@ if ($gui) {\n \t\t$ENV{GIT_DIFF_TOOL} = $guitool;\n \t}\n }\n-if (defined($prompt)) {\n-\tif ($prompt) {\n-\t\t$ENV{GIT_DIFFTOOL_PROMPT} = 'true';\n+\n+# In directory diff mode, 'git-difftool--helper' is called once\n+# to compare the a/b directories.  In file diff mode, 'git diff'\n+# will invoke a separate instance of 'git-difftool--helper' for\n+# each file that changed.\n+if (defined($dirdiff)) {\n+\tmy ($a, $b) = setup_dir_diff();\n+\tif (defined($extcmd)) {\n+\t\t$rc = system($extcmd, $a, $b);\n \t} else {\n-\t\t$ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';\n+\t\t$ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';\n+\t\t$rc = system('git', 'difftool--helper', $a, $b);\n \t}\n-}\n \n-$ENV{GIT_PAGER} = '';\n-$ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';\n-my @command = ('git', 'diff', @ARGV);\n-\n-# ActiveState Perl for Win32 does not implement POSIX semantics of\n-# exec* system call. It just spawns the given executable and finishes\n-# the starting program, exiting with code 0.\n-# system will at least catch the errors returned by git diff,\n-# allowing the caller of git difftool better handling of failures.\n-my $rc = system(@command);\n-exit($rc | ($rc >> 8));\n+\texit($rc | ($rc >> 8)) if ($rc != 0);\n+\n+\t# If the diff including working copy files and those\n+\t# files were modified during the diff, then the changes\n+\t# should be copied back to the working tree\n+\tfor my $file (@working_tree) {\n+\t\tcopy(\"$b/$file\", \"$workdir/$file\") or die $!;\n+\t\tchmod(stat(\"$b/$file\")->mode, \"$workdir/$file\") or die $!;\n+\t}\n+} else {\n+\tif (defined($prompt)) {\n+\t\tif ($prompt) {\n+\t\t\t$ENV{GIT_DIFFTOOL_PROMPT} = 'true';\n+\t\t} else {\n+\t\t\t$ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';\n+\t\t}\n+\t}\n+\n+\t$ENV{GIT_PAGER} = '';\n+\t$ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';\n+\n+\t# ActiveState Perl for Win32 does not implement POSIX semantics of\n+\t# exec* system call. It just spawns the given executable and finishes\n+\t# the starting program, exiting with code 0.\n+\t# system will at least catch the errors returned by git diff,\n+\t# allowing the caller of git difftool better handling of failures.\n+\tmy $rc = system('git', 'diff', @ARGV);\n+\texit($rc | ($rc >> 8));\n+}\ndiff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh\nindex e716d06..478c1be 100755\n--- a/t/t7800-difftool.sh\n+++ b/t/t7800-difftool.sh\n@@ -319,4 +319,43 @@ test_expect_success PERL 'say no to the second file' '\n \techo \"$diff\" | stdin_doesnot_contain br2\n '\n \n+test_expect_success PERL 'setup change in subdirectory' '\n+\tgit checkout master &&\n+\tmkdir sub &&\n+\techo master >sub/sub &&\n+\tgit add sub/sub &&\n+\tgit commit -m \"added sub/sub\" &&\n+\techo test >>file &&\n+\techo test >>sub/sub &&\n+\tgit add . &&\n+\tgit commit -m \"modified both\"\n+'\n+\n+test_expect_success PERL 'difftool -d' '\n+\tdiff=$(git difftool -d --extcmd ls branch) &&\n+\techo \"$diff\" | stdin_contains sub &&\n+\techo \"$diff\" | stdin_contains file\n+'\n+\n+test_expect_success PERL 'difftool --dir-diff' '\n+\tdiff=$(git difftool --dir-diff --extcmd ls branch) &&\n+\techo \"$diff\" | stdin_contains sub &&\n+\techo \"$diff\" | stdin_contains file\n+'\n+\n+test_expect_success PERL 'difftool --dir-diff ignores --prompt' '\n+\tdiff=$(git difftool --dir-diff --prompt --extcmd ls branch) &&\n+\techo \"$diff\" | stdin_contains sub &&\n+\techo \"$diff\" | stdin_contains file\n+'\n+\n+test_expect_success PERL 'difftool --dir-diff from subdirectory' '\n+\t(\n+\t\tcd sub &&\n+\t\tdiff=$(git difftool --dir-diff --extcmd ls branch) &&\n+\t\techo \"$diff\" | stdin_contains sub &&\n+\t\techo \"$diff\" | stdin_contains file\n+\t)\n+'\n+\n test_done\n-- \n1.7.10.rc1.26.g5eb68\n"},{"id":"189782","messageId":"xmqqipgum8e9.fsf@junio.mtv.corp.google.com","threadId":"30296","inReplyTo":"1334940988-8940-1-git-send-email-tim.henigan@gmail.com","subject":"Re: [PATCH v14 8/9] difftool: teach difftool to handle directory diffs","fromName":"Junio C Hamano","fromEmail":"jch@google.com","sentAt":"2012-04-20T20:54:22Z","receivedAt":"2012-04-20T20:54:22Z","isPatch":true,"sender":{"key":"jch@google.com","avatar":null},"body":"Tim Henigan <tim.henigan@gmail.com> writes:\n\n> When 'difftool' is called to compare a range of commits that modify\n> more than one file, it opens a separate instance of the diff tool for\n> each file that changed.\n>\n> The new '--dir-diff' option copies all the modified files to a temporary\n> location and runs a directory diff on them in a single instance of the\n> diff tool.\n>\n> Signed-off-by: Tim Henigan <tim.henigan@gmail.com>\n> ---\n>\n> This replaces 7fbdc6c on the th/difftool-diffall branch [1].\n>\n> Changes in v14:\n>\n>   - Fixed use of unitialized vars in the find_worktree function. A warning\n>     was seen when run on msysgit, but not other platforms.\n>\n>   - Added a helper function (write_to_file) to replace repeated logic.\n>\n>   - Fixed handling of symbolic links. The diff shown in the external tool\n>     now matches what is seen in the output of 'git diff'.\n>\n>   - Added support for the diff format used for renames and copies. In the\n>     external tool, these will still appear as delete/add pairs.\n>\n>   - Added logic to detect and abort if a combined diff ('--cc' or '-c')\n>     is commanded at the same time as directory diff ('--dir-diff').\n>\n> [1]: https://github.com/gitster/git/tree/th/difftool-diffall\n>\n>\n>  Documentation/git-difftool.txt |    6 +\n>  git-difftool--helper.sh        |   19 ++-\n>  git-difftool.perl              |  261 +++++++++++++++++++++++++++++++++++++---\n>  t/t7800-difftool.sh            |   39 ++++++\n>  4 files changed, 299 insertions(+), 26 deletions(-)\n>\n> diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt\n> index fe38f66..aba5e76 100644\n> --- a/Documentation/git-difftool.txt\n> +++ b/Documentation/git-difftool.txt\n> @@ -19,6 +19,12 @@ linkgit:git-diff[1].\n>  \n>  OPTIONS\n>  -------\n> +-d::\n> +--dir-diff::\n> +\tCopy the modified files to a temporary location and perform\n> +\ta directory diff on them. This mode never prompts before\n> +\tlaunching the diff tool.\n> +\n>  -y::\n>  --no-prompt::\n>  \tDo not prompt before launching a diff tool.\n> diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh\n> index e6558d1..3d0fe0c 100755\n> --- a/git-difftool--helper.sh\n> +++ b/git-difftool--helper.sh\n> @@ -73,9 +73,16 @@ then\n>  \tfi\n>  fi\n>  \n> -# Launch the merge tool on each path provided by 'git diff'\n> -while test $# -gt 6\n> -do\n> -\tlaunch_merge_tool \"$1\" \"$2\" \"$5\"\n> -\tshift 7\n> -done\n> +if test -n \"$GIT_DIFFTOOL_DIRDIFF\"\n> +then\n> +\tLOCAL=\"$1\"\n> +\tREMOTE=\"$2\"\n> +\trun_merge_tool \"$merge_tool\" false\n> +else\n> +\t# Launch the merge tool on each path provided by 'git diff'\n> +\twhile test $# -gt 6\n> +\tdo\n> +\t\tlaunch_merge_tool \"$1\" \"$2\" \"$5\"\n> +\t\tshift 7\n> +\tdone\n> +fi\n> diff --git a/git-difftool.perl b/git-difftool.perl\n> index aba3d2f..7211f1e 100755\n> --- a/git-difftool.perl\n> +++ b/git-difftool.perl\n> @@ -1,21 +1,31 @@\n> -#!/usr/bin/env perl\n> +#!/usr/bin/perl\n>  # Copyright (c) 2009, 2010 David Aguilar\n> +# Copyright (c) 2012 Tim Henigan\n>  #\n>  # This is a wrapper around the GIT_EXTERNAL_DIFF-compatible\n>  # git-difftool--helper script.\n>  #\n>  # This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.\n> -# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL\n> -# are exported for use by git-difftool--helper.\n> +# The GIT_DIFF* variables are exported for use by git-difftool--helper.\n>  #\n>  # Any arguments that are unknown to this script are forwarded to 'git diff'.\n>  \n>  use 5.008;\n>  use strict;\n>  use warnings;\n> +use File::Basename qw(dirname);\n> +use File::Copy;\n> +use File::stat;\n> +use File::Path qw(mkpath);\n> +use File::Temp qw(tempdir);\n>  use Getopt::Long qw(:config pass_through);\n>  use Git;\n>  \n> +my @working_tree;\n> +my $rc;\n> +my $repo = Git->repository();\n> +my $repo_path = $repo->repo_path();\n> +\n>  sub usage\n>  {\n>  \tmy $exitcode = shift;\n> @@ -24,15 +34,202 @@ usage: git difftool [-t|--tool=<tool>]\n>                      [-x|--extcmd=<cmd>]\n>                      [-g|--gui] [--no-gui]\n>                      [--prompt] [-y|--no-prompt]\n> +                    [-d|--dir-diff]\n>                      ['git diff' options]\n>  USAGE\n>  \texit($exitcode);\n>  }\n>  \n> +sub find_worktree\n> +{\n> +\t# Git->repository->wc_path() does not honor changes to the working\n> +\t# tree location made by $ENV{GIT_WORK_TREE} or the 'core.worktree'\n> +\t# config variable.\n> +\tmy $worktree;\n> +\tmy $env_worktree = $ENV{GIT_WORK_TREE};\n> +\tmy $core_worktree = Git::config('core.worktree');\n> +\n> +\tif (defined($env_worktree) and (length($env_worktree) > 0)) {\n> +\t\t$worktree = $env_worktree;\n> +\t} elsif (defined($core_worktree) and (length($core_worktree) > 0)) {\n> +\t\t$worktree = $core_worktree;\n> +\t} else {\n> +\t\t$worktree = $repo->wc_path();\n> +\t}\n> +\n> +\treturn $worktree;\n> +}\n> +\n> +my $workdir = find_worktree();\n> +\n> +sub setup_dir_diff\n> +{\n> +\t# Run the diff; exit immediately if no diff found\n> +\t# 'Repository' and 'WorkingCopy' must be explicitly set to insure that\n> +\t# if $GIT_DIR and $GIT_WORK_TREE are set in ENV, they are actually used\n> +\t# by Git->repository->command*.\n> +\tmy $diffrepo = Git->repository(Repository => $repo_path, WorkingCopy => $workdir);\n> +\tmy $diffrtn = $diffrepo->command_oneline('diff', '--raw', '--no-abbrev', '-z', @ARGV);\n> +\texit(0) if (length($diffrtn) == 0);\n> +\n> +\tif ($diffrtn =~ /::/) {\n> +\t\tprint \"Combined diff formats ('-c' and '--cc') are not supported in directory diff mode.\\n\";\n\nCan't a pathname happen to have these two-letter sequence?  /^::/ perhaps?\nAlso what does \"command_oneline()\" return, when there is a file with LF\nin their pathname?\n\n> +\t\texit(1);\n> +\t}\n> +\n> +\t# Setup temp directories\n> +\tmy $tmpdir = tempdir('git-diffall.XXXXX', CLEANUP => 1, TMPDIR => 1);\n> +\tmy $ldir = \"$tmpdir/left\";\n> +\tmy $rdir = \"$tmpdir/right\";\n> +\tmkpath($ldir) or die $!;\n> +\tmkpath($rdir) or die $!;\n> +\n> +\t# Build index info for left and right sides of the diff\n> +\tmy $submodule_mode = \"160000\";\n> +\tmy $symlink_mode = \"120000\";\n> +\tmy $null_mode = \"0\" x 6;\n> +\tmy $null_sha1 = \"0\" x 40;\n> +\tmy $lindex = \"\";\n> +\tmy $rindex = \"\";\n> +\tmy %submodule;\n> +\tmy %symlink;\n> +\tmy @rawdiff = split(':', $diffrtn);\n> +\tfor (my $i=1; $i<@rawdiff; $i++) {\n\nHuh?  Can't a pathname happen to have ':' in it?  This really is not the\nway to parse --raw -z format.  Cut them into tokens at '\\0' boundary\nfirst, and decide how many paths there are when interpreting the status\nfield, to collect one record worth of NUL-delimited tokens, and then\nprocess.\n"},{"id":"189817","messageId":"CAJDDKr4AuTHX2LSFuw4g2fWsE3M+A2EcX9bXx6yDRebvAMx_1g@mail.gmail.com","threadId":"30296","inReplyTo":"1334940988-8940-1-git-send-email-tim.henigan@gmail.com","subject":"Re: [PATCH v14 8/9] difftool: teach difftool to handle directory diffs","fromName":"David Aguilar","fromEmail":"davvid@gmail.com","sentAt":"2012-04-21T23:07:36Z","receivedAt":"2012-04-21T23:07:36Z","isPatch":true,"sender":{"key":"davvid@gmail.com","avatar":"https://avatars.githubusercontent.com/u/13196?v=4"},"body":"On Fri, Apr 20, 2012 at 9:56 AM, Tim Henigan <tim.henigan@gmail.com> wrote:\n> [snip]\n> +\n> +sub setup_dir_diff\n> +{\n> +       # Run the diff; exit immediately if no diff found\n> +       # 'Repository' and 'WorkingCopy' must be explicitly set to insure that\n> +       # if $GIT_DIR and $GIT_WORK_TREE are set in ENV, they are actually used\n> +       # by Git->repository->command*.\n> +       my $diffrepo = Git->repository(Repository => $repo_path, WorkingCopy => $workdir);\n> +       my $diffrtn = $diffrepo->command_oneline('diff', '--raw', '--no-abbrev', '-z', @ARGV);\n> +       exit(0) if (length($diffrtn) == 0);\n> +\n> +       if ($diffrtn =~ /::/) {\n> +               print \"Combined diff formats ('-c' and '--cc') are not supported in directory diff mode.\\n\";\n> +               exit(1);\n> +       }\n> +\n> +       # Setup temp directories\n> +       my $tmpdir = tempdir('git-diffall.XXXXX', CLEANUP => 1, TMPDIR => 1);\n> +       my $ldir = \"$tmpdir/left\";\n> +       my $rdir = \"$tmpdir/right\";\n> +       mkpath($ldir) or die $!;\n> +       mkpath($rdir) or die $!;\n> +\n> +       # Build index info for left and right sides of the diff\n> +       my $submodule_mode = \"160000\";\n> +       my $symlink_mode = \"120000\";\n> +       my $null_mode = \"0\" x 6;\n> +       my $null_sha1 = \"0\" x 40;\n> +       my $lindex = \"\";\n> +       my $rindex = \"\";\n\nSuper super tiny nit....\n\nWhen reading Perl I expect to see DQ used only when $variable\ninterpolation is used.  Can we use ' instead of \" for these strings in\nthe re-roll to address Junio's notes?\n-- \nDavid\n"}]}