Fifth batch for 2.1
[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                 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_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                 status=$?
123                 return $status
124         }
125
126         merge_cmd () {
127                 trust_exit_code=$(git config --bool \
128                         "mergetool.$1.trustExitCode" || echo false)
129                 if test "$trust_exit_code" = "false"
130                 then
131                         touch "$BACKUP"
132                         ( eval $merge_tool_cmd )
133                         status=$?
134                         check_unchanged
135                 else
136                         ( eval $merge_tool_cmd )
137                         status=$?
138                 fi
139                 return $status
140         }
141 }
142
143 setup_tool () {
144         tool="$1"
145
146         # Fallback definitions, to be overridden by tools.
147         can_merge () {
148                 return 0
149         }
150
151         can_diff () {
152                 return 0
153         }
154
155         diff_cmd () {
156                 status=1
157                 return $status
158         }
159
160         merge_cmd () {
161                 status=1
162                 return $status
163         }
164
165         translate_merge_tool_path () {
166                 echo "$1"
167         }
168
169         if ! test -f "$MERGE_TOOLS_DIR/$tool"
170         then
171                 setup_user_tool
172                 return $?
173         fi
174
175         # Load the redefined functions
176         . "$MERGE_TOOLS_DIR/$tool"
177         # Now let the user override the default command for the tool.  If
178         # they have not done so then this will return 1 which we ignore.
179         setup_user_tool
180
181         if merge_mode && ! can_merge
182         then
183                 echo "error: '$tool' can not be used to resolve merges" >&2
184                 return 1
185         elif diff_mode && ! can_diff
186         then
187                 echo "error: '$tool' can only be used to resolve merges" >&2
188                 return 1
189         fi
190         return 0
191 }
192
193 get_merge_tool_cmd () {
194         merge_tool="$1"
195         if diff_mode
196         then
197                 git config "difftool.$merge_tool.cmd" ||
198                 git config "mergetool.$merge_tool.cmd"
199         else
200                 git config "mergetool.$merge_tool.cmd"
201         fi
202 }
203
204 # Entry point for running tools
205 run_merge_tool () {
206         # If GIT_PREFIX is empty then we cannot use it in tools
207         # that expect to be able to chdir() to its value.
208         GIT_PREFIX=${GIT_PREFIX:-.}
209         export GIT_PREFIX
210
211         merge_tool_path=$(get_merge_tool_path "$1") || exit
212         base_present="$2"
213         status=0
214
215         # Bring tool-specific functions into scope
216         setup_tool "$1" || return 1
217
218         if merge_mode
219         then
220                 run_merge_cmd "$1"
221         else
222                 run_diff_cmd "$1"
223         fi
224         return $status
225 }
226
227 # Run a either a configured or built-in diff tool
228 run_diff_cmd () {
229         diff_cmd "$1"
230 }
231
232 # Run a either a configured or built-in merge tool
233 run_merge_cmd () {
234         merge_cmd "$1"
235 }
236
237 list_merge_tool_candidates () {
238         if merge_mode
239         then
240                 tools="tortoisemerge"
241         else
242                 tools="kompare"
243         fi
244         if test -n "$DISPLAY"
245         then
246                 if test -n "$GNOME_DESKTOP_SESSION_ID"
247                 then
248                         tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
249                 else
250                         tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
251                 fi
252                 tools="$tools gvimdiff diffuse diffmerge ecmerge"
253                 tools="$tools 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 }