blob: 3aab5aae844b963fc4178675a56da20e02488dfd [file] [log] [blame]
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -05001#!/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
Josh Triplett2571ac62007-06-05 21:24:19 -07008# at the discretion of Junio C Hamano.
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -05009#
10
Charles Bailey682b4512008-11-13 12:41:14 +000011USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...'
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050012SUBDIRECTORY_OK=Yes
Junio C Hamano8f321a32007-11-06 01:50:02 -080013OPTIONS_SPEC=
David Aguilar21d0ba72009-04-08 00:17:20 -070014TOOL_MODE=merge
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050015. git-sh-setup
David Aguilar21d0ba72009-04-08 00:17:20 -070016. git-mergetool--lib
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050017require_work_tree
18
19# Returns true if the mode reflects a symlink
Theodore Ts'o262c9812007-03-29 06:55:11 -040020is_symlink () {
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050021 test "$1" = 120000
22}
23
Jonathon Mahff7f0892011-04-13 03:00:48 -070024is_submodule () {
25 test "$1" = 160000
26}
27
Theodore Ts'o262c9812007-03-29 06:55:11 -040028local_present () {
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050029 test -n "$local_mode"
30}
31
Theodore Ts'o262c9812007-03-29 06:55:11 -040032remote_present () {
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050033 test -n "$remote_mode"
34}
35
Theodore Ts'o262c9812007-03-29 06:55:11 -040036base_present () {
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050037 test -n "$base_mode"
38}
39
40cleanup_temp_files () {
41 if test "$1" = --save-backup ; then
Jonathon Mahff7f0892011-04-13 03:00:48 -070042 rm -rf -- "$MERGED.orig"
43 test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050044 rm -f -- "$LOCAL" "$REMOTE" "$BASE"
45 else
46 rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
47 fi
48}
49
Theodore Ts'o262c9812007-03-29 06:55:11 -040050describe_file () {
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050051 mode="$1"
52 branch="$2"
53 file="$3"
54
Theodore Ts'o27090aa2007-03-29 11:39:46 -040055 printf " {%s}: " "$branch"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050056 if test -z "$mode"; then
Theodore Ts'o27090aa2007-03-29 11:39:46 -040057 echo "deleted"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050058 elif is_symlink "$mode" ; then
Theodore Ts'o27090aa2007-03-29 11:39:46 -040059 echo "a symbolic link -> '$(cat "$file")'"
Jonathon Mahff7f0892011-04-13 03:00:48 -070060 elif is_submodule "$mode" ; then
61 echo "submodule commit $file"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050062 else
63 if base_present; then
Jonathon Mahff7f0892011-04-13 03:00:48 -070064 echo "modified file"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050065 else
Jonathon Mahff7f0892011-04-13 03:00:48 -070066 echo "created file"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050067 fi
68 fi
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050069}
70
71
72resolve_symlink_merge () {
Theodore Ts'od1dc6952007-03-29 06:46:59 -040073 while true; do
Theodore Ts'o27090aa2007-03-29 11:39:46 -040074 printf "Use (l)ocal or (r)emote, or (a)bort? "
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050075 read ans
76 case "$ans" in
77 [lL]*)
Charles Baileyb3ea27e2008-02-21 23:30:34 +000078 git checkout-index -f --stage=2 -- "$MERGED"
79 git add -- "$MERGED"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050080 cleanup_temp_files --save-backup
Charles Baileyb0169d82008-12-12 21:48:40 +000081 return 0
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050082 ;;
Theodore Ts'o5a174f12007-03-29 09:48:31 -040083 [rR]*)
Charles Baileyb3ea27e2008-02-21 23:30:34 +000084 git checkout-index -f --stage=3 -- "$MERGED"
85 git add -- "$MERGED"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050086 cleanup_temp_files --save-backup
Charles Baileyb0169d82008-12-12 21:48:40 +000087 return 0
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050088 ;;
Theodore Ts'o5a174f12007-03-29 09:48:31 -040089 [aA]*)
Charles Baileyb0169d82008-12-12 21:48:40 +000090 return 1
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -050091 ;;
92 esac
93 done
94}
95
96resolve_deleted_merge () {
Theodore Ts'od1dc6952007-03-29 06:46:59 -040097 while true; do
Theodore Ts'o27090aa2007-03-29 11:39:46 -040098 if base_present; then
99 printf "Use (m)odified or (d)eleted file, or (a)bort? "
100 else
101 printf "Use (c)reated or (d)eleted file, or (a)bort? "
102 fi
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500103 read ans
104 case "$ans" in
Theodore Ts'o27090aa2007-03-29 11:39:46 -0400105 [mMcC]*)
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000106 git add -- "$MERGED"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500107 cleanup_temp_files --save-backup
Charles Baileyb0169d82008-12-12 21:48:40 +0000108 return 0
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500109 ;;
Theodore Ts'o5a174f12007-03-29 09:48:31 -0400110 [dD]*)
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000111 git rm -- "$MERGED" > /dev/null
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500112 cleanup_temp_files
Charles Baileyb0169d82008-12-12 21:48:40 +0000113 return 0
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500114 ;;
Theodore Ts'o5a174f12007-03-29 09:48:31 -0400115 [aA]*)
Charles Baileyb0169d82008-12-12 21:48:40 +0000116 return 1
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500117 ;;
118 esac
119 done
120}
121
Jonathon Mahff7f0892011-04-13 03:00:48 -0700122resolve_submodule_merge () {
123 while true; do
124 printf "Use (l)ocal or (r)emote, or (a)bort? "
125 read ans
126 case "$ans" in
127 [lL]*)
128 if ! local_present; then
129 if test -n "$(git ls-tree HEAD -- "$MERGED")"; then
130 # Local isn't present, but it's a subdirectory
131 git ls-tree --full-name -r HEAD -- "$MERGED" | git update-index --index-info || exit $?
132 else
133 test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
134 git update-index --force-remove "$MERGED"
135 cleanup_temp_files --save-backup
136 fi
137 elif is_submodule "$local_mode"; then
138 stage_submodule "$MERGED" "$local_sha1"
139 else
140 git checkout-index -f --stage=2 -- "$MERGED"
141 git add -- "$MERGED"
142 fi
143 return 0
144 ;;
145 [rR]*)
146 if ! remote_present; then
147 if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"; then
148 # Remote isn't present, but it's a subdirectory
149 git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" | git update-index --index-info || exit $?
150 else
151 test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
152 git update-index --force-remove "$MERGED"
153 fi
154 elif is_submodule "$remote_mode"; then
155 ! is_submodule "$local_mode" && test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
156 stage_submodule "$MERGED" "$remote_sha1"
157 else
158 test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
159 git checkout-index -f --stage=3 -- "$MERGED"
160 git add -- "$MERGED"
161 fi
162 cleanup_temp_files --save-backup
163 return 0
164 ;;
165 [aA]*)
166 return 1
167 ;;
168 esac
169 done
170}
171
172stage_submodule () {
173 path="$1"
174 submodule_sha1="$2"
175 mkdir -p "$path" || die "fatal: unable to create directory for module at $path"
176 # Find $path relative to work tree
177 work_tree_root=$(cd_to_toplevel && pwd)
178 work_rel_path=$(cd "$path" && GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix)
179 test -n "$work_rel_path" || die "fatal: unable to get path of module $path relative to work tree"
180 git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
181}
182
Charles Bailey0ec7b6c2009-01-21 22:57:48 +0000183checkout_staged_file () {
184 tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ')
185
186 if test $? -eq 0 -a -n "$tmpfile" ; then
Charles Baileyff4a1852009-01-30 23:20:11 +0000187 mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
Charles Bailey0ec7b6c2009-01-21 22:57:48 +0000188 fi
189}
190
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500191merge_file () {
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000192 MERGED="$1"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500193
David Aguilar9a62d722009-04-06 01:31:28 -0700194 f=$(git ls-files -u -- "$MERGED")
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500195 if test -z "$f" ; then
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000196 if test ! -f "$MERGED" ; then
197 echo "$MERGED: file not found"
Theodore Ts'oce5b6d72007-03-27 18:00:03 -0400198 else
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000199 echo "$MERGED: file does not need merging"
Theodore Ts'oce5b6d72007-03-27 18:00:03 -0400200 fi
Charles Baileyb0169d82008-12-12 21:48:40 +0000201 return 1
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500202 fi
203
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000204 ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
Patrick Higgins641dba42008-06-16 17:33:41 -0600205 BACKUP="./$MERGED.BACKUP.$ext"
206 LOCAL="./$MERGED.LOCAL.$ext"
207 REMOTE="./$MERGED.REMOTE.$ext"
208 BASE="./$MERGED.BASE.$ext"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500209
David Aguilar9a62d722009-04-06 01:31:28 -0700210 base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
211 local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
212 remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500213
Jonathon Mahff7f0892011-04-13 03:00:48 -0700214 if is_submodule "$local_mode" || is_submodule "$remote_mode"; then
215 echo "Submodule merge conflict for '$MERGED':"
216 local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}')
217 remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}')
218 describe_file "$local_mode" "local" "$local_sha1"
219 describe_file "$remote_mode" "remote" "$remote_sha1"
220 resolve_submodule_merge
221 return
222 fi
223
224 mv -- "$MERGED" "$BACKUP"
225 cp -- "$BACKUP" "$MERGED"
226
Charles Baileyff4a1852009-01-30 23:20:11 +0000227 base_present && checkout_staged_file 1 "$MERGED" "$BASE"
228 local_present && checkout_staged_file 2 "$MERGED" "$LOCAL"
229 remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500230
231 if test -z "$local_mode" -o -z "$remote_mode"; then
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000232 echo "Deleted merge conflict for '$MERGED':"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500233 describe_file "$local_mode" "local" "$LOCAL"
234 describe_file "$remote_mode" "remote" "$REMOTE"
235 resolve_deleted_merge
236 return
237 fi
238
239 if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000240 echo "Symbolic link merge conflict for '$MERGED':"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500241 describe_file "$local_mode" "local" "$LOCAL"
242 describe_file "$remote_mode" "remote" "$REMOTE"
243 resolve_symlink_merge
244 return
245 fi
246
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000247 echo "Normal merge conflict for '$MERGED':"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500248 describe_file "$local_mode" "local" "$LOCAL"
249 describe_file "$remote_mode" "remote" "$REMOTE"
Charles Bailey682b4512008-11-13 12:41:14 +0000250 if "$prompt" = true; then
251 printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
252 read ans
253 fi
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500254
David Aguilar47d65922009-04-11 20:41:56 -0700255 if base_present; then
256 present=true
257 else
258 present=false
259 fi
David Aguilar21d0ba72009-04-08 00:17:20 -0700260
261 if ! run_merge_tool "$merge_tool" "$present"; then
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000262 echo "merge of $MERGED failed" 1>&2
263 mv -- "$BACKUP" "$MERGED"
Charles Bailey162eba82008-12-12 21:48:41 +0000264
265 if test "$merge_keep_temporaries" = "false"; then
266 cleanup_temp_files
267 fi
268
Charles Baileyb0169d82008-12-12 21:48:40 +0000269 return 1
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500270 fi
Charles Bailey44c36d12008-02-21 23:30:02 +0000271
272 if test "$merge_keep_backup" = "true"; then
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000273 mv -- "$BACKUP" "$MERGED.orig"
Charles Bailey44c36d12008-02-21 23:30:02 +0000274 else
275 rm -- "$BACKUP"
276 fi
277
Charles Baileyb3ea27e2008-02-21 23:30:34 +0000278 git add -- "$MERGED"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500279 cleanup_temp_files
Charles Baileyb0169d82008-12-12 21:48:40 +0000280 return 0
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500281}
282
Charles Bailey682b4512008-11-13 12:41:14 +0000283prompt=$(git config --bool mergetool.prompt || echo true)
284
David Kastrup822f7c72007-09-23 22:42:08 +0200285while test $# != 0
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500286do
287 case "$1" in
288 -t|--tool*)
289 case "$#,$1" in
290 *,*=*)
David Aguilar9a62d722009-04-06 01:31:28 -0700291 merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500292 ;;
293 1,*)
294 usage ;;
295 *)
296 merge_tool="$2"
297 shift ;;
298 esac
299 ;;
Charles Bailey682b4512008-11-13 12:41:14 +0000300 -y|--no-prompt)
301 prompt=false
302 ;;
303 --prompt)
304 prompt=true
305 ;;
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500306 --)
David Aguilarce2c3eb2008-12-19 17:01:01 -0800307 shift
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500308 break
309 ;;
310 -*)
311 usage
312 ;;
313 *)
314 break
315 ;;
316 esac
317 shift
318done
319
Charles Baileyb0169d82008-12-12 21:48:40 +0000320prompt_after_failed_merge() {
321 while true; do
322 printf "Continue merging other unresolved paths (y/n) ? "
323 read ans
324 case "$ans" in
325
326 [yY]*)
327 return 0
328 ;;
329
330 [nN]*)
331 return 1
332 ;;
333 esac
334 done
335}
Steffen Prohaskae3fa2c72007-10-17 19:16:11 +0200336
David Aguilar47d65922009-04-11 20:41:56 -0700337if test -z "$merge_tool"; then
338 merge_tool=$(get_merge_tool "$merge_tool") || exit
339fi
Ferry Huberts70af4e92009-04-10 21:33:57 +0200340merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
David Aguilar21d0ba72009-04-08 00:17:20 -0700341merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500342
Charles Baileyb0169d82008-12-12 21:48:40 +0000343last_status=0
344rollup_status=0
David Aguilarbb0a4842010-08-17 02:22:46 -0700345rerere=false
346
347files_to_merge() {
348 if test "$rerere" = true
349 then
Martin von Zweigbergk2f59c942011-02-16 05:47:45 -0500350 git rerere remaining
David Aguilarbb0a4842010-08-17 02:22:46 -0700351 else
352 git ls-files -u | sed -e 's/^[^ ]* //' | sort -u
353 fi
354}
355
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500356
357if test $# -eq 0 ; then
David Aguilarbb0a4842010-08-17 02:22:46 -0700358 cd_to_toplevel
359
360 if test -e "$GIT_DIR/MERGE_RR"
361 then
362 rerere=true
363 fi
364
365 files=$(files_to_merge)
Charles Bailey0eea3452008-11-13 12:41:13 +0000366 if test -z "$files" ; then
367 echo "No files need merging"
368 exit 0
369 fi
Charles Baileyaf314712010-08-20 16:25:09 +0100370
371 # Save original stdin
372 exec 3<&0
373
David Aguilarbb0a4842010-08-17 02:22:46 -0700374 printf "Merging:\n"
375 printf "$files\n"
376
377 files_to_merge |
Charles Bailey0eea3452008-11-13 12:41:13 +0000378 while IFS= read i
379 do
Charles Baileyb0169d82008-12-12 21:48:40 +0000380 if test $last_status -ne 0; then
Charles Baileyaf314712010-08-20 16:25:09 +0100381 prompt_after_failed_merge <&3 || exit 1
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500382 fi
Charles Bailey0eea3452008-11-13 12:41:13 +0000383 printf "\n"
Charles Baileyaf314712010-08-20 16:25:09 +0100384 merge_file "$i" <&3
Charles Baileyb0169d82008-12-12 21:48:40 +0000385 last_status=$?
386 if test $last_status -ne 0; then
387 rollup_status=1
388 fi
Charles Bailey0eea3452008-11-13 12:41:13 +0000389 done
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500390else
Charles Bailey0eea3452008-11-13 12:41:13 +0000391 while test $# -gt 0; do
Charles Baileyb0169d82008-12-12 21:48:40 +0000392 if test $last_status -ne 0; then
393 prompt_after_failed_merge || exit 1
394 fi
Charles Bailey0eea3452008-11-13 12:41:13 +0000395 printf "\n"
396 merge_file "$1"
Charles Baileyb0169d82008-12-12 21:48:40 +0000397 last_status=$?
398 if test $last_status -ne 0; then
399 rollup_status=1
400 fi
Charles Bailey0eea3452008-11-13 12:41:13 +0000401 shift
402 done
Theodore Ts'oc4b4a5a2007-03-06 00:05:16 -0500403fi
Charles Baileyb0169d82008-12-12 21:48:40 +0000404
405exit $rollup_status