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.
7 USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*]
9 --cached Compare to the index rather than the working tree.
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.
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 '
24 SUBDIRECTORY_OK=1
25 . "$(git --exec-path)/git-sh-setup"
27 TOOL_MODE=diff
28 . "$(git --exec-path)/git-mergetool--lib"
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
37 start_dir=$(pwd)
39 # All the file paths returned by the diff command are relative to the root
40 # of the working copy. So if the script is called from a subdirectory, it
41 # must switch to the root of working copy before trying to use those paths.
42 cdup=$(git rev-parse --show-cdup) &&
43 cd "$cdup" || {
44 echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
45 exit 1
46 }
48 # set up temp dir
49 tmp=$(perl -e 'use File::Temp qw(tempdir);
50 $t=tempdir("/tmp/git-diffall.XXXXX") or exit(1);
51 print $t') || exit 1
52 trap 'rm -rf "$tmp" 2>/dev/null' EXIT
54 left=
55 right=
56 paths=
57 dashdash_seen=
58 compare_staged=
59 merge_base=
60 left_dir=
61 right_dir=
62 diff_tool=
63 copy_back=
65 while test $# != 0
66 do
67 case "$1" in
68 -h|--h|--he|--hel|--help)
69 usage
70 ;;
71 --cached)
72 compare_staged=1
73 ;;
74 --copy-back)
75 copy_back=1
76 ;;
77 -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
78 if test $# = 1
79 then
80 echo You must specify the tool for use with --extcmd
81 usage
82 else
83 diff_tool=$2
84 shift
85 fi
86 ;;
87 --)
88 dashdash_seen=1
89 ;;
90 -*)
91 echo Invalid option: "$1"
92 usage
93 ;;
94 *)
95 # could be commit, commit range or path limiter
96 case "$1" in
97 *...*)
98 left=${1%...*}
99 right=${1#*...}
100 merge_base=1
101 ;;
102 *..*)
103 left=${1%..*}
104 right=${1#*..}
105 ;;
106 *)
107 if test -n "$dashdash_seen"
108 then
109 paths="$paths$1 "
110 elif test -z "$left"
111 then
112 left=$1
113 elif test -z "$right"
114 then
115 right=$1
116 else
117 paths="$paths$1 "
118 fi
119 ;;
120 esac
121 ;;
122 esac
123 shift
124 done
126 # Determine the set of files which changed
127 if test -n "$left" && test -n "$right"
128 then
129 left_dir="cmt-$(git rev-parse --short $left)"
130 right_dir="cmt-$(git rev-parse --short $right)"
132 if test -n "$compare_staged"
133 then
134 usage
135 elif test -n "$merge_base"
136 then
137 git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
138 else
139 git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
140 fi
141 elif test -n "$left"
142 then
143 left_dir="cmt-$(git rev-parse --short $left)"
145 if test -n "$compare_staged"
146 then
147 right_dir="staged"
148 git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
149 else
150 right_dir="working_tree"
151 git diff --name-only "$left" -- $paths >"$tmp/filelist"
152 fi
153 else
154 left_dir="HEAD"
156 if test -n "$compare_staged"
157 then
158 right_dir="staged"
159 git diff --name-only --cached -- $paths >"$tmp/filelist"
160 else
161 right_dir="working_tree"
162 git diff --name-only -- $paths >"$tmp/filelist"
163 fi
164 fi
166 # Exit immediately if there are no diffs
167 if test ! -s "$tmp/filelist"
168 then
169 exit 0
170 fi
172 if test -n "$copy_back" && test "$right_dir" != "working_tree"
173 then
174 echo "--copy-back is only valid when diff includes the working tree."
175 exit 1
176 fi
178 # Create the named tmp directories that will hold the files to be compared
179 mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
181 # Populate the tmp/right_dir directory with the files to be compared
182 if test -n "$right"
183 then
184 while read name
185 do
186 ls_list=$(git ls-tree $right "$name")
187 if test -n "$ls_list"
188 then
189 mkdir -p "$tmp/$right_dir/$(dirname "$name")"
190 git show "$right":"$name" >"$tmp/$right_dir/$name" || true
191 fi
192 done < "$tmp/filelist"
193 elif test -n "$compare_staged"
194 then
195 while read name
196 do
197 ls_list=$(git ls-files -- "$name")
198 if test -n "$ls_list"
199 then
200 mkdir -p "$tmp/$right_dir/$(dirname "$name")"
201 git show :"$name" >"$tmp/$right_dir/$name"
202 fi
203 done < "$tmp/filelist"
204 else
205 while read name
206 do
207 if test -e "$name"
208 then
209 mkdir -p "$tmp/$right_dir/$(dirname "$name")"
210 cp "$name" "$tmp/$right_dir/$name"
211 fi
212 done < "$tmp/filelist"
213 fi
215 # Populate the tmp/left_dir directory with the files to be compared
216 while read name
217 do
218 if test -n "$left"
219 then
220 ls_list=$(git ls-tree $left "$name")
221 if test -n "$ls_list"
222 then
223 mkdir -p "$tmp/$left_dir/$(dirname "$name")"
224 git show "$left":"$name" >"$tmp/$left_dir/$name" || true
225 fi
226 else
227 if test -n "$compare_staged"
228 then
229 ls_list=$(git ls-tree HEAD "$name")
230 if test -n "$ls_list"
231 then
232 mkdir -p "$tmp/$left_dir/$(dirname "$name")"
233 git show HEAD:"$name" >"$tmp/$left_dir/$name"
234 fi
235 else
236 mkdir -p "$tmp/$left_dir/$(dirname "$name")"
237 git show :"$name" >"$tmp/$left_dir/$name"
238 fi
239 fi
240 done < "$tmp/filelist"
242 cd "$tmp"
243 LOCAL="$left_dir"
244 REMOTE="$right_dir"
246 if test -n "$diff_tool"
247 then
248 export BASE
249 eval $diff_tool '"$LOCAL"' '"$REMOTE"'
250 else
251 run_merge_tool "$merge_tool" false
252 fi
254 # Copy files back to the working dir, if requested
255 if test -n "$copy_back" && test "$right_dir" = "working_tree"
256 then
257 cd "$start_dir"
258 git_top_dir=$(git rev-parse --show-toplevel)
259 find "$tmp/$right_dir" -type f |
260 while read file
261 do
262 cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
263 done
264 fi