Code

9bbd27f4a5db3352dc97d50e011615650ac0e533
[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 # 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 }
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 }
53 trap 'rm -rf "$tmp" 2>/dev/null' EXIT
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=
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
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)"
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)"
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"
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
167 # Exit immediately if there are no diffs
168 if test ! -s "$tmp/filelist"
169 then
170         exit 0
171 fi
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
179 # Create the named tmp directories that will hold the files to be compared
180 mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
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
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"
239 cd "$tmp"
240 LOCAL="$left_dir"
241 REMOTE="$right_dir"
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
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