Merge branch 'maint'
[git] / git-mergetool--lib.sh
1 #!/bin/sh
2 # git-mergetool--lib is a library for common merge tool functions
3 diff_mode() {
4         test "$TOOL_MODE" = diff
5 }
6
7 merge_mode() {
8         test "$TOOL_MODE" = merge
9 }
10
11 translate_merge_tool_path () {
12         case "$1" in
13         araxis)
14                 echo compare
15                 ;;
16         bc3)
17                 echo bcompare
18                 ;;
19         emerge)
20                 echo emacs
21                 ;;
22         gvimdiff|gvimdiff2)
23                 echo gvim
24                 ;;
25         vimdiff|vimdiff2)
26                 echo vim
27                 ;;
28         *)
29                 echo "$1"
30                 ;;
31         esac
32 }
33
34 check_unchanged () {
35         if test "$MERGED" -nt "$BACKUP"; then
36                 status=0
37         else
38                 while true; do
39                         echo "$MERGED seems unchanged."
40                         printf "Was the merge successful? [y/n] "
41                         read answer
42                         case "$answer" in
43                         y*|Y*) status=0; break ;;
44                         n*|N*) status=1; break ;;
45                         esac
46                 done
47         fi
48 }
49
50 valid_tool () {
51         case "$1" in
52         araxis | bc3 | diffuse | ecmerge | emerge | gvimdiff | gvimdiff2 | \
53         kdiff3 | meld | opendiff | p4merge | tkdiff | vimdiff | vimdiff2 | xxdiff)
54                 ;; # happy
55         kompare)
56                 if ! diff_mode; then
57                         return 1
58                 fi
59                 ;;
60         tortoisemerge)
61                 if ! merge_mode; then
62                         return 1
63                 fi
64                 ;;
65         *)
66                 if test -z "$(get_merge_tool_cmd "$1")"; then
67                         return 1
68                 fi
69                 ;;
70         esac
71 }
72
73 get_merge_tool_cmd () {
74         # Prints the custom command for a merge tool
75         if test -n "$1"; then
76                 merge_tool="$1"
77         else
78                 merge_tool="$(get_merge_tool)"
79         fi
80         if diff_mode; then
81                 echo "$(git config difftool.$merge_tool.cmd ||
82                         git config mergetool.$merge_tool.cmd)"
83         else
84                 echo "$(git config mergetool.$merge_tool.cmd)"
85         fi
86 }
87
88 run_merge_tool () {
89         # If GIT_PREFIX is empty then we cannot use it in tools
90         # that expect to be able to chdir() to its value.
91         GIT_PREFIX=${GIT_PREFIX:-.}
92         export GIT_PREFIX
93
94         merge_tool_path="$(get_merge_tool_path "$1")" || exit
95         base_present="$2"
96         status=0
97
98         case "$1" in
99         araxis)
100                 if merge_mode; then
101                         touch "$BACKUP"
102                         if $base_present; then
103                                 "$merge_tool_path" -wait -merge -3 -a1 \
104                                         "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \
105                                         >/dev/null 2>&1
106                         else
107                                 "$merge_tool_path" -wait -2 \
108                                         "$LOCAL" "$REMOTE" "$MERGED" \
109                                         >/dev/null 2>&1
110                         fi
111                         check_unchanged
112                 else
113                         "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \
114                                 >/dev/null 2>&1
115                 fi
116                 ;;
117         bc3)
118                 if merge_mode; then
119                         touch "$BACKUP"
120                         if $base_present; then
121                                 "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \
122                                         -mergeoutput="$MERGED"
123                         else
124                                 "$merge_tool_path" "$LOCAL" "$REMOTE" \
125                                         -mergeoutput="$MERGED"
126                         fi
127                         check_unchanged
128                 else
129                         "$merge_tool_path" "$LOCAL" "$REMOTE"
130                 fi
131                 ;;
132         diffuse)
133                 if merge_mode; then
134                         touch "$BACKUP"
135                         if $base_present; then
136                                 "$merge_tool_path" \
137                                         "$LOCAL" "$MERGED" "$REMOTE" \
138                                         "$BASE" | cat
139                         else
140                                 "$merge_tool_path" \
141                                         "$LOCAL" "$MERGED" "$REMOTE" | cat
142                         fi
143                         check_unchanged
144                 else
145                         "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
146                 fi
147                 ;;
148         ecmerge)
149                 if merge_mode; then
150                         touch "$BACKUP"
151                         if $base_present; then
152                                 "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
153                                         --default --mode=merge3 --to="$MERGED"
154                         else
155                                 "$merge_tool_path" "$LOCAL" "$REMOTE" \
156                                         --default --mode=merge2 --to="$MERGED"
157                         fi
158                         check_unchanged
159                 else
160                         "$merge_tool_path" --default --mode=diff2 \
161                                 "$LOCAL" "$REMOTE"
162                 fi
163                 ;;
164         emerge)
165                 if merge_mode; then
166                         if $base_present; then
167                                 "$merge_tool_path" \
168                                         -f emerge-files-with-ancestor-command \
169                                         "$LOCAL" "$REMOTE" "$BASE" \
170                                         "$(basename "$MERGED")"
171                         else
172                                 "$merge_tool_path" \
173                                         -f emerge-files-command \
174                                         "$LOCAL" "$REMOTE" \
175                                         "$(basename "$MERGED")"
176                         fi
177                         status=$?
178                 else
179                         "$merge_tool_path" -f emerge-files-command \
180                                 "$LOCAL" "$REMOTE"
181                 fi
182                 ;;
183         gvimdiff|vimdiff)
184                 if merge_mode; then
185                         touch "$BACKUP"
186                         if $base_present; then
187                                 "$merge_tool_path" -f -d -c "wincmd J" \
188                                         "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
189                         else
190                                 "$merge_tool_path" -f -d -c "wincmd l" \
191                                         "$LOCAL" "$MERGED" "$REMOTE"
192                         fi
193                         check_unchanged
194                 else
195                         "$merge_tool_path" -R -f -d -c "wincmd l" \
196                                 -c 'cd $GIT_PREFIX' \
197                                 "$LOCAL" "$REMOTE"
198                 fi
199                 ;;
200         gvimdiff2|vimdiff2)
201                 if merge_mode; then
202                         touch "$BACKUP"
203                         "$merge_tool_path" -f -d -c "wincmd l" \
204                                 "$LOCAL" "$MERGED" "$REMOTE"
205                         check_unchanged
206                 else
207                         "$merge_tool_path" -R -f -d -c "wincmd l" \
208                                 -c 'cd $GIT_PREFIX' \
209                                 "$LOCAL" "$REMOTE"
210                 fi
211                 ;;
212         kdiff3)
213                 if merge_mode; then
214                         if $base_present; then
215                                 ("$merge_tool_path" --auto \
216                                         --L1 "$MERGED (Base)" \
217                                         --L2 "$MERGED (Local)" \
218                                         --L3 "$MERGED (Remote)" \
219                                         -o "$MERGED" \
220                                         "$BASE" "$LOCAL" "$REMOTE" \
221                                 > /dev/null 2>&1)
222                         else
223                                 ("$merge_tool_path" --auto \
224                                         --L1 "$MERGED (Local)" \
225                                         --L2 "$MERGED (Remote)" \
226                                         -o "$MERGED" \
227                                         "$LOCAL" "$REMOTE" \
228                                 > /dev/null 2>&1)
229                         fi
230                         status=$?
231                 else
232                         ("$merge_tool_path" --auto \
233                                 --L1 "$MERGED (A)" \
234                                 --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
235                         > /dev/null 2>&1)
236                 fi
237                 ;;
238         kompare)
239                 "$merge_tool_path" "$LOCAL" "$REMOTE"
240                 ;;
241         meld)
242                 if merge_mode; then
243                         touch "$BACKUP"
244                         "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
245                         check_unchanged
246                 else
247                         "$merge_tool_path" "$LOCAL" "$REMOTE"
248                 fi
249                 ;;
250         opendiff)
251                 if merge_mode; then
252                         touch "$BACKUP"
253                         if $base_present; then
254                                 "$merge_tool_path" "$LOCAL" "$REMOTE" \
255                                         -ancestor "$BASE" \
256                                         -merge "$MERGED" | cat
257                         else
258                                 "$merge_tool_path" "$LOCAL" "$REMOTE" \
259                                         -merge "$MERGED" | cat
260                         fi
261                         check_unchanged
262                 else
263                         "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
264                 fi
265                 ;;
266         p4merge)
267                 if merge_mode; then
268                         touch "$BACKUP"
269                         $base_present || >"$BASE"
270                         "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
271                         check_unchanged
272                 else
273                         "$merge_tool_path" "$LOCAL" "$REMOTE"
274                 fi
275                 ;;
276         tkdiff)
277                 if merge_mode; then
278                         if $base_present; then
279                                 "$merge_tool_path" -a "$BASE" \
280                                         -o "$MERGED" "$LOCAL" "$REMOTE"
281                         else
282                                 "$merge_tool_path" \
283                                         -o "$MERGED" "$LOCAL" "$REMOTE"
284                         fi
285                         status=$?
286                 else
287                         "$merge_tool_path" "$LOCAL" "$REMOTE"
288                 fi
289                 ;;
290         tortoisemerge)
291                 if $base_present; then
292                         touch "$BACKUP"
293                         "$merge_tool_path" \
294                                 -base:"$BASE" -mine:"$LOCAL" \
295                                 -theirs:"$REMOTE" -merged:"$MERGED"
296                         check_unchanged
297                 else
298                         echo "TortoiseMerge cannot be used without a base" 1>&2
299                         status=1
300                 fi
301                 ;;
302         xxdiff)
303                 if merge_mode; then
304                         touch "$BACKUP"
305                         if $base_present; then
306                                 "$merge_tool_path" -X --show-merged-pane \
307                                         -R 'Accel.SaveAsMerged: "Ctrl-S"' \
308                                         -R 'Accel.Search: "Ctrl+F"' \
309                                         -R 'Accel.SearchForward: "Ctrl-G"' \
310                                         --merged-file "$MERGED" \
311                                         "$LOCAL" "$BASE" "$REMOTE"
312                         else
313                                 "$merge_tool_path" -X $extra \
314                                         -R 'Accel.SaveAsMerged: "Ctrl-S"' \
315                                         -R 'Accel.Search: "Ctrl+F"' \
316                                         -R 'Accel.SearchForward: "Ctrl-G"' \
317                                         --merged-file "$MERGED" \
318                                         "$LOCAL" "$REMOTE"
319                         fi
320                         check_unchanged
321                 else
322                         "$merge_tool_path" \
323                                 -R 'Accel.Search: "Ctrl+F"' \
324                                 -R 'Accel.SearchForward: "Ctrl-G"' \
325                                 "$LOCAL" "$REMOTE"
326                 fi
327                 ;;
328         *)
329                 merge_tool_cmd="$(get_merge_tool_cmd "$1")"
330                 if test -z "$merge_tool_cmd"; then
331                         if merge_mode; then
332                                 status=1
333                         fi
334                         break
335                 fi
336                 if merge_mode; then
337                         trust_exit_code="$(git config --bool \
338                                 mergetool."$1".trustExitCode || echo false)"
339                         if test "$trust_exit_code" = "false"; then
340                                 touch "$BACKUP"
341                                 ( eval $merge_tool_cmd )
342                                 check_unchanged
343                         else
344                                 ( eval $merge_tool_cmd )
345                                 status=$?
346                         fi
347                 else
348                         ( eval $merge_tool_cmd )
349                 fi
350                 ;;
351         esac
352         return $status
353 }
354
355 guess_merge_tool () {
356         if merge_mode; then
357                 tools="tortoisemerge"
358         else
359                 tools="kompare"
360         fi
361         if test -n "$DISPLAY"; then
362                 if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
363                         tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
364                 else
365                         tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
366                 fi
367                 tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3"
368         fi
369         case "${VISUAL:-$EDITOR}" in
370         *vim*)
371                 tools="$tools vimdiff emerge"
372                 ;;
373         *)
374                 tools="$tools emerge vimdiff"
375                 ;;
376         esac
377         echo >&2 "merge tool candidates: $tools"
378
379         # Loop over each candidate and stop when a valid merge tool is found.
380         for i in $tools
381         do
382                 merge_tool_path="$(translate_merge_tool_path "$i")"
383                 if type "$merge_tool_path" > /dev/null 2>&1; then
384                         echo "$i"
385                         return 0
386                 fi
387         done
388
389         echo >&2 "No known merge resolution program available."
390         return 1
391 }
392
393 get_configured_merge_tool () {
394         # Diff mode first tries diff.tool and falls back to merge.tool.
395         # Merge mode only checks merge.tool
396         if diff_mode; then
397                 merge_tool=$(git config diff.tool || git config merge.tool)
398         else
399                 merge_tool=$(git config merge.tool)
400         fi
401         if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
402                 echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
403                 echo >&2 "Resetting to default..."
404                 return 1
405         fi
406         echo "$merge_tool"
407 }
408
409 get_merge_tool_path () {
410         # A merge tool has been set, so verify that it's valid.
411         if test -n "$1"; then
412                 merge_tool="$1"
413         else
414                 merge_tool="$(get_merge_tool)"
415         fi
416         if ! valid_tool "$merge_tool"; then
417                 echo >&2 "Unknown merge tool $merge_tool"
418                 exit 1
419         fi
420         if diff_mode; then
421                 merge_tool_path=$(git config difftool."$merge_tool".path ||
422                                   git config mergetool."$merge_tool".path)
423         else
424                 merge_tool_path=$(git config mergetool."$merge_tool".path)
425         fi
426         if test -z "$merge_tool_path"; then
427                 merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
428         fi
429         if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
430         ! type "$merge_tool_path" > /dev/null 2>&1; then
431                 echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
432                          "'$merge_tool_path'"
433                 exit 1
434         fi
435         echo "$merge_tool_path"
436 }
437
438 get_merge_tool () {
439         # Check if a merge tool has been configured
440         merge_tool=$(get_configured_merge_tool)
441         # Try to guess an appropriate merge tool if no tool has been set.
442         if test -z "$merge_tool"; then
443                 merge_tool="$(guess_merge_tool)" || exit
444         fi
445         echo "$merge_tool"
446 }