1 #!/bin/sh
2 # git-mergetool--lib is a library for common merge tool functions
3 diff_mode() {
4 test "$TOOL_MODE" = diff
5 }
7 merge_mode() {
8 test "$TOOL_MODE" = merge
9 }
11 translate_merge_tool_path () {
12 case "$1" in
13 vimdiff)
14 echo vim
15 ;;
16 gvimdiff)
17 echo gvim
18 ;;
19 emerge)
20 echo emacs
21 ;;
22 araxis)
23 echo compare
24 ;;
25 *)
26 echo "$1"
27 ;;
28 esac
29 }
31 check_unchanged () {
32 if test "$MERGED" -nt "$BACKUP"; then
33 status=0
34 else
35 while true; do
36 echo "$MERGED seems unchanged."
37 printf "Was the merge successful? [y/n] "
38 read answer
39 case "$answer" in
40 y*|Y*) status=0; break ;;
41 n*|N*) status=1; break ;;
42 esac
43 done
44 fi
45 }
47 valid_tool () {
48 case "$1" in
49 kdiff3 | tkdiff | xxdiff | meld | opendiff | \
50 emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge)
51 ;; # happy
52 tortoisemerge)
53 if ! merge_mode; then
54 return 1
55 fi
56 ;;
57 kompare)
58 if ! diff_mode; then
59 return 1
60 fi
61 ;;
62 *)
63 if test -z "$(get_merge_tool_cmd "$1")"; then
64 return 1
65 fi
66 ;;
67 esac
68 }
70 get_merge_tool_cmd () {
71 # Prints the custom command for a merge tool
72 if test -n "$1"; then
73 merge_tool="$1"
74 else
75 merge_tool="$(get_merge_tool)"
76 fi
77 if diff_mode; then
78 echo "$(git config difftool.$merge_tool.cmd ||
79 git config mergetool.$merge_tool.cmd)"
80 else
81 echo "$(git config mergetool.$merge_tool.cmd)"
82 fi
83 }
85 run_merge_tool () {
86 merge_tool_path="$(get_merge_tool_path "$1")" || exit
87 base_present="$2"
88 status=0
90 case "$1" in
91 kdiff3)
92 if merge_mode; then
93 if $base_present; then
94 ("$merge_tool_path" --auto \
95 --L1 "$MERGED (Base)" \
96 --L2 "$MERGED (Local)" \
97 --L3 "$MERGED (Remote)" \
98 -o "$MERGED" \
99 "$BASE" "$LOCAL" "$REMOTE" \
100 > /dev/null 2>&1)
101 else
102 ("$merge_tool_path" --auto \
103 --L1 "$MERGED (Local)" \
104 --L2 "$MERGED (Remote)" \
105 -o "$MERGED" \
106 "$LOCAL" "$REMOTE" \
107 > /dev/null 2>&1)
108 fi
109 status=$?
110 else
111 ("$merge_tool_path" --auto \
112 --L1 "$MERGED (A)" \
113 --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
114 > /dev/null 2>&1)
115 fi
116 ;;
117 kompare)
118 "$merge_tool_path" "$LOCAL" "$REMOTE"
119 ;;
120 tkdiff)
121 if merge_mode; then
122 if $base_present; then
123 "$merge_tool_path" -a "$BASE" \
124 -o "$MERGED" "$LOCAL" "$REMOTE"
125 else
126 "$merge_tool_path" \
127 -o "$MERGED" "$LOCAL" "$REMOTE"
128 fi
129 status=$?
130 else
131 "$merge_tool_path" "$LOCAL" "$REMOTE"
132 fi
133 ;;
134 p4merge)
135 if merge_mode; then
136 touch "$BACKUP"
137 if $base_present; then
138 "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
139 else
140 "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED"
141 fi
142 check_unchanged
143 else
144 "$merge_tool_path" "$LOCAL" "$REMOTE"
145 fi
146 ;;
147 meld)
148 if merge_mode; then
149 touch "$BACKUP"
150 "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
151 check_unchanged
152 else
153 "$merge_tool_path" "$LOCAL" "$REMOTE"
154 fi
155 ;;
156 diffuse)
157 if merge_mode; then
158 touch "$BACKUP"
159 if $base_present; then
160 "$merge_tool_path" \
161 "$LOCAL" "$MERGED" "$REMOTE" \
162 "$BASE" | cat
163 else
164 "$merge_tool_path" \
165 "$LOCAL" "$MERGED" "$REMOTE" | cat
166 fi
167 check_unchanged
168 else
169 "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
170 fi
171 ;;
172 vimdiff|gvimdiff)
173 if merge_mode; then
174 touch "$BACKUP"
175 "$merge_tool_path" -f -d -c "wincmd l" \
176 "$LOCAL" "$MERGED" "$REMOTE"
177 check_unchanged
178 else
179 "$merge_tool_path" -f -d -c "wincmd l" \
180 "$LOCAL" "$REMOTE"
181 fi
182 ;;
183 xxdiff)
184 if merge_mode; then
185 touch "$BACKUP"
186 if $base_present; then
187 "$merge_tool_path" -X --show-merged-pane \
188 -R 'Accel.SaveAsMerged: "Ctrl-S"' \
189 -R 'Accel.Search: "Ctrl+F"' \
190 -R 'Accel.SearchForward: "Ctrl-G"' \
191 --merged-file "$MERGED" \
192 "$LOCAL" "$BASE" "$REMOTE"
193 else
194 "$merge_tool_path" -X $extra \
195 -R 'Accel.SaveAsMerged: "Ctrl-S"' \
196 -R 'Accel.Search: "Ctrl+F"' \
197 -R 'Accel.SearchForward: "Ctrl-G"' \
198 --merged-file "$MERGED" \
199 "$LOCAL" "$REMOTE"
200 fi
201 check_unchanged
202 else
203 "$merge_tool_path" \
204 -R 'Accel.Search: "Ctrl+F"' \
205 -R 'Accel.SearchForward: "Ctrl-G"' \
206 "$LOCAL" "$REMOTE"
207 fi
208 ;;
209 opendiff)
210 if merge_mode; then
211 touch "$BACKUP"
212 if $base_present; then
213 "$merge_tool_path" "$LOCAL" "$REMOTE" \
214 -ancestor "$BASE" \
215 -merge "$MERGED" | cat
216 else
217 "$merge_tool_path" "$LOCAL" "$REMOTE" \
218 -merge "$MERGED" | cat
219 fi
220 check_unchanged
221 else
222 "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
223 fi
224 ;;
225 ecmerge)
226 if merge_mode; then
227 touch "$BACKUP"
228 if $base_present; then
229 "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
230 --default --mode=merge3 --to="$MERGED"
231 else
232 "$merge_tool_path" "$LOCAL" "$REMOTE" \
233 --default --mode=merge2 --to="$MERGED"
234 fi
235 check_unchanged
236 else
237 "$merge_tool_path" --default --mode=diff2 \
238 "$LOCAL" "$REMOTE"
239 fi
240 ;;
241 emerge)
242 if merge_mode; then
243 if $base_present; then
244 "$merge_tool_path" \
245 -f emerge-files-with-ancestor-command \
246 "$LOCAL" "$REMOTE" "$BASE" \
247 "$(basename "$MERGED")"
248 else
249 "$merge_tool_path" \
250 -f emerge-files-command \
251 "$LOCAL" "$REMOTE" \
252 "$(basename "$MERGED")"
253 fi
254 status=$?
255 else
256 "$merge_tool_path" -f emerge-files-command \
257 "$LOCAL" "$REMOTE"
258 fi
259 ;;
260 tortoisemerge)
261 if $base_present; then
262 touch "$BACKUP"
263 "$merge_tool_path" \
264 -base:"$BASE" -mine:"$LOCAL" \
265 -theirs:"$REMOTE" -merged:"$MERGED"
266 check_unchanged
267 else
268 echo "TortoiseMerge cannot be used without a base" 1>&2
269 status=1
270 fi
271 ;;
272 araxis)
273 if merge_mode; then
274 touch "$BACKUP"
275 if $base_present; then
276 "$merge_tool_path" -wait -merge -3 -a1 \
277 "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \
278 >/dev/null 2>&1
279 else
280 "$merge_tool_path" -wait -2 \
281 "$LOCAL" "$REMOTE" "$MERGED" \
282 >/dev/null 2>&1
283 fi
284 check_unchanged
285 else
286 "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \
287 >/dev/null 2>&1
288 fi
289 ;;
290 *)
291 merge_tool_cmd="$(get_merge_tool_cmd "$1")"
292 if test -z "$merge_tool_cmd"; then
293 if merge_mode; then
294 status=1
295 fi
296 break
297 fi
298 if merge_mode; then
299 trust_exit_code="$(git config --bool \
300 mergetool."$1".trustExitCode || echo false)"
301 if test "$trust_exit_code" = "false"; then
302 touch "$BACKUP"
303 ( eval $merge_tool_cmd )
304 check_unchanged
305 else
306 ( eval $merge_tool_cmd )
307 status=$?
308 fi
309 else
310 ( eval $merge_tool_cmd )
311 fi
312 ;;
313 esac
314 return $status
315 }
317 guess_merge_tool () {
318 if merge_mode; then
319 tools="tortoisemerge"
320 else
321 tools="kompare"
322 fi
323 if test -n "$DISPLAY"; then
324 if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
325 tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
326 else
327 tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
328 fi
329 tools="$tools gvimdiff diffuse ecmerge p4merge araxis"
330 fi
331 case "${VISUAL:-$EDITOR}" in
332 *vim*)
333 tools="$tools vimdiff emerge"
334 ;;
335 *)
336 tools="$tools emerge vimdiff"
337 ;;
338 esac
339 echo >&2 "merge tool candidates: $tools"
341 # Loop over each candidate and stop when a valid merge tool is found.
342 for i in $tools
343 do
344 merge_tool_path="$(translate_merge_tool_path "$i")"
345 if type "$merge_tool_path" > /dev/null 2>&1; then
346 echo "$i"
347 return 0
348 fi
349 done
351 echo >&2 "No known merge resolution program available."
352 return 1
353 }
355 get_configured_merge_tool () {
356 # Diff mode first tries diff.tool and falls back to merge.tool.
357 # Merge mode only checks merge.tool
358 if diff_mode; then
359 merge_tool=$(git config diff.tool || git config merge.tool)
360 else
361 merge_tool=$(git config merge.tool)
362 fi
363 if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
364 echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
365 echo >&2 "Resetting to default..."
366 return 1
367 fi
368 echo "$merge_tool"
369 }
371 get_merge_tool_path () {
372 # A merge tool has been set, so verify that it's valid.
373 if test -n "$1"; then
374 merge_tool="$1"
375 else
376 merge_tool="$(get_merge_tool)"
377 fi
378 if ! valid_tool "$merge_tool"; then
379 echo >&2 "Unknown merge tool $merge_tool"
380 exit 1
381 fi
382 if diff_mode; then
383 merge_tool_path=$(git config difftool."$merge_tool".path ||
384 git config mergetool."$merge_tool".path)
385 else
386 merge_tool_path=$(git config mergetool."$merge_tool".path)
387 fi
388 if test -z "$merge_tool_path"; then
389 merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
390 fi
391 if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
392 ! type "$merge_tool_path" > /dev/null 2>&1; then
393 echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
394 "'$merge_tool_path'"
395 exit 1
396 fi
397 echo "$merge_tool_path"
398 }
400 get_merge_tool () {
401 # Check if a merge tool has been configured
402 merge_tool=$(get_configured_merge_tool)
403 # Try to guess an appropriate merge tool if no tool has been set.
404 if test -z "$merge_tool"; then
405 merge_tool="$(guess_merge_tool)" || exit
406 fi
407 echo "$merge_tool"
408 }