Code

contrib/diffall: comment actual reason for 'cdup'
[git.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.
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 # mktemp is not available on all platforms (missing from msysgit)
49 # Use a hard-coded tmp dir if it is not available
50 tmp="$(mktemp -d -t tmp.XXXXXX 2>/dev/null)" || {
51         tmp=/tmp/git-diffall-tmp.$$
52         mkdir "$tmp" || exit 1
53 }
55 trap 'rm -rf "$tmp" 2>/dev/null' EXIT
57 left=
58 right=
59 paths=
60 dashdash_seen=
61 compare_staged=
62 merge_base=
63 left_dir=
64 right_dir=
65 diff_tool=
66 copy_back=
68 while test $# != 0
69 do
70         case "$1" in
71         -h|--h|--he|--hel|--help)
72                 usage
73                 ;;
74         --cached)
75                 compare_staged=1
76                 ;;
77         --copy-back)
78                 copy_back=1
79                 ;;
80         -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
81                 if test $# = 1
82                 then
83                         echo You must specify the tool for use with --extcmd
84                         usage
85                 else
86                         diff_tool=$2
87                         shift
88                 fi
89                 ;;
90         --)
91                 dashdash_seen=1
92                 ;;
93         -*)
94                 echo Invalid option: "$1"
95                 usage
96                 ;;
97         *)
98                 # could be commit, commit range or path limiter
99                 case "$1" in
100                 *...*)
101                         left=${1%...*}
102                         right=${1#*...}
103                         merge_base=1
104                         ;;
105                 *..*)
106                         left=${1%..*}
107                         right=${1#*..}
108                         ;;
109                 *)
110                         if test -n "$dashdash_seen"
111                         then
112                                 paths="$paths$1 "
113                         elif test -z "$left"
114                         then
115                                 left=$1
116                         elif test -z "$right"
117                         then
118                                 right=$1
119                         else
120                                 paths="$paths$1 "
121                         fi
122                         ;;
123                 esac
124                 ;;
125         esac
126         shift
127 done
129 # Determine the set of files which changed
130 if test -n "$left" && test -n "$right"
131 then
132         left_dir="cmt-$(git rev-parse --short $left)"
133         right_dir="cmt-$(git rev-parse --short $right)"
135         if test -n "$compare_staged"
136         then
137                 usage
138         elif test -n "$merge_base"
139         then
140                 git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
141         else
142                 git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
143         fi
144 elif test -n "$left"
145 then
146         left_dir="cmt-$(git rev-parse --short $left)"
148         if test -n "$compare_staged"
149         then
150                 right_dir="staged"
151                 git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
152         else
153                 right_dir="working_tree"
154                 git diff --name-only "$left" -- $paths >"$tmp/filelist"
155         fi
156 else
157         left_dir="HEAD"
159         if test -n "$compare_staged"
160         then
161                 right_dir="staged"
162                 git diff --name-only --cached -- $paths >"$tmp/filelist"
163         else
164                 right_dir="working_tree"
165                 git diff --name-only -- $paths >"$tmp/filelist"
166         fi
167 fi
169 # Exit immediately if there are no diffs
170 if test ! -s "$tmp/filelist"
171 then
172         exit 0
173 fi
175 if test -n "$copy_back" && test "$right_dir" != "working_tree"
176 then
177         echo "--copy-back is only valid when diff includes the working tree."
178         exit 1
179 fi
181 # Create the named tmp directories that will hold the files to be compared
182 mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
184 # Populate the tmp/right_dir directory with the files to be compared
185 if test -n "$right"
186 then
187         while read name
188         do
189                 ls_list=$(git ls-tree $right "$name")
190                 if test -n "$ls_list"
191                 then
192                         mkdir -p "$tmp/$right_dir/$(dirname "$name")"
193                         git show "$right":"$name" >"$tmp/$right_dir/$name" || true
194                 fi
195         done < "$tmp/filelist"
196 elif test -n "$compare_staged"
197 then
198         while read name
199         do
200                 ls_list=$(git ls-files -- "$name")
201                 if test -n "$ls_list"
202                 then
203                         mkdir -p "$tmp/$right_dir/$(dirname "$name")"
204                         git show :"$name" >"$tmp/$right_dir/$name"
205                 fi
206         done < "$tmp/filelist"
207 else
208         # Mac users have gnutar rather than tar
209         (tar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && tar -x)) || {
210                 gnutar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && gnutar -x)
211         }
212 fi
214 # Populate the tmp/left_dir directory with the files to be compared
215 while read name
216 do
217         if test -n "$left"
218         then
219                 ls_list=$(git ls-tree $left "$name")
220                 if test -n "$ls_list"
221                 then
222                         mkdir -p "$tmp/$left_dir/$(dirname "$name")"
223                         git show "$left":"$name" >"$tmp/$left_dir/$name" || true
224                 fi
225         else
226                 if test -n "$compare_staged"
227                 then
228                         ls_list=$(git ls-tree HEAD "$name")
229                         if test -n "$ls_list"
230                         then
231                                 mkdir -p "$tmp/$left_dir/$(dirname "$name")"
232                                 git show HEAD:"$name" >"$tmp/$left_dir/$name"
233                         fi
234                 else
235                         mkdir -p "$tmp/$left_dir/$(dirname "$name")"
236                         git show :"$name" >"$tmp/$left_dir/$name"
237                 fi
238         fi
239 done < "$tmp/filelist"
241 cd "$tmp"
242 LOCAL="$left_dir"
243 REMOTE="$right_dir"
245 if test -n "$diff_tool"
246 then
247         export BASE
248         eval $diff_tool '"$LOCAL"' '"$REMOTE"'
249 else
250         run_merge_tool "$merge_tool" false
251 fi
253 # Copy files back to the working dir, if requested
254 if test -n "$copy_back" && test "$right_dir" = "working_tree"
255 then
256         cd "$start_dir"
257         git_top_dir=$(git rev-parse --show-toplevel)
258         find "$tmp/$right_dir" -type f |
259         while read file
260         do
261                 cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
262         done
263 fi