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