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