t: fix severe &&-chain breakage
[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 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                 return 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*) return 0 ;;
104                         n*|N*) return 1 ;;
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_user_tool () {
117         merge_tool_cmd=$(get_merge_tool_cmd "$tool")
118         test -n "$merge_tool_cmd" || return 1
119
120         diff_cmd () {
121                 ( eval $merge_tool_cmd )
122         }
123
124         merge_cmd () {
125                 trust_exit_code=$(git config --bool \
126                         "mergetool.$1.trustExitCode" || echo false)
127                 if test "$trust_exit_code" = "false"
128                 then
129                         touch "$BACKUP"
130                         ( eval $merge_tool_cmd )
131                         check_unchanged
132                 else
133                         ( eval $merge_tool_cmd )
134                 fi
135         }
136 }
137
138 setup_tool () {
139         tool="$1"
140
141         # Fallback definitions, to be overridden by tools.
142         can_merge () {
143                 return 0
144         }
145
146         can_diff () {
147                 return 0
148         }
149
150         diff_cmd () {
151                 return 1
152         }
153
154         merge_cmd () {
155                 return 1
156         }
157
158         translate_merge_tool_path () {
159                 echo "$1"
160         }
161
162         if ! test -f "$MERGE_TOOLS_DIR/$tool"
163         then
164                 setup_user_tool
165                 return $?
166         fi
167
168         # Load the redefined functions
169         . "$MERGE_TOOLS_DIR/$tool"
170         # Now let the user override the default command for the tool.  If
171         # they have not done so then this will return 1 which we ignore.
172         setup_user_tool
173
174         if merge_mode && ! can_merge
175         then
176                 echo "error: '$tool' can not be used to resolve merges" >&2
177                 return 1
178         elif diff_mode && ! can_diff
179         then
180                 echo "error: '$tool' can only be used to resolve merges" >&2
181                 return 1
182         fi
183         return 0
184 }
185
186 get_merge_tool_cmd () {
187         merge_tool="$1"
188         if diff_mode
189         then
190                 git config "difftool.$merge_tool.cmd" ||
191                 git config "mergetool.$merge_tool.cmd"
192         else
193                 git config "mergetool.$merge_tool.cmd"
194         fi
195 }
196
197 # Entry point for running tools
198 run_merge_tool () {
199         # If GIT_PREFIX is empty then we cannot use it in tools
200         # that expect to be able to chdir() to its value.
201         GIT_PREFIX=${GIT_PREFIX:-.}
202         export GIT_PREFIX
203
204         merge_tool_path=$(get_merge_tool_path "$1") || exit
205         base_present="$2"
206
207         # Bring tool-specific functions into scope
208         setup_tool "$1" || return 1
209
210         if merge_mode
211         then
212                 run_merge_cmd "$1"
213         else
214                 run_diff_cmd "$1"
215         fi
216 }
217
218 # Run a either a configured or built-in diff tool
219 run_diff_cmd () {
220         diff_cmd "$1"
221 }
222
223 # Run a either a configured or built-in merge tool
224 run_merge_cmd () {
225         merge_cmd "$1"
226 }
227
228 list_merge_tool_candidates () {
229         if merge_mode
230         then
231                 tools="tortoisemerge"
232         else
233                 tools="kompare"
234         fi
235         if test -n "$DISPLAY"
236         then
237                 if test -n "$GNOME_DESKTOP_SESSION_ID"
238                 then
239                         tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
240                 else
241                         tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
242                 fi
243                 tools="$tools gvimdiff diffuse diffmerge ecmerge"
244                 tools="$tools p4merge araxis bc codecompare"
245         fi
246         case "${VISUAL:-$EDITOR}" in
247         *vim*)
248                 tools="$tools vimdiff emerge"
249                 ;;
250         *)
251                 tools="$tools emerge vimdiff"
252                 ;;
253         esac
254 }
255
256 show_tool_help () {
257         tool_opt="'git ${TOOL_MODE}tool --tool=<tool>'"
258
259         tab='   '
260         LF='
261 '
262         any_shown=no
263
264         cmd_name=${TOOL_MODE}tool
265         config_tools=$({
266                 diff_mode && list_config_tools difftool "$tab$tab"
267                 list_config_tools mergetool "$tab$tab"
268         } | sort)
269         extra_content=
270         if test -n "$config_tools"
271         then
272                 extra_content="${tab}user-defined:${LF}$config_tools"
273         fi
274
275         show_tool_names 'mode_ok && is_available' "$tab$tab" \
276                 "$tool_opt may be set to one of the following:" \
277                 "No suitable tool for 'git $cmd_name --tool=<tool>' found." \
278                 "$extra_content" &&
279                 any_shown=yes
280
281         show_tool_names 'mode_ok && ! is_available' "$tab$tab" \
282                 "${LF}The following tools are valid, but not currently available:" &&
283                 any_shown=yes
284
285         if test "$any_shown" = yes
286         then
287                 echo
288                 echo "Some of the tools listed above only work in a windowed"
289                 echo "environment. If run in a terminal-only session, they will fail."
290         fi
291         exit 0
292 }
293
294 guess_merge_tool () {
295         list_merge_tool_candidates
296         cat >&2 <<-EOF
297
298         This message is displayed because '$TOOL_MODE.tool' is not configured.
299         See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details.
300         'git ${TOOL_MODE}tool' will now attempt to use one of the following tools:
301         $tools
302         EOF
303
304         # Loop over each candidate and stop when a valid merge tool is found.
305         for tool in $tools
306         do
307                 is_available "$tool" && echo "$tool" && return 0
308         done
309
310         echo >&2 "No known ${TOOL_MODE} tool is available."
311         return 1
312 }
313
314 get_configured_merge_tool () {
315         # Diff mode first tries diff.tool and falls back to merge.tool.
316         # Merge mode only checks merge.tool
317         if diff_mode
318         then
319                 merge_tool=$(git config diff.tool || git config merge.tool)
320         else
321                 merge_tool=$(git config merge.tool)
322         fi
323         if test -n "$merge_tool" && ! valid_tool "$merge_tool"
324         then
325                 echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
326                 echo >&2 "Resetting to default..."
327                 return 1
328         fi
329         echo "$merge_tool"
330 }
331
332 get_merge_tool_path () {
333         # A merge tool has been set, so verify that it's valid.
334         merge_tool="$1"
335         if ! valid_tool "$merge_tool"
336         then
337                 echo >&2 "Unknown merge tool $merge_tool"
338                 exit 1
339         fi
340         if diff_mode
341         then
342                 merge_tool_path=$(git config difftool."$merge_tool".path ||
343                                   git config mergetool."$merge_tool".path)
344         else
345                 merge_tool_path=$(git config mergetool."$merge_tool".path)
346         fi
347         if test -z "$merge_tool_path"
348         then
349                 merge_tool_path=$(translate_merge_tool_path "$merge_tool")
350         fi
351         if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
352                 ! type "$merge_tool_path" >/dev/null 2>&1
353         then
354                 echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
355                          "'$merge_tool_path'"
356                 exit 1
357         fi
358         echo "$merge_tool_path"
359 }
360
361 get_merge_tool () {
362         # Check if a merge tool has been configured
363         merge_tool=$(get_configured_merge_tool)
364         # Try to guess an appropriate merge tool if no tool has been set.
365         if test -z "$merge_tool"
366         then
367                 merge_tool=$(guess_merge_tool) || exit
368         fi
369         echo "$merge_tool"
370 }