contrib: add git-diffall script
authorTim Henigan <tim.henigan@gmail.com>
Fri, 24 Feb 2012 19:48:57 +0000 (14:48 -0500)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2012 20:37:10 +0000 (12:37 -0800)
The 'git difftool' allows the user to view diffs using an external tool.
It runs a separate instance of the tool for each file in the diff. This
makes it tedious to review changes spanning multiple files.

The 'git-diffall' script instead prepares temporary directories with the
files to be compared and launches a single instance of the external diff
tool to view them (i.e. a directory diff).

The 'diff.tool' or 'merge.tool' configuration variable is used to specify
which external tool is used.

Signed-off-by: Tim Henigan <tim.henigan@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
contrib/diffall/README [new file with mode: 0644]
contrib/diffall/git-diffall [new file with mode: 0755]

diff --git a/contrib/diffall/README b/contrib/diffall/README
new file mode 100644 (file)
index 0000000..507f17d
--- /dev/null
@@ -0,0 +1,31 @@
+The git-diffall script provides a directory based diff mechanism
+for git.
+
+To determine what diff viewer is used, the script requires either
+the 'diff.tool' or 'merge.tool' configuration option to be set.
+
+This script is compatible with most common forms used to specify a
+range of revisions to diff:
+
+  1. git diffall: shows diff between working tree and staged changes
+  2. git diffall --cached [<commit>]: shows diff between staged
+     changes and HEAD (or other named commit)
+  3. git diffall <commit>: shows diff between working tree and named
+     commit
+  4. git diffall <commit> <commit>: show diff between two named commits
+  5. git diffall <commit>..<commit>: same as above
+  6. git diffall <commit>...<commit>: show the changes on the branch
+     containing and up to the second, starting at a common ancestor
+     of both <commit>
+
+Note: all forms take an optional path limiter [-- <path>*]
+
+The '--extcmd=<command>' option allows the user to specify a custom
+command for viewing diffs.  When given, configured defaults are
+ignored and the script runs $command $LOCAL $REMOTE.  Additionally,
+$BASE is set in the environment.
+
+This script is based on an example provided by Thomas Rast on the
+Git list [1]:
+
+[1] http://thread.gmane.org/gmane.comp.version-control.git/124807
diff --git a/contrib/diffall/git-diffall b/contrib/diffall/git-diffall
new file mode 100755 (executable)
index 0000000..9bbd27f
--- /dev/null
@@ -0,0 +1,261 @@
+#!/bin/sh
+# Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com>
+#
+# Perform a directory diff between commits in the repository using
+# the external diff or merge tool specified in the user's config.
+
+USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*]
+
+    --cached     Compare to the index rather than the working tree.
+
+    --copy-back  Copy files back to the working tree when the diff
+                 tool exits (in case they were modified by the
+                 user).  This option is only valid if the diff
+                 compared with the working tree.
+
+    -x=<command>
+    --extcmd=<command>  Specify a custom command for viewing diffs.
+                 git-diffall ignores the configured defaults and
+                 runs $command $LOCAL $REMOTE when this option is
+                 specified. Additionally, $BASE is set in the
+                 environment.
+'
+
+SUBDIRECTORY_OK=1
+. "$(git --exec-path)/git-sh-setup"
+
+TOOL_MODE=diff
+. "$(git --exec-path)/git-mergetool--lib"
+
+merge_tool="$(get_merge_tool)"
+if test -z "$merge_tool"
+then
+       echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set."
+       usage
+fi
+
+start_dir=$(pwd)
+
+# needed to access tar utility
+cdup=$(git rev-parse --show-cdup) &&
+cd "$cdup" || {
+       echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
+       exit 1
+}
+
+# mktemp is not available on all platforms (missing from msysgit)
+# Use a hard-coded tmp dir if it is not available
+tmp="$(mktemp -d -t tmp.XXXXXX 2>/dev/null)" || {
+       tmp=/tmp/git-diffall-tmp.$$
+       mkdir "$tmp" || exit 1
+}
+
+trap 'rm -rf "$tmp" 2>/dev/null' EXIT
+
+left=
+right=
+paths=
+dashdash_seen=
+compare_staged=
+merge_base=
+left_dir=
+right_dir=
+diff_tool=
+copy_back=
+
+while test $# != 0
+do
+       case "$1" in
+       -h|--h|--he|--hel|--help)
+               usage
+               ;;
+       --cached)
+               compare_staged=1
+               ;;
+       --copy-back)
+               copy_back=1
+               ;;
+       -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
+               if test $# = 1
+               then
+                       echo You must specify the tool for use with --extcmd
+                       usage
+               else
+                       diff_tool=$2
+                       shift
+               fi
+               ;;
+       --)
+               dashdash_seen=1
+               ;;
+       -*)
+               echo Invalid option: "$1"
+               usage
+               ;;
+       *)
+               # could be commit, commit range or path limiter
+               case "$1" in
+               *...*)
+                       left=${1%...*}
+                       right=${1#*...}
+                       merge_base=1
+                       ;;
+               *..*)
+                       left=${1%..*}
+                       right=${1#*..}
+                       ;;
+               *)
+                       if test -n "$dashdash_seen"
+                       then
+                               paths="$paths$1 "
+                       elif test -z "$left"
+                       then
+                               left=$1
+                       elif test -z "$right"
+                       then
+                               right=$1
+                       else
+                               paths="$paths$1 "
+                       fi
+                       ;;
+               esac
+               ;;
+       esac
+       shift
+done
+
+# Determine the set of files which changed
+if test -n "$left" && test -n "$right"
+then
+       left_dir="cmt-$(git rev-parse --short $left)"
+       right_dir="cmt-$(git rev-parse --short $right)"
+
+       if test -n "$compare_staged"
+       then
+               usage
+       elif test -n "$merge_base"
+       then
+               git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
+       else
+               git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
+       fi
+elif test -n "$left"
+then
+       left_dir="cmt-$(git rev-parse --short $left)"
+
+       if test -n "$compare_staged"
+       then
+               right_dir="staged"
+               git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
+       else
+               right_dir="working_tree"
+               git diff --name-only "$left" -- $paths >"$tmp/filelist"
+       fi
+else
+       left_dir="HEAD"
+
+       if test -n "$compare_staged"
+       then
+               right_dir="staged"
+               git diff --name-only --cached -- $paths >"$tmp/filelist"
+       else
+               right_dir="working_tree"
+               git diff --name-only -- $paths >"$tmp/filelist"
+       fi
+fi
+
+# Exit immediately if there are no diffs
+if test ! -s "$tmp/filelist"
+then
+       exit 0
+fi
+
+if test -n "$copy_back" && test "$right_dir" != "working_tree"
+then
+       echo "--copy-back is only valid when diff includes the working tree."
+       exit 1
+fi
+
+# Create the named tmp directories that will hold the files to be compared
+mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
+
+# Populate the tmp/right_dir directory with the files to be compared
+if test -n "$right"
+then
+       while read name
+       do
+               ls_list=$(git ls-tree $right "$name")
+               if test -n "$ls_list"
+               then
+                       mkdir -p "$tmp/$right_dir/$(dirname "$name")"
+                       git show "$right":"$name" >"$tmp/$right_dir/$name" || true
+               fi
+       done < "$tmp/filelist"
+elif test -n "$compare_staged"
+then
+       while read name
+       do
+               ls_list=$(git ls-files -- "$name")
+               if test -n "$ls_list"
+               then
+                       mkdir -p "$tmp/$right_dir/$(dirname "$name")"
+                       git show :"$name" >"$tmp/$right_dir/$name"
+               fi
+       done < "$tmp/filelist"
+else
+       # Mac users have gnutar rather than tar
+       (tar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && tar -x)) || {
+               gnutar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && gnutar -x)
+       }
+fi
+
+# Populate the tmp/left_dir directory with the files to be compared
+while read name
+do
+       if test -n "$left"
+       then
+               ls_list=$(git ls-tree $left "$name")
+               if test -n "$ls_list"
+               then
+                       mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+                       git show "$left":"$name" >"$tmp/$left_dir/$name" || true
+               fi
+       else
+               if test -n "$compare_staged"
+               then
+                       ls_list=$(git ls-tree HEAD "$name")
+                       if test -n "$ls_list"
+                       then
+                               mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+                               git show HEAD:"$name" >"$tmp/$left_dir/$name"
+                       fi
+               else
+                       mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+                       git show :"$name" >"$tmp/$left_dir/$name"
+               fi
+       fi
+done < "$tmp/filelist"
+
+cd "$tmp"
+LOCAL="$left_dir"
+REMOTE="$right_dir"
+
+if test -n "$diff_tool"
+then
+       export BASE
+       eval $diff_tool '"$LOCAL"' '"$REMOTE"'
+else
+       run_merge_tool "$merge_tool" false
+fi
+
+# Copy files back to the working dir, if requested
+if test -n "$copy_back" && test "$right_dir" = "working_tree"
+then
+       cd "$start_dir"
+       git_top_dir=$(git rev-parse --show-toplevel)
+       find "$tmp/$right_dir" -type f |
+       while read file
+       do
+               cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
+       done
+fi