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