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