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