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