git instaweb: enable remote_heads
[git] / git-mergetool.sh
1 #!/bin/sh
2 #
3 # This program resolves merge conflicts in git
4 #
5 # Copyright (c) 2006 Theodore Y. Ts'o
6 #
7 # This file is licensed under the GPL v2, or a later version
8 # at the discretion of Junio C Hamano.
9 #
10
11 USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...'
12 SUBDIRECTORY_OK=Yes
13 OPTIONS_SPEC=
14 TOOL_MODE=merge
15 . git-sh-setup
16 . git-mergetool--lib
17 require_work_tree
18
19 # Returns true if the mode reflects a symlink
20 is_symlink () {
21     test "$1" = 120000
22 }
23
24 local_present () {
25     test -n "$local_mode"
26 }
27
28 remote_present () {
29     test -n "$remote_mode"
30 }
31
32 base_present () {
33     test -n "$base_mode"
34 }
35
36 cleanup_temp_files () {
37     if test "$1" = --save-backup ; then
38         mv -- "$BACKUP" "$MERGED.orig"
39         rm -f -- "$LOCAL" "$REMOTE" "$BASE"
40     else
41         rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
42     fi
43 }
44
45 describe_file () {
46     mode="$1"
47     branch="$2"
48     file="$3"
49
50     printf "  {%s}: " "$branch"
51     if test -z "$mode"; then
52         echo "deleted"
53     elif is_symlink "$mode" ; then
54         echo "a symbolic link -> '$(cat "$file")'"
55     else
56         if base_present; then
57             echo "modified"
58         else
59             echo "created"
60         fi
61     fi
62 }
63
64
65 resolve_symlink_merge () {
66     while true; do
67         printf "Use (l)ocal or (r)emote, or (a)bort? "
68         read ans
69         case "$ans" in
70             [lL]*)
71                 git checkout-index -f --stage=2 -- "$MERGED"
72                 git add -- "$MERGED"
73                 cleanup_temp_files --save-backup
74                 return 0
75                 ;;
76             [rR]*)
77                 git checkout-index -f --stage=3 -- "$MERGED"
78                 git add -- "$MERGED"
79                 cleanup_temp_files --save-backup
80                 return 0
81                 ;;
82             [aA]*)
83                 return 1
84                 ;;
85             esac
86         done
87 }
88
89 resolve_deleted_merge () {
90     while true; do
91         if base_present; then
92             printf "Use (m)odified or (d)eleted file, or (a)bort? "
93         else
94             printf "Use (c)reated or (d)eleted file, or (a)bort? "
95         fi
96         read ans
97         case "$ans" in
98             [mMcC]*)
99                 git add -- "$MERGED"
100                 cleanup_temp_files --save-backup
101                 return 0
102                 ;;
103             [dD]*)
104                 git rm -- "$MERGED" > /dev/null
105                 cleanup_temp_files
106                 return 0
107                 ;;
108             [aA]*)
109                 return 1
110                 ;;
111             esac
112         done
113 }
114
115 checkout_staged_file () {
116     tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^     ]*\)    ')
117
118     if test $? -eq 0 -a -n "$tmpfile" ; then
119         mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
120     fi
121 }
122
123 merge_file () {
124     MERGED="$1"
125
126     f=$(git ls-files -u -- "$MERGED")
127     if test -z "$f" ; then
128         if test ! -f "$MERGED" ; then
129             echo "$MERGED: file not found"
130         else
131             echo "$MERGED: file does not need merging"
132         fi
133         return 1
134     fi
135
136     ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
137     BACKUP="./$MERGED.BACKUP.$ext"
138     LOCAL="./$MERGED.LOCAL.$ext"
139     REMOTE="./$MERGED.REMOTE.$ext"
140     BASE="./$MERGED.BASE.$ext"
141
142     mv -- "$MERGED" "$BACKUP"
143     cp -- "$BACKUP" "$MERGED"
144
145     base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
146     local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
147     remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
148
149     base_present   && checkout_staged_file 1 "$MERGED" "$BASE"
150     local_present  && checkout_staged_file 2 "$MERGED" "$LOCAL"
151     remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
152
153     if test -z "$local_mode" -o -z "$remote_mode"; then
154         echo "Deleted merge conflict for '$MERGED':"
155         describe_file "$local_mode" "local" "$LOCAL"
156         describe_file "$remote_mode" "remote" "$REMOTE"
157         resolve_deleted_merge
158         return
159     fi
160
161     if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
162         echo "Symbolic link merge conflict for '$MERGED':"
163         describe_file "$local_mode" "local" "$LOCAL"
164         describe_file "$remote_mode" "remote" "$REMOTE"
165         resolve_symlink_merge
166         return
167     fi
168
169     echo "Normal merge conflict for '$MERGED':"
170     describe_file "$local_mode" "local" "$LOCAL"
171     describe_file "$remote_mode" "remote" "$REMOTE"
172     if "$prompt" = true; then
173         printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
174         read ans
175     fi
176
177     if base_present; then
178             present=true
179     else
180             present=false
181     fi
182
183     if ! run_merge_tool "$merge_tool" "$present"; then
184         echo "merge of $MERGED failed" 1>&2
185         mv -- "$BACKUP" "$MERGED"
186
187         if test "$merge_keep_temporaries" = "false"; then
188             cleanup_temp_files
189         fi
190
191         return 1
192     fi
193
194     if test "$merge_keep_backup" = "true"; then
195         mv -- "$BACKUP" "$MERGED.orig"
196     else
197         rm -- "$BACKUP"
198     fi
199
200     git add -- "$MERGED"
201     cleanup_temp_files
202     return 0
203 }
204
205 prompt=$(git config --bool mergetool.prompt || echo true)
206
207 while test $# != 0
208 do
209     case "$1" in
210         -t|--tool*)
211             case "$#,$1" in
212                 *,*=*)
213                     merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
214                     ;;
215                 1,*)
216                     usage ;;
217                 *)
218                     merge_tool="$2"
219                     shift ;;
220             esac
221             ;;
222         -y|--no-prompt)
223             prompt=false
224             ;;
225         --prompt)
226             prompt=true
227             ;;
228         --)
229             shift
230             break
231             ;;
232         -*)
233             usage
234             ;;
235         *)
236             break
237             ;;
238     esac
239     shift
240 done
241
242 prompt_after_failed_merge() {
243     while true; do
244         printf "Continue merging other unresolved paths (y/n) ? "
245         read ans
246         case "$ans" in
247
248             [yY]*)
249                 return 0
250                 ;;
251
252             [nN]*)
253                 return 1
254                 ;;
255         esac
256     done
257 }
258
259 if test -z "$merge_tool"; then
260     merge_tool=$(get_merge_tool "$merge_tool") || exit
261 fi
262 merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
263 merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
264
265 last_status=0
266 rollup_status=0
267 rerere=false
268
269 files_to_merge() {
270     if test "$rerere" = true
271     then
272         git rerere status
273     else
274         git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u
275     fi
276 }
277
278
279 if test $# -eq 0 ; then
280     cd_to_toplevel
281
282     if test -e "$GIT_DIR/MERGE_RR"
283     then
284         rerere=true
285     fi
286
287     files=$(files_to_merge)
288     if test -z "$files" ; then
289         echo "No files need merging"
290         exit 0
291     fi
292
293     # Save original stdin
294     exec 3<&0
295
296     printf "Merging:\n"
297     printf "$files\n"
298
299     files_to_merge |
300     while IFS= read i
301     do
302         if test $last_status -ne 0; then
303             prompt_after_failed_merge <&3 || exit 1
304         fi
305         printf "\n"
306         merge_file "$i" <&3
307         last_status=$?
308         if test $last_status -ne 0; then
309             rollup_status=1
310         fi
311     done
312 else
313     while test $# -gt 0; do
314         if test $last_status -ne 0; then
315             prompt_after_failed_merge || exit 1
316         fi
317         printf "\n"
318         merge_file "$1"
319         last_status=$?
320         if test $last_status -ne 0; then
321             rollup_status=1
322         fi
323         shift
324     done
325 fi
326
327 exit $rollup_status