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