3 # This program uses merge tools to stage and compare commits
5 # Copyright (c) 2008 David Aguilar
7 # Adapted from git-mergetool.sh
8 # Copyright (c) 2006 Theodore Y. Ts'o
10 # This file is licensed under the GPL v2, or a later version
13 [--tool=tool] [--no-prompt]
14 [--commit=ref] [--start=ref --end=ref]
15 [--] [file to view] ...'
22 keep_backup_mode
="$(git config --bool merge.keepBackup || echo true)"
23 PREFIX
=.git-difftool.
"$$".
"$(date +%N)".
27 # whether to keep the .orig file
28 test "$keep_backup_mode" = "true"
33 expr "z$1" : 'z-[^=]*=\(.*\)'
37 # does the index contain staged changes?
42 # are there changes in the requested comparison?
43 test -n "$modified_mode"
46 commitish_present
() {
47 # are we comparing against an arbitrary commit?
52 # are we running interactively?
53 ! test -n "$no_prompt"
57 # are we using --start=REF and --end=REF
58 test -n "$start" && test -n "$end"
61 merge_dir_missing
() {
62 # does dirname($MERGED) exist in the work tree?
63 test -n "$non_existant_dir"
67 # whether we have 3 things to compare (index, worktree, other)
68 index_present
&& modified_present
&& ! use_rev_range
72 # returns the appopriate list of differences based on the mode
73 if use_rev_range
; then
74 git
diff --name-only "$start"..
"$end" -- "$@" 2>/dev
/null
75 elif commitish_present
; then
76 git
diff --name-only "$commitish" -- "$@" 2>/dev
/null
78 git
diff --name-only -- "$@" 2>/dev
/null
83 # returns any staged changes
84 git
diff --name-only --cached "$@" 2>/dev
/null
87 cleanup_temp_files
() {
88 # removes temporary files
89 if test -n "$MERGED"; then
90 if keep_backup
&& test "$MERGED" -nt "$BACKUP"; then
91 test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
92 rm -f -- "$LOCAL" "$REMOTE" "$BASE"
94 rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
96 test -n "$MERGEDIR" && rmdir -p "$MERGEDIR"
107 # prepares temporary files and launches the appropriate merge tool
110 modified_mode
=$
(modified_files
"$MERGED")
111 index_mode
=$
(staged_files
"$MERGED")
113 if ! modified_present
&& use_rev_range
; then
114 echo "$MERGED: no changes between '$start' and '$end'."
116 elif ! modified_present
&& ! index_present
; then
117 if ! test -f "$MERGED"; then
118 echo "$MERGED: file not found"
120 echo "$MERGED: file is unchanged."
125 # handle comparing a file that doesn't exist in the current checkout
126 MERGEDIR
=$
(dirname "$MERGED")
128 test -d "$MERGEDIR" || non_existant_dir
=true
129 if merge_dir_missing
; then
130 mkdir
-p -- "$MERGEDIR"
135 ext
="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
136 BACKUP
="./$MERGED.BACKUP.$ext"
137 test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
139 if use_rev_range
; then
140 # we're comparing two arbitrary commits
141 BASE
="./$MERGED.CURRENT.$ext"
142 LOCAL
="./$MERGED.START.$ext"
143 REMOTE
="./$MERGED.END.$ext"
151 # detects renames.. sweet!
152 oldname
=$
(git
diff --follow "$start"..
"$end" -- "$MERGED" |
154 grep '^rename from' |
155 sed -e 's/rename from //')
157 test -n "$oldname" && startname
="$oldname"
159 if ! git show
"$start":"$startname" > "$LOCAL" 2>/dev
/null
; then
160 if should_prompt
; then
163 echo -n "'$startname' does not exist at $start."
166 cp -- "$LOCAL" "$BASE"
168 if ! git show
"$end":"$MERGED" > "$REMOTE" 2>/dev
/null
; then
169 if should_prompt
; then
172 echo -n "'$MERGED' does not exist at $end."
175 ! test -f "$BASE" && cp -- "$REMOTE" "$BASE"
178 # $BASE could be used by custom mergetool commands
179 if test -f "$MERGED"; then
180 cp -- "$MERGED" "$BASE"
183 # We're either comparing against the index or an
185 # The goal is to re-use existing mergetool.$tool.cmd
186 # configurations so we provide $BASE $LOCAL and $REMOTE
187 if commitish_present
; then
194 base
=${commitish-HEAD}
196 BASE
="./$MERGED.$HEAD.$ext"
197 LOCAL
="./$MERGED.INDEX.$ext"
198 REMOTE
="./$MERGED.CURRENT.$ext"
202 if ! git show
"$base":"$MERGED" > "$BASE" 2>&1; then
205 echo -n "'$MERGED' does not exist at $base."
208 if commitish_present
; then
213 # If changes are present in the index use them as $LOCAL
214 # but only if $MERGED exists at $base
215 if ! commitish_present
; then
216 git checkout-index
--prefix="$PREFIX" "$MERGED"
217 if test -f "$PREFIX""$MERGED"; then
218 mv -- "$PREFIX""$MERGED" "$LOCAL"
219 tmpdir
=$
(dirname "$PREFIX""$MERGED")
221 test "$tmpdir" != "." &&
229 if test -f "$MERGED"; then
230 cp -- "$MERGED" "$REMOTE"
234 # ensure that we clean up after ourselves
235 trap sigint_handler SIGINT
237 if should_prompt
; then
238 printf "\nEditing: '$MERGED'\n"
239 printf "Hit return to launch '%s': " "$merge_tool"
243 case "$merge_tool" in
245 basename=$
(basename "$MERGED")
246 if base_present
; then
248 "$merge_tool_path" --auto \
249 --L1 "($base) $basename" \
250 --L2 "($local) $basename" \
251 --L3 "($remote) $basename" \
252 -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" \
257 "$merge_tool_path" --auto \
258 --L1 "($local) $basename" \
259 --L2 "($remote) $basename" \
260 -o "$MERGED" "$LOCAL" "$REMOTE" \
267 if base_present
; then
270 -o "$MERGED" "$LOCAL" "$REMOTE"
273 -o "$MERGED" "$LOCAL" "$REMOTE"
278 "$merge_tool_path" "$LOCAL" "$MERGED"
282 if base_present
; then
283 "$merge_tool_path" "$BASE" "$LOCAL" "$MERGED"
285 "$merge_tool_path" "$LOCAL" "$MERGED"
290 if base_present
; then
291 "$merge_tool_path" -f "$BASE" "$LOCAL" "$MERGED"
293 "$merge_tool_path" -f "$LOCAL" "$MERGED"
298 if base_present
; then
299 "$merge_tool_path" -X --show-merged-pane \
300 -R 'Accel.SaveAsMerged: "Ctrl-S"' \
301 -R 'Accel.Search: "Ctrl+F"' \
302 -R 'Accel.SearchForward: "Ctrl-G"' \
303 --merged-file "$MERGED" \
304 "$BASE" "$LOCAL" "$REMOTE"
306 "$merge_tool_path" -X --show-merged-pane \
307 -R 'Accel.SaveAsMerged: "Ctrl-S"' \
308 -R 'Accel.Search: "Ctrl+F"' \
309 -R 'Accel.SearchForward: "Ctrl-G"' \
310 --merged-file "$MERGED" \
316 if base_present
; then
317 "$merge_tool_path" "$LOCAL" "$REMOTE" \
319 -merge "$MERGED" |
cat
321 "$merge_tool_path" "$LOCAL" "$REMOTE" \
322 -merge "$MERGED" |
cat
327 if base_present
; then
328 "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
329 --default --mode=merge3
--to="$MERGED"
331 "$merge_tool_path" "$LOCAL" "$REMOTE" \
332 --default --mode=merge2
--to="$MERGED"
337 if base_present
; then
339 -f emerge-files-with-ancestor-command \
340 "$LOCAL" "$REMOTE" "$BASE" \
341 "$(basename "$MERGED")"
343 "$merge_tool_path" -f emerge-files-command \
344 "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
348 if test -n "$merge_tool_cmd"; then
349 if test "$merge_tool_trust_exit_code" = "false"; then
350 ( eval $merge_tool_cmd )
352 ( eval $merge_tool_cmd )
366 merge_tool
=$
(parse_arg
"$1")
382 commitish
=$
(parse_arg
"$1")
398 start
=$
(parse_arg
"$1")
414 end
=$
(parse_arg
"$1")
444 valid_custom_tool
() {
445 merge_tool_cmd
="$(git config mergetool.$1.cmd)"
446 test -n "$merge_tool_cmd"
451 kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge
)
454 if ! valid_custom_tool
"$1"
462 init_merge_tool_path
() {
463 merge_tool_path
=$
(git config mergetool.
"$1".path
)
464 if test -z "$merge_tool_path"; then
467 merge_tool_path
=emacs
477 if test -z "$merge_tool"; then
478 merge_tool
=$
(git config merge.tool
)
479 if test -n "$merge_tool" && ! valid_tool
"$merge_tool"; then
480 echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
481 echo >&2 "Resetting to default..."
486 if test -z "$merge_tool"; then
487 if test -n "$DISPLAY"; then
488 merge_tool_candidates
="kdiff3 tkdiff xxdiff meld gvimdiff"
489 if test -n "$GNOME_DESKTOP_SESSION_ID"; then
490 merge_tool_candidates
="meld $merge_tool_candidates"
492 if test "$KDE_FULL_SESSION" = "true"; then
493 merge_tool_candidates
="kdiff3 $merge_tool_candidates"
497 if echo "${VISUAL:-$EDITOR}" |
grep 'emacs' > /dev
/null
2>&1; then
498 merge_tool_candidates
="$merge_tool_candidates emerge"
501 if echo "${VISUAL:-$EDITOR}" |
grep 'vim' > /dev
/null
2>&1; then
502 merge_tool_candidates
="$merge_tool_candidates vimdiff"
505 merge_tool_candidates
="$merge_tool_candidates opendiff emerge vimdiff"
506 echo "merge tool candidates: $merge_tool_candidates"
508 for i
in $merge_tool_candidates
510 init_merge_tool_path
$i
511 if type "$merge_tool_path" > /dev
/null
2>&1; then
517 if test -z "$merge_tool" ; then
518 echo "No known merge resolution program available."
523 if ! valid_tool
"$merge_tool"; then
524 echo >&2 "Unknown merge tool $merge_tool"
528 init_merge_tool_path
"$merge_tool"
530 if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev
/null
2>&1; then
531 echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
535 if ! test -z "$merge_tool_cmd"; then
536 merge_tool_trust_exit_code
="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)"
541 if test $# -eq 0; then
543 files
=$
(modified_files
)
545 if test -z "$files"; then
547 files
=$
(staged_files
)
550 if test -z "$files"; then
551 echo "No modified files exist."
556 if test $use_index -eq 0; then
560 merge_file
"$i" < /dev
/tty
> /dev
/tty
562 elif ! use_rev_range
; then
566 merge_file
"$i" < /dev
/tty
> /dev
/tty
569 echo "Nothing to compare."