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