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
12 USAGE
='[--tool=tool] [--commit=ref] [--start=ref --end=ref] [--no-prompt] [file to merge] ...'
19 keep_backup_mode
="$(git config --bool merge.keepBackup || echo true)"
20 PREFIX
=.git-difftool.
"$$".
"$(date +%N)".
24 # whether to keep the .orig file
25 test "$keep_backup_mode" = "true"
30 expr "z$1" : 'z-[^=]*=\(.*\)'
34 # does the index contain staged changes?
39 # are there changes in the requested comparison?
40 test -n "$modified_mode"
43 commitish_present
() {
44 # are we comparing against an arbitrary commit?
49 # are we running interactively?
50 ! test -n "$no_prompt"
54 # are we using --start=REF and --end=REF
55 test -n "$start" && test -n "$end"
58 merge_dir_missing
() {
59 # does dirname($MERGED) exist in the work tree?
60 test -n "$non_existant_dir"
64 # whether we have 3 things to compare (index, worktree, other)
65 index_present
&& modified_present
&& ! use_rev_range
69 # returns the appopriate list of differences based on the mode
70 if use_rev_range
; then
71 git
diff --name-only "$start"..
"$end" -- "$@" 2>/dev
/null
72 elif commitish_present
; then
73 git
diff --name-only "$commitish" -- "$@" 2>/dev
/null
75 git
diff --name-only -- "$@" 2>/dev
/null
80 # returns any staged changes
81 git
diff --name-only --cached "$@" 2>/dev
/null
84 cleanup_temp_files
() {
85 # removes temporary files
86 if test -n "$MERGED"; then
87 if keep_backup
&& test "$MERGED" -nt "$BACKUP"; then
88 test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
89 rm -f -- "$LOCAL" "$REMOTE" "$BASE"
91 rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
93 test -n "$MERGEDIR" && rmdir -p "$MERGEDIR"
104 # prepares temporary files and launches the appropriate merge tool
107 modified_mode
=$
(modified_files
"$MERGED")
108 index_mode
=$
(staged_files
"$MERGED")
110 if ! modified_present
&& use_rev_range
; then
111 echo "$MERGED: no changes between '$start' and '$end'."
113 elif ! modified_present
&& ! index_present
; then
114 if ! test -f "$MERGED"; then
115 echo "$MERGED: file not found"
117 echo "$MERGED: file is unchanged."
122 # handle comparing a file that doesn't exist in the current checkout
123 MERGEDIR
=$
(dirname "$MERGED")
125 test -d "$MERGEDIR" || non_existant_dir
=true
126 if merge_dir_missing
; then
127 mkdir
-p -- "$MERGEDIR"
132 ext
="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
133 BACKUP
="./$MERGED.BACKUP.$ext"
134 test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
136 if use_rev_range
; then
137 # we're comparing two arbitrary commits
138 BASE
="./$MERGED.LOCAL.$ext"
139 LOCAL
="./$MERGED.START.$ext"
140 REMOTE
="./$MERGED.END.$ext"
145 # detects renames.. sweet!
146 oldname
=$
(git
diff --follow "$start"..
"$end" -- "$MERGED" |
148 grep '^rename from' |
149 sed -e 's/rename from //')
151 test -n "$oldname" && startname
="$oldname"
153 if ! git show
"$start":"$startname" > "$LOCAL" 2>/dev
/null
; then
154 if should_prompt
; then
157 echo -n "'$startname' does not exist at $start."
161 if ! git show
"$end":"$MERGED" > "$REMOTE" 2>/dev
/null
; then
162 if should_prompt
; then
165 echo -n "'$MERGED' does not exist at $end."
170 # $BASE could be used by custom mergetool commands, so provide
171 # it. $MERGED might not exist in the worktree, start or end so
172 # commit so check in that order.
173 if test -f "$MERGED"; then
174 cp -- "$MERGED" "$BASE"
175 elif test -f "$REMOTE"; then
176 cp -- "$REMOTE" "$BASE"
177 elif test -f "$LOCAL"; then
178 cp -- "$LOCAL" "$BASE"
183 # we're either comparing against the index or an
187 remote
=${commitish-HEAD}
189 commitish_present
&& HEAD
=OTHER
190 LOCAL
="./$MERGED.LOCAL.$ext"
191 REMOTE
="./$MERGED.$HEAD.$ext"
192 BASE
="./$MERGED.INDEX.$ext"
193 git show
"$remote":"$MERGED" > "$REMOTE" 2>&1
194 # If changes are present in the index use them as $BASE
195 if test -f "$MERGED"; then
196 cp -- "$MERGED" "$LOCAL"
197 git checkout-index
--prefix="$PREFIX" "$MERGED"
198 mv -- "$PREFIX""$MERGED" "$BASE"
199 tmpdir
=$
(dirname "$PREFIX""$MERGED")
200 test -d "$tmpdir" && rmdir -p "$tmpdir"
202 # $MERGED doesn't exist here...
208 # ensure that we clean up after ourselves
209 trap sigint_handler SIGINT
211 if should_prompt
; then
212 printf "\nEditing: '$MERGED'\n"
213 printf "Hit return to launch '%s': " "$merge_tool"
217 case "$merge_tool" in
219 basename=$
(basename "$MERGED")
222 "$merge_tool_path" --auto \
223 --L1 "[$base] $basename" \
224 --L2 "[$local] $basename" \
225 --L3 "[$remote] $basename" \
226 -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" \
231 "$merge_tool_path" --auto \
232 --L1 "[$local] $basename" \
233 --L2 "[$remote] $basename" \
234 -o "$MERGED" "$LOCAL" "$REMOTE" \
244 -o "$MERGED" "$LOCAL" "$REMOTE"
247 -o "$MERGED" "$LOCAL" "$REMOTE"
252 "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
256 "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"
261 "$merge_tool_path" -X --show-merged-pane \
262 -R 'Accel.SaveAsMerged: "Ctrl-S"' \
263 -R 'Accel.Search: "Ctrl+F"' \
264 -R 'Accel.SearchForward: "Ctrl-G"' \
265 --merged-file "$MERGED" \
266 "$LOCAL" "$BASE" "$REMOTE"
268 "$merge_tool_path" -X --show-merged-pane \
269 -R 'Accel.SaveAsMerged: "Ctrl-S"' \
270 -R 'Accel.Search: "Ctrl+F"' \
271 -R 'Accel.SearchForward: "Ctrl-G"' \
272 --merged-file "$MERGED" \
279 "$merge_tool_path" "$LOCAL" "$REMOTE" \
281 -merge "$MERGED" |
cat
283 "$merge_tool_path" "$LOCAL" "$REMOTE" \
284 -merge "$MERGED" |
cat
290 "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
291 --default --mode=merge3
--to="$MERGED"
293 "$merge_tool_path" "$LOCAL" "$REMOTE" \
294 --default --mode=merge2
--to="$MERGED"
301 -f emerge-files-with-ancestor-command \
302 "$LOCAL" "$REMOTE" "$BASE" \
303 "$(basename "$MERGED")"
305 "$merge_tool_path" -f emerge-files-command \
306 "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
310 if test -n "$merge_tool_cmd"; then
311 if test "$merge_tool_trust_exit_code" = "false"; then
312 ( eval $merge_tool_cmd )
314 ( eval $merge_tool_cmd )
328 merge_tool
=$
(parse_arg
"$1")
344 commitish
=$
(parse_arg
"$1")
360 start
=$
(parse_arg
"$1")
376 end
=$
(parse_arg
"$1")
406 valid_custom_tool
() {
407 merge_tool_cmd
="$(git config mergetool.$1.cmd)"
408 test -n "$merge_tool_cmd"
413 kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge
)
416 if ! valid_custom_tool
"$1"
424 init_merge_tool_path
() {
425 merge_tool_path
=$
(git config mergetool.
"$1".path
)
426 if test -z "$merge_tool_path"; then
429 merge_tool_path
=emacs
439 if test -z "$merge_tool"; then
440 merge_tool
=$
(git config merge.tool
)
441 if test -n "$merge_tool" && ! valid_tool
"$merge_tool"; then
442 echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
443 echo >&2 "Resetting to default..."
448 if test -z "$merge_tool"; then
449 if test -n "$DISPLAY"; then
450 merge_tool_candidates
="kdiff3 tkdiff xxdiff meld gvimdiff"
451 if test -n "$GNOME_DESKTOP_SESSION_ID"; then
452 merge_tool_candidates
="meld $merge_tool_candidates"
454 if test "$KDE_FULL_SESSION" = "true"; then
455 merge_tool_candidates
="kdiff3 $merge_tool_candidates"
459 if echo "${VISUAL:-$EDITOR}" |
grep 'emacs' > /dev
/null
2>&1; then
460 merge_tool_candidates
="$merge_tool_candidates emerge"
463 if echo "${VISUAL:-$EDITOR}" |
grep 'vim' > /dev
/null
2>&1; then
464 merge_tool_candidates
="$merge_tool_candidates vimdiff"
467 merge_tool_candidates
="$merge_tool_candidates opendiff emerge vimdiff"
468 echo "merge tool candidates: $merge_tool_candidates"
470 for i
in $merge_tool_candidates
472 init_merge_tool_path
$i
473 if type "$merge_tool_path" > /dev
/null
2>&1; then
479 if test -z "$merge_tool" ; then
480 echo "No known merge resolution program available."
485 if ! valid_tool
"$merge_tool"; then
486 echo >&2 "Unknown merge tool $merge_tool"
490 init_merge_tool_path
"$merge_tool"
492 if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev
/null
2>&1; then
493 echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
497 if ! test -z "$merge_tool_cmd"; then
498 merge_tool_trust_exit_code
="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)"
503 if test $# -eq 0; then
505 files
=$
(modified_files
)
507 if test -z "$files"; then
509 files
=$
(staged_files
)
512 if test -z "$files"; then
513 echo "No modified files exist."
518 if test $use_index -eq 0; then
522 merge_file
"$i" < /dev
/tty
> /dev
/tty
524 elif ! use_rev_range
; then
528 merge_file
"$i" < /dev
/tty
> /dev
/tty
531 echo "Nothing to compare."