Merge branch 'sb/sha1-loose-object-info-check-existence'
[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"' 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 while read name
183 do
184         if test -n "$right"
185         then
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         elif test -n "$compare_staged"
193         then
194                 ls_list=$(git ls-files -- "$name")
195                 if test -n "$ls_list"
196                 then
197                         mkdir -p "$tmp/$right_dir/$(dirname "$name")"
198                         git show :"$name" >"$tmp/$right_dir/$name"
199                 fi
200         else
201                 if test -e "$name"
202                 then
203                         mkdir -p "$tmp/$right_dir/$(dirname "$name")"
204                         cp "$name" "$tmp/$right_dir/$name"
205                 fi
206         fi
207 done < "$tmp/filelist"
208
209 # Populate the tmp/left_dir directory with the files to be compared
210 while read name
211 do
212         if test -n "$left"
213         then
214                 ls_list=$(git ls-tree $left "$name")
215                 if test -n "$ls_list"
216                 then
217                         mkdir -p "$tmp/$left_dir/$(dirname "$name")"
218                         git show "$left":"$name" >"$tmp/$left_dir/$name" || true
219                 fi
220         else
221                 if test -n "$compare_staged"
222                 then
223                         ls_list=$(git ls-tree HEAD "$name")
224                         if test -n "$ls_list"
225                         then
226                                 mkdir -p "$tmp/$left_dir/$(dirname "$name")"
227                                 git show HEAD:"$name" >"$tmp/$left_dir/$name"
228                         fi
229                 else
230                         mkdir -p "$tmp/$left_dir/$(dirname "$name")"
231                         git show :"$name" >"$tmp/$left_dir/$name"
232                 fi
233         fi
234 done < "$tmp/filelist"
235
236 LOCAL="$tmp/$left_dir"
237 REMOTE="$tmp/$right_dir"
238
239 if test -n "$diff_tool"
240 then
241         export BASE
242         eval $diff_tool '"$LOCAL"' '"$REMOTE"'
243 else
244         run_merge_tool "$merge_tool" false
245 fi
246
247 # Copy files back to the working dir, if requested
248 if test -n "$copy_back" && test "$right_dir" = "working_tree"
249 then
250         cd "$start_dir"
251         git_top_dir=$(git rev-parse --show-toplevel)
252         find "$tmp/$right_dir" -type f |
253         while read file
254         do
255                 cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
256         done
257 fi