mergetool: break setup_tool out into separate initialization function
[git] / git-mergetool--lib.sh
1 # git-mergetool--lib is a shell library for common merge tool functions
2
3 : ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
4
5 IFS='
6 '
7
8 mode_ok () {
9         if diff_mode
10         then
11                 can_diff
12         elif merge_mode
13         then
14                 can_merge
15         else
16                 false
17         fi
18 }
19
20 is_available () {
21         merge_tool_path=$(translate_merge_tool_path "$1") &&
22         type "$merge_tool_path" >/dev/null 2>&1
23 }
24
25 list_config_tools () {
26         section=$1
27         line_prefix=${2:-}
28
29         git config --get-regexp $section'\..*\.cmd' |
30         while read -r key value
31         do
32                 toolname=${key#$section.}
33                 toolname=${toolname%.cmd}
34
35                 printf "%s%s\n" "$line_prefix" "$toolname"
36         done
37 }
38
39 show_tool_names () {
40         condition=${1:-true} per_line_prefix=${2:-} preamble=${3:-}
41         not_found_msg=${4:-}
42         extra_content=${5:-}
43
44         shown_any=
45         ( cd "$MERGE_TOOLS_DIR" && ls ) | {
46                 while read scriptname
47                 do
48                         setup_tool "$scriptname" 2>/dev/null
49                         variants="$variants$(list_tool_variants)\n"
50                 done
51                 variants="$(echo "$variants" | sort | uniq)"
52
53                 for toolname in $variants
54                 do
55                         if setup_tool "$toolname" 2>/dev/null &&
56                                 (eval "$condition" "$toolname")
57                         then
58                                 if test -n "$preamble"
59                                 then
60                                         printf "%s\n" "$preamble"
61                                         preamble=
62                                 fi
63                                 shown_any=yes
64                                 printf "%s%s\n" "$per_line_prefix" "$toolname"
65                         fi
66                 done
67
68                 if test -n "$extra_content"
69                 then
70                         if test -n "$preamble"
71                         then
72                                 # Note: no '\n' here since we don't want a
73                                 # blank line if there is no initial content.
74                                 printf "%s" "$preamble"
75                                 preamble=
76                         fi
77                         shown_any=yes
78                         printf "\n%s\n" "$extra_content"
79                 fi
80
81                 if test -n "$preamble" && test -n "$not_found_msg"
82                 then
83                         printf "%s\n" "$not_found_msg"
84                 fi
85
86                 test -n "$shown_any"
87         }
88 }
89
90 diff_mode () {
91         test "$TOOL_MODE" = diff
92 }
93
94 merge_mode () {
95         test "$TOOL_MODE" = merge
96 }
97
98 gui_mode () {
99         test "$GIT_MERGETOOL_GUI" = true
100 }
101
102 translate_merge_tool_path () {
103         echo "$1"
104 }
105
106 check_unchanged () {
107         if test "$MERGED" -nt "$BACKUP"
108         then
109                 return 0
110         else
111                 while true
112                 do
113                         echo "$MERGED seems unchanged."
114                         printf "Was the merge successful [y/n]? "
115                         read answer || return 1
116                         case "$answer" in
117                         y*|Y*) return 0 ;;
118                         n*|N*) return 1 ;;
119                         esac
120                 done
121         fi
122 }
123
124 valid_tool () {
125         setup_tool "$1" && return 0
126         cmd=$(get_merge_tool_cmd "$1")
127         test -n "$cmd"
128 }
129
130 setup_user_tool () {
131         merge_tool_cmd=$(get_merge_tool_cmd "$tool")
132         test -n "$merge_tool_cmd" || return 1
133
134         diff_cmd () {
135                 ( eval $merge_tool_cmd )
136         }
137
138         merge_cmd () {
139                 ( eval $merge_tool_cmd )
140         }
141
142         list_tool_variants () {
143                 echo "$tool"
144         }
145 }
146
147 setup_tool () {
148         tool="$1"
149
150         # Fallback definitions, to be overridden by tools.
151         can_merge () {
152                 return 0
153         }
154
155         can_diff () {
156                 return 0
157         }
158
159         diff_cmd () {
160                 return 1
161         }
162
163         merge_cmd () {
164                 return 1
165         }
166
167         translate_merge_tool_path () {
168                 echo "$1"
169         }
170
171         list_tool_variants () {
172                 echo "$tool"
173         }
174
175         # Most tools' exit codes cannot be trusted, so By default we ignore
176         # their exit code and check the merged file's modification time in
177         # check_unchanged() to determine whether or not the merge was
178         # successful.  The return value from run_merge_cmd, by default, is
179         # determined by check_unchanged().
180         #
181         # When a tool's exit code can be trusted then the return value from
182         # run_merge_cmd is simply the tool's exit code, and check_unchanged()
183         # is not called.
184         #
185         # The return value of exit_code_trustable() tells us whether or not we
186         # can trust the tool's exit code.
187         #
188         # User-defined and built-in tools default to false.
189         # Built-in tools advertise that their exit code is trustable by
190         # redefining exit_code_trustable() to true.
191
192         exit_code_trustable () {
193                 false
194         }
195
196         if test -f "$MERGE_TOOLS_DIR/$tool"
197         then
198                 . "$MERGE_TOOLS_DIR/$tool"
199         elif test -f "$MERGE_TOOLS_DIR/${tool%[0-9]}"
200         then
201                 . "$MERGE_TOOLS_DIR/${tool%[0-9]}"
202         else
203                 setup_user_tool
204                 return $?
205         fi
206
207         # Now let the user override the default command for the tool.  If
208         # they have not done so then this will return 1 which we ignore.
209         setup_user_tool
210
211         if ! list_tool_variants | grep -q "^$tool$"
212         then
213                 return 1
214         fi
215
216         if merge_mode && ! can_merge
217         then
218                 echo "error: '$tool' can not be used to resolve merges" >&2
219                 return 1
220         elif diff_mode && ! can_diff
221         then
222                 echo "error: '$tool' can only be used to resolve merges" >&2
223                 return 1
224         fi
225         return 0
226 }
227
228 get_merge_tool_cmd () {
229         merge_tool="$1"
230         if diff_mode
231         then
232                 git config "difftool.$merge_tool.cmd" ||
233                 git config "mergetool.$merge_tool.cmd"
234         else
235                 git config "mergetool.$merge_tool.cmd"
236         fi
237 }
238
239 trust_exit_code () {
240         if git config --bool "mergetool.$1.trustExitCode"
241         then
242                 :; # OK
243         elif exit_code_trustable
244         then
245                 echo true
246         else
247                 echo false
248         fi
249 }
250
251 initialize_merge_tool () {
252         # Bring tool-specific functions into scope
253         setup_tool "$1" || return 1
254 }
255
256 # Entry point for running tools
257 run_merge_tool () {
258         # If GIT_PREFIX is empty then we cannot use it in tools
259         # that expect to be able to chdir() to its value.
260         GIT_PREFIX=${GIT_PREFIX:-.}
261         export GIT_PREFIX
262
263         merge_tool_path=$(get_merge_tool_path "$1") || exit
264         base_present="$2"
265
266         if merge_mode
267         then
268                 run_merge_cmd "$1"
269         else
270                 run_diff_cmd "$1"
271         fi
272 }
273
274 # Run a either a configured or built-in diff tool
275 run_diff_cmd () {
276         diff_cmd "$1"
277 }
278
279 # Run a either a configured or built-in merge tool
280 run_merge_cmd () {
281         mergetool_trust_exit_code=$(trust_exit_code "$1")
282         if test "$mergetool_trust_exit_code" = "true"
283         then
284                 merge_cmd "$1"
285         else
286                 touch "$BACKUP"
287                 merge_cmd "$1"
288                 check_unchanged
289         fi
290 }
291
292 list_merge_tool_candidates () {
293         if merge_mode
294         then
295                 tools="tortoisemerge"
296         else
297                 tools="kompare"
298         fi
299         if test -n "$DISPLAY"
300         then
301                 if test -n "$GNOME_DESKTOP_SESSION_ID"
302                 then
303                         tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
304                 else
305                         tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
306                 fi
307                 tools="$tools gvimdiff diffuse diffmerge ecmerge"
308                 tools="$tools p4merge araxis bc codecompare"
309                 tools="$tools smerge"
310         fi
311         case "${VISUAL:-$EDITOR}" in
312         *nvim*)
313                 tools="$tools nvimdiff vimdiff emerge"
314                 ;;
315         *vim*)
316                 tools="$tools vimdiff nvimdiff emerge"
317                 ;;
318         *)
319                 tools="$tools emerge vimdiff nvimdiff"
320                 ;;
321         esac
322 }
323
324 show_tool_help () {
325         tool_opt="'git ${TOOL_MODE}tool --tool=<tool>'"
326
327         tab='   '
328         LF='
329 '
330         any_shown=no
331
332         cmd_name=${TOOL_MODE}tool
333         config_tools=$({
334                 diff_mode && list_config_tools difftool "$tab$tab"
335                 list_config_tools mergetool "$tab$tab"
336         } | sort)
337         extra_content=
338         if test -n "$config_tools"
339         then
340                 extra_content="${tab}user-defined:${LF}$config_tools"
341         fi
342
343         show_tool_names 'mode_ok && is_available' "$tab$tab" \
344                 "$tool_opt may be set to one of the following:" \
345                 "No suitable tool for 'git $cmd_name --tool=<tool>' found." \
346                 "$extra_content" &&
347                 any_shown=yes
348
349         show_tool_names 'mode_ok && ! is_available' "$tab$tab" \
350                 "${LF}The following tools are valid, but not currently available:" &&
351                 any_shown=yes
352
353         if test "$any_shown" = yes
354         then
355                 echo
356                 echo "Some of the tools listed above only work in a windowed"
357                 echo "environment. If run in a terminal-only session, they will fail."
358         fi
359         exit 0
360 }
361
362 guess_merge_tool () {
363         list_merge_tool_candidates
364         cat >&2 <<-EOF
365
366         This message is displayed because '$TOOL_MODE.tool' is not configured.
367         See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details.
368         'git ${TOOL_MODE}tool' will now attempt to use one of the following tools:
369         $tools
370         EOF
371
372         # Loop over each candidate and stop when a valid merge tool is found.
373         IFS=' '
374         for tool in $tools
375         do
376                 is_available "$tool" && echo "$tool" && return 0
377         done
378
379         echo >&2 "No known ${TOOL_MODE} tool is available."
380         return 1
381 }
382
383 get_configured_merge_tool () {
384         keys=
385         if diff_mode
386         then
387                 if gui_mode
388                 then
389                         keys="diff.guitool merge.guitool diff.tool merge.tool"
390                 else
391                         keys="diff.tool merge.tool"
392                 fi
393         else
394                 if gui_mode
395                 then
396                         keys="merge.guitool merge.tool"
397                 else
398                         keys="merge.tool"
399                 fi
400         fi
401
402         merge_tool=$(
403                 IFS=' '
404                 for key in $keys
405                 do
406                         selected=$(git config $key)
407                         if test -n "$selected"
408                         then
409                                 echo "$selected"
410                                 return
411                         fi
412                 done)
413
414         if test -n "$merge_tool" && ! valid_tool "$merge_tool"
415         then
416                 echo >&2 "git config option $TOOL_MODE.${gui_prefix}tool set to unknown tool: $merge_tool"
417                 echo >&2 "Resetting to default..."
418                 return 1
419         fi
420         echo "$merge_tool"
421 }
422
423 get_merge_tool_path () {
424         # A merge tool has been set, so verify that it's valid.
425         merge_tool="$1"
426         if ! valid_tool "$merge_tool"
427         then
428                 echo >&2 "Unknown merge tool $merge_tool"
429                 exit 1
430         fi
431         if diff_mode
432         then
433                 merge_tool_path=$(git config difftool."$merge_tool".path ||
434                                   git config mergetool."$merge_tool".path)
435         else
436                 merge_tool_path=$(git config mergetool."$merge_tool".path)
437         fi
438         if test -z "$merge_tool_path"
439         then
440                 merge_tool_path=$(translate_merge_tool_path "$merge_tool")
441         fi
442         if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
443                 ! type "$merge_tool_path" >/dev/null 2>&1
444         then
445                 echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
446                          "'$merge_tool_path'"
447                 exit 1
448         fi
449         echo "$merge_tool_path"
450 }
451
452 get_merge_tool () {
453         is_guessed=false
454         # Check if a merge tool has been configured
455         merge_tool=$(get_configured_merge_tool)
456         # Try to guess an appropriate merge tool if no tool has been set.
457         if test -z "$merge_tool"
458         then
459                 merge_tool=$(guess_merge_tool) || exit
460                 is_guessed=true
461         fi
462         echo "$merge_tool"
463         test "$is_guessed" = false
464 }
465
466 mergetool_find_win32_cmd () {
467         executable=$1
468         sub_directory=$2
469
470         # Use $executable if it exists in $PATH
471         if type -p "$executable" >/dev/null 2>&1
472         then
473                 printf '%s' "$executable"
474                 return
475         fi
476
477         # Look for executable in the typical locations
478         for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' |
479                 cut -d '=' -f 2- | sort -u)
480         do
481                 if test -n "$directory" && test -x "$directory/$sub_directory/$executable"
482                 then
483                         printf '%s' "$directory/$sub_directory/$executable"
484                         return
485                 fi
486         done
487
488         printf '%s' "$executable"
489 }