Merge branch 'kb/use-nsec-doc'
[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] [--tool-help] [-y|--no-prompt|--prompt] [file to merge] ...'
12 SUBDIRECTORY_OK=Yes
13 NONGIT_OK=Yes
14 OPTIONS_SPEC=
15 TOOL_MODE=merge
16 . git-sh-setup
17 . git-mergetool--lib
18
19 # Returns true if the mode reflects a symlink
20 is_symlink () {
21         test "$1" = 120000
22 }
23
24 is_submodule () {
25         test "$1" = 160000
26 }
27
28 local_present () {
29         test -n "$local_mode"
30 }
31
32 remote_present () {
33         test -n "$remote_mode"
34 }
35
36 base_present () {
37         test -n "$base_mode"
38 }
39
40 mergetool_tmpdir_init () {
41         if test "$(git config --bool mergetool.writeToTemp)" != true
42         then
43                 MERGETOOL_TMPDIR=.
44                 return 0
45         fi
46         if MERGETOOL_TMPDIR=$(mktemp -d -t "git-mergetool-XXXXXX" 2>/dev/null)
47         then
48                 return 0
49         fi
50         die "error: mktemp is needed when 'mergetool.writeToTemp' is true"
51 }
52
53 cleanup_temp_files () {
54         if test "$1" = --save-backup
55         then
56                 rm -rf -- "$MERGED.orig"
57                 test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
58                 rm -f -- "$LOCAL" "$REMOTE" "$BASE"
59         else
60                 rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
61         fi
62         if test "$MERGETOOL_TMPDIR" != "."
63         then
64                 rmdir "$MERGETOOL_TMPDIR"
65         fi
66 }
67
68 describe_file () {
69         mode="$1"
70         branch="$2"
71         file="$3"
72
73         printf "  {%s}: " "$branch"
74         if test -z "$mode"
75         then
76                 echo "deleted"
77         elif is_symlink "$mode"
78         then
79                 echo "a symbolic link -> '$(cat "$file")'"
80         elif is_submodule "$mode"
81         then
82                 echo "submodule commit $file"
83         elif base_present
84         then
85                 echo "modified file"
86         else
87                 echo "created file"
88         fi
89 }
90
91 resolve_symlink_merge () {
92         while true
93         do
94                 printf "Use (l)ocal or (r)emote, or (a)bort? "
95                 read ans || return 1
96                 case "$ans" in
97                 [lL]*)
98                         git checkout-index -f --stage=2 -- "$MERGED"
99                         git add -- "$MERGED"
100                         cleanup_temp_files --save-backup
101                         return 0
102                         ;;
103                 [rR]*)
104                         git checkout-index -f --stage=3 -- "$MERGED"
105                         git add -- "$MERGED"
106                         cleanup_temp_files --save-backup
107                         return 0
108                         ;;
109                 [aA]*)
110                         return 1
111                         ;;
112                 esac
113         done
114 }
115
116 resolve_deleted_merge () {
117         while true
118         do
119                 if base_present
120                 then
121                         printf "Use (m)odified or (d)eleted file, or (a)bort? "
122                 else
123                         printf "Use (c)reated or (d)eleted file, or (a)bort? "
124                 fi
125                 read ans || return 1
126                 case "$ans" in
127                 [mMcC]*)
128                         git add -- "$MERGED"
129                         cleanup_temp_files --save-backup
130                         return 0
131                         ;;
132                 [dD]*)
133                         git rm -- "$MERGED" > /dev/null
134                         cleanup_temp_files
135                         return 0
136                         ;;
137                 [aA]*)
138                         return 1
139                         ;;
140                 esac
141         done
142 }
143
144 resolve_submodule_merge () {
145         while true
146         do
147                 printf "Use (l)ocal or (r)emote, or (a)bort? "
148                 read ans || return 1
149                 case "$ans" in
150                 [lL]*)
151                         if ! local_present
152                         then
153                                 if test -n "$(git ls-tree HEAD -- "$MERGED")"
154                                 then
155                                         # Local isn't present, but it's a subdirectory
156                                         git ls-tree --full-name -r HEAD -- "$MERGED" |
157                                         git update-index --index-info || exit $?
158                                 else
159                                         test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
160                                         git update-index --force-remove "$MERGED"
161                                         cleanup_temp_files --save-backup
162                                 fi
163                         elif is_submodule "$local_mode"
164                         then
165                                 stage_submodule "$MERGED" "$local_sha1"
166                         else
167                                 git checkout-index -f --stage=2 -- "$MERGED"
168                                 git add -- "$MERGED"
169                         fi
170                         return 0
171                         ;;
172                 [rR]*)
173                         if ! remote_present
174                         then
175                                 if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"
176                                 then
177                                         # Remote isn't present, but it's a subdirectory
178                                         git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" |
179                                         git update-index --index-info || exit $?
180                                 else
181                                         test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
182                                         git update-index --force-remove "$MERGED"
183                                 fi
184                         elif is_submodule "$remote_mode"
185                         then
186                                 ! is_submodule "$local_mode" &&
187                                 test -e "$MERGED" &&
188                                 mv -- "$MERGED" "$BACKUP"
189                                 stage_submodule "$MERGED" "$remote_sha1"
190                         else
191                                 test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
192                                 git checkout-index -f --stage=3 -- "$MERGED"
193                                 git add -- "$MERGED"
194                         fi
195                         cleanup_temp_files --save-backup
196                         return 0
197                         ;;
198                 [aA]*)
199                         return 1
200                         ;;
201                 esac
202         done
203 }
204
205 stage_submodule () {
206         path="$1"
207         submodule_sha1="$2"
208         mkdir -p "$path" ||
209         die "fatal: unable to create directory for module at $path"
210         # Find $path relative to work tree
211         work_tree_root=$(cd_to_toplevel && pwd)
212         work_rel_path=$(cd "$path" &&
213                 GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix
214         )
215         test -n "$work_rel_path" ||
216         die "fatal: unable to get path of module $path relative to work tree"
217         git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
218 }
219
220 checkout_staged_file () {
221         tmpfile=$(expr \
222                 "$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \
223                 : '\([^ ]*\)    ')
224
225         if test $? -eq 0 && test -n "$tmpfile"
226         then
227                 mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
228         else
229                 >"$3"
230         fi
231 }
232
233 merge_file () {
234         MERGED="$1"
235
236         f=$(git ls-files -u -- "$MERGED")
237         if test -z "$f"
238         then
239                 if test ! -f "$MERGED"
240                 then
241                         echo "$MERGED: file not found"
242                 else
243                         echo "$MERGED: file does not need merging"
244                 fi
245                 return 1
246         fi
247
248         if BASE=$(expr "$MERGED" : '\(.*\)\.[^/]*$')
249         then
250                 ext=$(expr "$MERGED" : '.*\(\.[^/]*\)$')
251         else
252                 BASE=$MERGED
253                 ext=
254         fi
255
256         mergetool_tmpdir_init
257
258         if test "$MERGETOOL_TMPDIR" != "."
259         then
260                 # If we're using a temporary directory then write to the
261                 # top-level of that directory.
262                 BASE=${BASE##*/}
263         fi
264
265         BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext"
266         LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext"
267         REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext"
268         BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext"
269
270         base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
271         local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
272         remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
273
274         if is_submodule "$local_mode" || is_submodule "$remote_mode"
275         then
276                 echo "Submodule merge conflict for '$MERGED':"
277                 local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}')
278                 remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}')
279                 describe_file "$local_mode" "local" "$local_sha1"
280                 describe_file "$remote_mode" "remote" "$remote_sha1"
281                 resolve_submodule_merge
282                 return
283         fi
284
285         mv -- "$MERGED" "$BACKUP"
286         cp -- "$BACKUP" "$MERGED"
287
288         checkout_staged_file 1 "$MERGED" "$BASE"
289         checkout_staged_file 2 "$MERGED" "$LOCAL"
290         checkout_staged_file 3 "$MERGED" "$REMOTE"
291
292         if test -z "$local_mode" || test -z "$remote_mode"
293         then
294                 echo "Deleted merge conflict for '$MERGED':"
295                 describe_file "$local_mode" "local" "$LOCAL"
296                 describe_file "$remote_mode" "remote" "$REMOTE"
297                 resolve_deleted_merge
298                 return
299         fi
300
301         if is_symlink "$local_mode" || is_symlink "$remote_mode"
302         then
303                 echo "Symbolic link merge conflict for '$MERGED':"
304                 describe_file "$local_mode" "local" "$LOCAL"
305                 describe_file "$remote_mode" "remote" "$REMOTE"
306                 resolve_symlink_merge
307                 return
308         fi
309
310         echo "Normal merge conflict for '$MERGED':"
311         describe_file "$local_mode" "local" "$LOCAL"
312         describe_file "$remote_mode" "remote" "$REMOTE"
313         if test "$guessed_merge_tool" = true || test "$prompt" = true
314         then
315                 printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
316                 read ans || return 1
317         fi
318
319         if base_present
320         then
321                 present=true
322         else
323                 present=false
324         fi
325
326         if ! run_merge_tool "$merge_tool" "$present"
327         then
328                 echo "merge of $MERGED failed" 1>&2
329                 mv -- "$BACKUP" "$MERGED"
330
331                 if test "$merge_keep_temporaries" = "false"
332                 then
333                         cleanup_temp_files
334                 fi
335
336                 return 1
337         fi
338
339         if test "$merge_keep_backup" = "true"
340         then
341                 mv -- "$BACKUP" "$MERGED.orig"
342         else
343                 rm -- "$BACKUP"
344         fi
345
346         git add -- "$MERGED"
347         cleanup_temp_files
348         return 0
349 }
350
351 prompt=$(git config --bool mergetool.prompt)
352 guessed_merge_tool=false
353
354 while test $# != 0
355 do
356         case "$1" in
357         --tool-help=*)
358                 TOOL_MODE=${1#--tool-help=}
359                 show_tool_help
360                 ;;
361         --tool-help)
362                 show_tool_help
363                 ;;
364         -t|--tool*)
365                 case "$#,$1" in
366                 *,*=*)
367                         merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
368                         ;;
369                 1,*)
370                         usage ;;
371                 *)
372                         merge_tool="$2"
373                         shift ;;
374                 esac
375                 ;;
376         -y|--no-prompt)
377                 prompt=false
378                 ;;
379         --prompt)
380                 prompt=true
381                 ;;
382         --)
383                 shift
384                 break
385                 ;;
386         -*)
387                 usage
388                 ;;
389         *)
390                 break
391                 ;;
392         esac
393         shift
394 done
395
396 prompt_after_failed_merge () {
397         while true
398         do
399                 printf "Continue merging other unresolved paths (y/n) ? "
400                 read ans || return 1
401                 case "$ans" in
402                 [yY]*)
403                         return 0
404                         ;;
405                 [nN]*)
406                         return 1
407                         ;;
408                 esac
409         done
410 }
411
412 git_dir_init
413 require_work_tree
414
415 if test -z "$merge_tool"
416 then
417         # Check if a merge tool has been configured
418         merge_tool=$(get_configured_merge_tool)
419         # Try to guess an appropriate merge tool if no tool has been set.
420         if test -z "$merge_tool"
421         then
422                 merge_tool=$(guess_merge_tool) || exit
423                 guessed_merge_tool=true
424         fi
425 fi
426 merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
427 merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
428
429 files=
430
431 if test $# -eq 0
432 then
433         cd_to_toplevel
434
435         if test -e "$GIT_DIR/MERGE_RR"
436         then
437                 files=$(git rerere remaining)
438         else
439                 files=$(git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u)
440         fi
441 else
442         files=$(git ls-files -u -- "$@" | sed -e 's/^[^ ]*      //' | sort -u)
443 fi
444
445 if test -z "$files"
446 then
447         echo "No files need merging"
448         exit 0
449 fi
450
451 printf "Merging:\n"
452 printf "%s\n" "$files"
453
454 rc=0
455 for i in $files
456 do
457         printf "\n"
458         if ! merge_file "$i"
459         then
460                 rc=1
461                 prompt_after_failed_merge || exit 1
462         fi
463 done
464
465 exit $rc