Merge branch 'tr/fd-gotcha-fixes'
[git] / git-mergetool--lib.sh
1 #!/bin/sh
2 # git-mergetool--lib is a library for common merge tool functions
3
4 : ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
5
6 mode_ok () {
7         if diff_mode
8         then
9                 can_diff
10         elif merge_mode
11         then
12                 can_merge
13         else
14                 false
15         fi
16 }
17
18 is_available () {
19         merge_tool_path=$(translate_merge_tool_path "$1") &&
20         type "$merge_tool_path" >/dev/null 2>&1
21 }
22
23 list_config_tools () {
24         section=$1
25         line_prefix=${2:-}
26
27         git config --get-regexp $section'\..*\.cmd' |
28         while read -r key value
29         do
30                 toolname=${key#$section.}
31                 toolname=${toolname%.cmd}
32
33                 printf "%s%s\n" "$line_prefix" "$toolname"
34         done
35 }
36
37 show_tool_names () {
38         condition=${1:-true} per_line_prefix=${2:-} preamble=${3:-}
39         not_found_msg=${4:-}
40         extra_content=${5:-}
41
42         shown_any=
43         ( cd "$MERGE_TOOLS_DIR" && ls ) | {
44                 while read toolname
45                 do
46                         if setup_tool "$toolname" 2>/dev/null &&
47                                 (eval "$condition" "$toolname")
48                         then
49                                 if test -n "$preamble"
50                                 then
51                                         printf "%s\n" "$preamble"
52                                         preamble=
53                                 fi
54                                 shown_any=yes
55                                 printf "%s%s\n" "$per_line_prefix" "$toolname"
56                         fi
57                 done
58
59                 if test -n "$extra_content"
60                 then
61                         if test -n "$preamble"
62                         then
63                                 # Note: no '\n' here since we don't want a
64                                 # blank line if there is no initial content.
65                                 printf "%s" "$preamble"
66                                 preamble=
67                         fi
68                         shown_any=yes
69                         printf "\n%s\n" "$extra_content"
70                 fi
71
72                 if test -n "$preamble" && test -n "$not_found_msg"
73                 then
74                         printf "%s\n" "$not_found_msg"
75                 fi
76
77                 test -n "$shown_any"
78         }
79 }
80
81 diff_mode() {
82         test "$TOOL_MODE" = diff
83 }
84
85 merge_mode() {
86         test "$TOOL_MODE" = merge
87 }
88
89 translate_merge_tool_path () {
90         echo "$1"
91 }
92
93 check_unchanged () {
94         if test "$MERGED" -nt "$BACKUP"
95         then
96                 status=0
97         else
98                 while true
99                 do
100                         echo "$MERGED seems unchanged."
101                         printf "Was the merge successful? [y/n] "
102                         read answer || return 1
103                         case "$answer" in
104                         y*|Y*) status=0; break ;;
105                         n*|N*) status=1; break ;;
106                         esac
107                 done
108         fi
109 }
110
111 valid_tool () {
112         setup_tool "$1" && return 0
113         cmd=$(get_merge_tool_cmd "$1")
114         test -n "$cmd"
115 }
116
117 setup_user_tool () {
118         merge_tool_cmd=$(get_merge_tool_cmd "$tool")
119         test -n "$merge_tool_cmd" || return 1
120
121         diff_cmd () {
122                 ( eval $merge_tool_cmd )
123                 status=$?
124                 return $status
125         }
126
127         merge_cmd () {
128                 trust_exit_code=$(git config --bool \
129                         "mergetool.$1.trustExitCode" || echo false)
130                 if test "$trust_exit_code" = "false"
131                 then
132                         touch "$BACKUP"
133                         ( eval $merge_tool_cmd )
134                         status=$?
135                         check_unchanged
136                 else
137                         ( eval $merge_tool_cmd )
138                         status=$?
139                 fi
140                 return $status
141         }
142 }
143
144 setup_tool () {
145         tool="$1"
146
147         # Fallback definitions, to be overridden by tools.
148         can_merge () {
149                 return 0
150         }
151
152         can_diff () {
153                 return 0
154         }
155
156         diff_cmd () {
157                 status=1
158                 return $status
159         }
160
161         merge_cmd () {
162                 status=1
163                 return $status
164         }
165
166         translate_merge_tool_path () {
167                 echo "$1"
168         }
169
170         if ! test -f "$MERGE_TOOLS_DIR/$tool"
171         then
172                 setup_user_tool
173                 return $?
174         fi
175
176         # Load the redefined functions
177         . "$MERGE_TOOLS_DIR/$tool"
178         # Now let the user override the default command for the tool.  If
179         # they have not done so then this will return 1 which we ignore.
180         setup_user_tool
181
182         if merge_mode && ! can_merge
183         then
184                 echo "error: '$tool' can not be used to resolve merges" >&2
185                 return 1
186         elif diff_mode && ! can_diff
187         then
188                 echo "error: '$tool' can only be used to resolve merges" >&2
189                 return 1
190         fi
191         return 0
192 }
193
194 get_merge_tool_cmd () {
195         merge_tool="$1"
196         if diff_mode
197         then
198                 git config "difftool.$merge_tool.cmd" ||
199                 git config "mergetool.$merge_tool.cmd"
200         else
201                 git config "mergetool.$merge_tool.cmd"
202         fi
203 }
204
205 # Entry point for running tools
206 run_merge_tool () {
207         # If GIT_PREFIX is empty then we cannot use it in tools
208         # that expect to be able to chdir() to its value.
209         GIT_PREFIX=${GIT_PREFIX:-.}
210         export GIT_PREFIX
211
212         merge_tool_path=$(get_merge_tool_path "$1") || exit
213         base_present="$2"
214         status=0
215
216         # Bring tool-specific functions into scope
217         setup_tool "$1" || return 1
218
219         if merge_mode
220         then
221                 run_merge_cmd "$1"
222         else
223                 run_diff_cmd "$1"
224         fi
225         return $status
226 }
227
228 # Run a either a configured or built-in diff tool
229 run_diff_cmd () {
230         diff_cmd "$1"
231 }
232
233 # Run a either a configured or built-in merge tool
234 run_merge_cmd () {
235         merge_cmd "$1"
236 }
237
238 list_merge_tool_candidates () {
239         if merge_mode
240         then
241                 tools="tortoisemerge"
242         else
243                 tools="kompare"
244         fi
245         if test -n "$DISPLAY"
246         then
247                 if test -n "$GNOME_DESKTOP_SESSION_ID"
248                 then
249                         tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
250                 else
251                         tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
252                 fi
253                 tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3 codecompare"
254         fi
255         case "${VISUAL:-$EDITOR}" in
256         *vim*)
257                 tools="$tools vimdiff emerge"
258                 ;;
259         *)
260                 tools="$tools emerge vimdiff"
261                 ;;
262         esac
263 }
264
265 show_tool_help () {
266         tool_opt="'git ${TOOL_MODE}tool --tool-<tool>'"
267
268         tab='   '
269         LF='
270 '
271         any_shown=no
272
273         cmd_name=${TOOL_MODE}tool
274         config_tools=$({
275                 diff_mode && list_config_tools difftool "$tab$tab"
276                 list_config_tools mergetool "$tab$tab"
277         } | sort)
278         extra_content=
279         if test -n "$config_tools"
280         then
281                 extra_content="${tab}user-defined:${LF}$config_tools"
282         fi
283
284         show_tool_names 'mode_ok && is_available' "$tab$tab" \
285                 "$tool_opt may be set to one of the following:" \
286                 "No suitable tool for 'git $cmd_name --tool=<tool>' found." \
287                 "$extra_content" &&
288                 any_shown=yes
289
290         show_tool_names 'mode_ok && ! is_available' "$tab$tab" \
291                 "${LF}The following tools are valid, but not currently available:" &&
292                 any_shown=yes
293
294         if test "$any_shown" = yes
295         then
296                 echo
297                 echo "Some of the tools listed above only work in a windowed"
298                 echo "environment. If run in a terminal-only session, they will fail."
299         fi
300         exit 0
301 }
302
303 guess_merge_tool () {
304         list_merge_tool_candidates
305         cat >&2 <<-EOF
306
307         This message is displayed because '$TOOL_MODE.tool' is not configured.
308         See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details.
309         'git ${TOOL_MODE}tool' will now attempt to use one of the following tools:
310         $tools
311         EOF
312
313         # Loop over each candidate and stop when a valid merge tool is found.
314         for tool in $tools
315         do
316                 is_available "$tool" && echo "$tool" && return 0
317         done
318
319         echo >&2 "No known ${TOOL_MODE} tool is available."
320         return 1
321 }
322
323 get_configured_merge_tool () {
324         # Diff mode first tries diff.tool and falls back to merge.tool.
325         # Merge mode only checks merge.tool
326         if diff_mode
327         then
328                 merge_tool=$(git config diff.tool || git config merge.tool)
329         else
330                 merge_tool=$(git config merge.tool)
331         fi
332         if test -n "$merge_tool" && ! valid_tool "$merge_tool"
333         then
334                 echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
335                 echo >&2 "Resetting to default..."
336                 return 1
337         fi
338         echo "$merge_tool"
339 }
340
341 get_merge_tool_path () {
342         # A merge tool has been set, so verify that it's valid.
343         merge_tool="$1"
344         if ! valid_tool "$merge_tool"
345         then
346                 echo >&2 "Unknown merge tool $merge_tool"
347                 exit 1
348         fi
349         if diff_mode
350         then
351                 merge_tool_path=$(git config difftool."$merge_tool".path ||
352                                   git config mergetool."$merge_tool".path)
353         else
354                 merge_tool_path=$(git config mergetool."$merge_tool".path)
355         fi
356         if test -z "$merge_tool_path"
357         then
358                 merge_tool_path=$(translate_merge_tool_path "$merge_tool")
359         fi
360         if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
361                 ! type "$merge_tool_path" >/dev/null 2>&1
362         then
363                 echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
364                          "'$merge_tool_path'"
365                 exit 1
366         fi
367         echo "$merge_tool_path"
368 }
369
370 get_merge_tool () {
371         # Check if a merge tool has been configured
372         merge_tool=$(get_configured_merge_tool)
373         # Try to guess an appropriate merge tool if no tool has been set.
374         if test -z "$merge_tool"
375         then
376                 merge_tool=$(guess_merge_tool) || exit
377         fi
378         echo "$merge_tool"
379 }