Merge branch 'nd/literal-pathspecs'
[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 diffmerge ecmerge"
254                 tools="$tools p4merge araxis bc3 codecompare"
255         fi
256         case "${VISUAL:-$EDITOR}" in
257         *vim*)
258                 tools="$tools vimdiff emerge"
259                 ;;
260         *)
261                 tools="$tools emerge vimdiff"
262                 ;;
263         esac
264 }
265
266 show_tool_help () {
267         tool_opt="'git ${TOOL_MODE}tool --tool=<tool>'"
268
269         tab='   '
270         LF='
271 '
272         any_shown=no
273
274         cmd_name=${TOOL_MODE}tool
275         config_tools=$({
276                 diff_mode && list_config_tools difftool "$tab$tab"
277                 list_config_tools mergetool "$tab$tab"
278         } | sort)
279         extra_content=
280         if test -n "$config_tools"
281         then
282                 extra_content="${tab}user-defined:${LF}$config_tools"
283         fi
284
285         show_tool_names 'mode_ok && is_available' "$tab$tab" \
286                 "$tool_opt may be set to one of the following:" \
287                 "No suitable tool for 'git $cmd_name --tool=<tool>' found." \
288                 "$extra_content" &&
289                 any_shown=yes
290
291         show_tool_names 'mode_ok && ! is_available' "$tab$tab" \
292                 "${LF}The following tools are valid, but not currently available:" &&
293                 any_shown=yes
294
295         if test "$any_shown" = yes
296         then
297                 echo
298                 echo "Some of the tools listed above only work in a windowed"
299                 echo "environment. If run in a terminal-only session, they will fail."
300         fi
301         exit 0
302 }
303
304 guess_merge_tool () {
305         list_merge_tool_candidates
306         cat >&2 <<-EOF
307
308         This message is displayed because '$TOOL_MODE.tool' is not configured.
309         See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details.
310         'git ${TOOL_MODE}tool' will now attempt to use one of the following tools:
311         $tools
312         EOF
313
314         # Loop over each candidate and stop when a valid merge tool is found.
315         for tool in $tools
316         do
317                 is_available "$tool" && echo "$tool" && return 0
318         done
319
320         echo >&2 "No known ${TOOL_MODE} tool is available."
321         return 1
322 }
323
324 get_configured_merge_tool () {
325         # Diff mode first tries diff.tool and falls back to merge.tool.
326         # Merge mode only checks merge.tool
327         if diff_mode
328         then
329                 merge_tool=$(git config diff.tool || git config merge.tool)
330         else
331                 merge_tool=$(git config merge.tool)
332         fi
333         if test -n "$merge_tool" && ! valid_tool "$merge_tool"
334         then
335                 echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
336                 echo >&2 "Resetting to default..."
337                 return 1
338         fi
339         echo "$merge_tool"
340 }
341
342 get_merge_tool_path () {
343         # A merge tool has been set, so verify that it's valid.
344         merge_tool="$1"
345         if ! valid_tool "$merge_tool"
346         then
347                 echo >&2 "Unknown merge tool $merge_tool"
348                 exit 1
349         fi
350         if diff_mode
351         then
352                 merge_tool_path=$(git config difftool."$merge_tool".path ||
353                                   git config mergetool."$merge_tool".path)
354         else
355                 merge_tool_path=$(git config mergetool."$merge_tool".path)
356         fi
357         if test -z "$merge_tool_path"
358         then
359                 merge_tool_path=$(translate_merge_tool_path "$merge_tool")
360         fi
361         if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
362                 ! type "$merge_tool_path" >/dev/null 2>&1
363         then
364                 echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
365                          "'$merge_tool_path'"
366                 exit 1
367         fi
368         echo "$merge_tool_path"
369 }
370
371 get_merge_tool () {
372         # Check if a merge tool has been configured
373         merge_tool=$(get_configured_merge_tool)
374         # Try to guess an appropriate merge tool if no tool has been set.
375         if test -z "$merge_tool"
376         then
377                 merge_tool=$(guess_merge_tool) || exit
378         fi
379         echo "$merge_tool"
380 }