contrib/diffall: eliminate use of tar
[git] / contrib / diffall / git-diffall
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 # 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 }
47
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
53
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=
64
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
125
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)"
131
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)"
144
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"
155
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
165
166 # Exit immediately if there are no diffs
167 if test ! -s "$tmp/filelist"
168 then
169         exit 0
170 fi
171
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
177
178 # Create the named tmp directories that will hold the files to be compared
179 mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
180
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
214
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"
241
242 cd "$tmp"
243 LOCAL="$left_dir"
244 REMOTE="$right_dir"
245
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
253
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