contrib: add git-diffall script

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>

Tim Henigan committed Feb 24, 2012 at 14:48 UTC 1252bbe1c685450ec76ac750e8b0c2e0b762f93f
2 files changed +292
contrib/diffall/README new
+31
@@ -0,0 +1,31 @@
1 +The git-diffall script provides a directory based diff mechanism
2 +for git.
3 +
4 +To determine what diff viewer is used, the script requires either
5 +the 'diff.tool' or 'merge.tool' configuration option to be set.
6 +
7 +This script is compatible with most common forms used to specify a
8 +range of revisions to diff:
9 +
10 + 1. git diffall: shows diff between working tree and staged changes
11 + 2. git diffall --cached [<commit>]: shows diff between staged
12 + changes and HEAD (or other named commit)
13 + 3. git diffall <commit>: shows diff between working tree and named
14 + commit
15 + 4. git diffall <commit> <commit>: show diff between two named commits
16 + 5. git diffall <commit>..<commit>: same as above
17 + 6. git diffall <commit>...<commit>: show the changes on the branch
18 + containing and up to the second, starting at a common ancestor
19 + of both <commit>
20 +
21 +Note: all forms take an optional path limiter [-- <path>*]
22 +
23 +The '--extcmd=<command>' option allows the user to specify a custom
24 +command for viewing diffs. When given, configured defaults are
25 +ignored and the script runs $command $LOCAL $REMOTE. Additionally,
26 +$BASE is set in the environment.
27 +
28 +This script is based on an example provided by Thomas Rast on the
29 +Git list [1]:
30 +
31 +[1] http://thread.gmane.org/gmane.comp.version-control.git/124807
contrib/diffall/git-diffall new
+261
@@ -0,0 +1,261 @@
1 +#!/bin/sh
2 +# Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com>
3 +#
4 +# Perform a directory diff between commits in the repository using
5 +# the external diff or merge tool specified in the user's config.
6 +
7 +USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*]
8 +
9 + --cached Compare to the index rather than the working tree.
10 +
11 + --copy-back Copy files back to the working tree when the diff
12 + tool exits (in case they were modified by the
13 + user). This option is only valid if the diff
14 + compared with the working tree.
15 +
16 + -x=<command>
17 + --extcmd=<command> Specify a custom command for viewing diffs.
18 + git-diffall ignores the configured defaults and
19 + runs $command $LOCAL $REMOTE when this option is
20 + specified. Additionally, $BASE is set in the
21 + environment.
22 +'
23 +
24 +SUBDIRECTORY_OK=1
25 +. "$(git --exec-path)/git-sh-setup"
26 +
27 +TOOL_MODE=diff
28 +. "$(git --exec-path)/git-mergetool--lib"
29 +
30 +merge_tool="$(get_merge_tool)"
31 +if test -z "$merge_tool"
32 +then
33 + echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set."
34 + usage
35 +fi
36 +
37 +start_dir=$(pwd)
38 +
39 +# needed to access tar utility
40 +cdup=$(git rev-parse --show-cdup) &&
41 +cd "$cdup" || {
42 + echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
43 + exit 1
44 +}
45 +
46 +# mktemp is not available on all platforms (missing from msysgit)
47 +# Use a hard-coded tmp dir if it is not available
48 +tmp="$(mktemp -d -t tmp.XXXXXX 2>/dev/null)" || {
49 + tmp=/tmp/git-diffall-tmp.$$
50 + mkdir "$tmp" || exit 1
51 +}
52 +
53 +trap 'rm -rf "$tmp" 2>/dev/null' EXIT
54 +
55 +left=
56 +right=
57 +paths=
58 +dashdash_seen=
59 +compare_staged=
60 +merge_base=
61 +left_dir=
62 +right_dir=
63 +diff_tool=
64 +copy_back=
65 +
66 +while test $# != 0
67 +do
68 + case "$1" in
69 + -h|--h|--he|--hel|--help)
70 + usage
71 + ;;
72 + --cached)
73 + compare_staged=1
74 + ;;
75 + --copy-back)
76 + copy_back=1
77 + ;;
78 + -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
79 + if test $# = 1
80 + then
81 + echo You must specify the tool for use with --extcmd
82 + usage
83 + else
84 + diff_tool=$2
85 + shift
86 + fi
87 + ;;
88 + --)
89 + dashdash_seen=1
90 + ;;
91 + -*)
92 + echo Invalid option: "$1"
93 + usage
94 + ;;
95 + *)
96 + # could be commit, commit range or path limiter
97 + case "$1" in
98 + *...*)
99 + left=${1%...*}
100 + right=${1#*...}
101 + merge_base=1
102 + ;;
103 + *..*)
104 + left=${1%..*}
105 + right=${1#*..}
106 + ;;
107 + *)
108 + if test -n "$dashdash_seen"
109 + then
110 + paths="$paths$1 "
111 + elif test -z "$left"
112 + then
113 + left=$1
114 + elif test -z "$right"
115 + then
116 + right=$1
117 + else
118 + paths="$paths$1 "
119 + fi
120 + ;;
121 + esac
122 + ;;
123 + esac
124 + shift
125 +done
126 +
127 +# Determine the set of files which changed
128 +if test -n "$left" && test -n "$right"
129 +then
130 + left_dir="cmt-$(git rev-parse --short $left)"
131 + right_dir="cmt-$(git rev-parse --short $right)"
132 +
133 + if test -n "$compare_staged"
134 + then
135 + usage
136 + elif test -n "$merge_base"
137 + then
138 + git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
139 + else
140 + git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
141 + fi
142 +elif test -n "$left"
143 +then
144 + left_dir="cmt-$(git rev-parse --short $left)"
145 +
146 + if test -n "$compare_staged"
147 + then
148 + right_dir="staged"
149 + git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
150 + else
151 + right_dir="working_tree"
152 + git diff --name-only "$left" -- $paths >"$tmp/filelist"
153 + fi
154 +else
155 + left_dir="HEAD"
156 +
157 + if test -n "$compare_staged"
158 + then
159 + right_dir="staged"
160 + git diff --name-only --cached -- $paths >"$tmp/filelist"
161 + else
162 + right_dir="working_tree"
163 + git diff --name-only -- $paths >"$tmp/filelist"
164 + fi
165 +fi
166 +
167 +# Exit immediately if there are no diffs
168 +if test ! -s "$tmp/filelist"
169 +then
170 + exit 0
171 +fi
172 +
173 +if test -n "$copy_back" && test "$right_dir" != "working_tree"
174 +then
175 + echo "--copy-back is only valid when diff includes the working tree."
176 + exit 1
177 +fi
178 +
179 +# Create the named tmp directories that will hold the files to be compared
180 +mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
181 +
182 +# Populate the tmp/right_dir directory with the files to be compared
183 +if test -n "$right"
184 +then
185 + while read name
186 + do
187 + ls_list=$(git ls-tree $right "$name")
188 + if test -n "$ls_list"
189 + then
190 + mkdir -p "$tmp/$right_dir/$(dirname "$name")"
191 + git show "$right":"$name" >"$tmp/$right_dir/$name" || true
192 + fi
193 + done < "$tmp/filelist"
194 +elif test -n "$compare_staged"
195 +then
196 + while read name
197 + do
198 + ls_list=$(git ls-files -- "$name")
199 + if test -n "$ls_list"
200 + then
201 + mkdir -p "$tmp/$right_dir/$(dirname "$name")"
202 + git show :"$name" >"$tmp/$right_dir/$name"
203 + fi
204 + done < "$tmp/filelist"
205 +else
206 + # Mac users have gnutar rather than tar
207 + (tar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && tar -x)) || {
208 + gnutar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && gnutar -x)
209 + }
210 +fi
211 +
212 +# Populate the tmp/left_dir directory with the files to be compared
213 +while read name
214 +do
215 + if test -n "$left"
216 + then
217 + ls_list=$(git ls-tree $left "$name")
218 + if test -n "$ls_list"
219 + then
220 + mkdir -p "$tmp/$left_dir/$(dirname "$name")"
221 + git show "$left":"$name" >"$tmp/$left_dir/$name" || true
222 + fi
223 + else
224 + if test -n "$compare_staged"
225 + then
226 + ls_list=$(git ls-tree HEAD "$name")
227 + if test -n "$ls_list"
228 + then
229 + mkdir -p "$tmp/$left_dir/$(dirname "$name")"
230 + git show HEAD:"$name" >"$tmp/$left_dir/$name"
231 + fi
232 + else
233 + mkdir -p "$tmp/$left_dir/$(dirname "$name")"
234 + git show :"$name" >"$tmp/$left_dir/$name"
235 + fi
236 + fi
237 +done < "$tmp/filelist"
238 +
239 +cd "$tmp"
240 +LOCAL="$left_dir"
241 +REMOTE="$right_dir"
242 +
243 +if test -n "$diff_tool"
244 +then
245 + export BASE
246 + eval $diff_tool '"$LOCAL"' '"$REMOTE"'
247 +else
248 + run_merge_tool "$merge_tool" false
249 +fi
250 +
251 +# Copy files back to the working dir, if requested
252 +if test -n "$copy_back" && test "$right_dir" = "working_tree"
253 +then
254 + cd "$start_dir"
255 + git_top_dir=$(git rev-parse --show-toplevel)
256 + find "$tmp/$right_dir" -type f |
257 + while read file
258 + do
259 + cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
260 + done
261 +fi