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