git-mergetool--lib: Make vimdiff retain the current directory
[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                         if $base_present; then
270                                 "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
271                         else
272                                 "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED"
273                         fi
274                         check_unchanged
275                 else
276                         "$merge_tool_path" "$LOCAL" "$REMOTE"
277                 fi
278                 ;;
279         tkdiff)
280                 if merge_mode; then
281                         if $base_present; then
282                                 "$merge_tool_path" -a "$BASE" \
283                                         -o "$MERGED" "$LOCAL" "$REMOTE"
284                         else
285                                 "$merge_tool_path" \
286                                         -o "$MERGED" "$LOCAL" "$REMOTE"
287                         fi
288                         status=$?
289                 else
290                         "$merge_tool_path" "$LOCAL" "$REMOTE"
291                 fi
292                 ;;
293         tortoisemerge)
294                 if $base_present; then
295                         touch "$BACKUP"
296                         "$merge_tool_path" \
297                                 -base:"$BASE" -mine:"$LOCAL" \
298                                 -theirs:"$REMOTE" -merged:"$MERGED"
299                         check_unchanged
300                 else
301                         echo "TortoiseMerge cannot be used without a base" 1>&2
302                         status=1
303                 fi
304                 ;;
305         xxdiff)
306                 if merge_mode; then
307                         touch "$BACKUP"
308                         if $base_present; then
309                                 "$merge_tool_path" -X --show-merged-pane \
310                                         -R 'Accel.SaveAsMerged: "Ctrl-S"' \
311                                         -R 'Accel.Search: "Ctrl+F"' \
312                                         -R 'Accel.SearchForward: "Ctrl-G"' \
313                                         --merged-file "$MERGED" \
314                                         "$LOCAL" "$BASE" "$REMOTE"
315                         else
316                                 "$merge_tool_path" -X $extra \
317                                         -R 'Accel.SaveAsMerged: "Ctrl-S"' \
318                                         -R 'Accel.Search: "Ctrl+F"' \
319                                         -R 'Accel.SearchForward: "Ctrl-G"' \
320                                         --merged-file "$MERGED" \
321                                         "$LOCAL" "$REMOTE"
322                         fi
323                         check_unchanged
324                 else
325                         "$merge_tool_path" \
326                                 -R 'Accel.Search: "Ctrl+F"' \
327                                 -R 'Accel.SearchForward: "Ctrl-G"' \
328                                 "$LOCAL" "$REMOTE"
329                 fi
330                 ;;
331         *)
332                 merge_tool_cmd="$(get_merge_tool_cmd "$1")"
333                 if test -z "$merge_tool_cmd"; then
334                         if merge_mode; then
335                                 status=1
336                         fi
337                         break
338                 fi
339                 if merge_mode; then
340                         trust_exit_code="$(git config --bool \
341                                 mergetool."$1".trustExitCode || echo false)"
342                         if test "$trust_exit_code" = "false"; then
343                                 touch "$BACKUP"
344                                 ( eval $merge_tool_cmd )
345                                 check_unchanged
346                         else
347                                 ( eval $merge_tool_cmd )
348                                 status=$?
349                         fi
350                 else
351                         ( eval $merge_tool_cmd )
352                 fi
353                 ;;
354         esac
355         return $status
356 }
357
358 guess_merge_tool () {
359         if merge_mode; then
360                 tools="tortoisemerge"
361         else
362                 tools="kompare"
363         fi
364         if test -n "$DISPLAY"; then
365                 if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
366                         tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
367                 else
368                         tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
369                 fi
370                 tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3"
371         fi
372         case "${VISUAL:-$EDITOR}" in
373         *vim*)
374                 tools="$tools vimdiff emerge"
375                 ;;
376         *)
377                 tools="$tools emerge vimdiff"
378                 ;;
379         esac
380         echo >&2 "merge tool candidates: $tools"
381
382         # Loop over each candidate and stop when a valid merge tool is found.
383         for i in $tools
384         do
385                 merge_tool_path="$(translate_merge_tool_path "$i")"
386                 if type "$merge_tool_path" > /dev/null 2>&1; then
387                         echo "$i"
388                         return 0
389                 fi
390         done
391
392         echo >&2 "No known merge resolution program available."
393         return 1
394 }
395
396 get_configured_merge_tool () {
397         # Diff mode first tries diff.tool and falls back to merge.tool.
398         # Merge mode only checks merge.tool
399         if diff_mode; then
400                 merge_tool=$(git config diff.tool || git config merge.tool)
401         else
402                 merge_tool=$(git config merge.tool)
403         fi
404         if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
405                 echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
406                 echo >&2 "Resetting to default..."
407                 return 1
408         fi
409         echo "$merge_tool"
410 }
411
412 get_merge_tool_path () {
413         # A merge tool has been set, so verify that it's valid.
414         if test -n "$1"; then
415                 merge_tool="$1"
416         else
417                 merge_tool="$(get_merge_tool)"
418         fi
419         if ! valid_tool "$merge_tool"; then
420                 echo >&2 "Unknown merge tool $merge_tool"
421                 exit 1
422         fi
423         if diff_mode; then
424                 merge_tool_path=$(git config difftool."$merge_tool".path ||
425                                   git config mergetool."$merge_tool".path)
426         else
427                 merge_tool_path=$(git config mergetool."$merge_tool".path)
428         fi
429         if test -z "$merge_tool_path"; then
430                 merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
431         fi
432         if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
433         ! type "$merge_tool_path" > /dev/null 2>&1; then
434                 echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
435                          "'$merge_tool_path'"
436                 exit 1
437         fi
438         echo "$merge_tool_path"
439 }
440
441 get_merge_tool () {
442         # Check if a merge tool has been configured
443         merge_tool=$(get_configured_merge_tool)
444         # Try to guess an appropriate merge tool if no tool has been set.
445         if test -z "$merge_tool"; then
446                 merge_tool="$(guess_merge_tool)" || exit
447         fi
448         echo "$merge_tool"
449 }