1#!/bin/sh
2# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
3# It supports kdiff3, tkdiff, xxdiff, meld, opendiff, emerge, ecmerge,
4# vimdiff, gvimdiff, and custom user-configurable tools.
5# This script is typically launched by using the 'git difftool'
6# convenience command.
7#
8# Copyright (c) 2009 David Aguilar
9
10# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt.
11should_prompt () {
12 ! test -n "$GIT_DIFFTOOL_NO_PROMPT"
13}
14
15# Should we keep the backup .orig file?
16keep_backup_mode="$(git config --bool merge.keepBackup || echo true)"
17keep_backup () {
18 test "$keep_backup_mode" = "true"
19}
20
21# This function manages the backup .orig file.
22# A backup $MERGED.orig file is created if changes are detected.
23cleanup_temp_files () {
24 if test -n "$MERGED"; then
25 if keep_backup && test "$MERGED" -nt "$BACKUP"; then
26 test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
27 else
28 rm -f -- "$BACKUP"
29 fi
30 fi
31}
32
33# This is called when users Ctrl-C out of git-difftool-helper
34sigint_handler () {
35 echo
36 cleanup_temp_files
37 exit 1
38}
39
40# This function prepares temporary files and launches the appropriate
41# merge tool.
42launch_merge_tool () {
43 # Merged is the filename as it appears in the work tree
44 # Local is the contents of a/filename
45 # Remote is the contents of b/filename
46 # Custom merge tool commands might use $BASE so we provide it
47 MERGED="$1"
48 LOCAL="$2"
49 REMOTE="$3"
50 BASE="$1"
51 ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
52 BACKUP="$MERGED.BACKUP.$ext"
53
54 # Create and ensure that we clean up $BACKUP
55 test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
56 trap sigint_handler SIGINT
57
58 # $LOCAL and $REMOTE are temporary files so prompt
59 # the user with the real $MERGED name before launching $merge_tool.
60 if should_prompt; then
61 printf "\nViewing: '$MERGED'\n"
62 printf "Hit return to launch '%s': " "$merge_tool"
63 read ans
64 fi
65
66 # Run the appropriate merge tool command
67 case "$merge_tool" in
68 kdiff3)
69 basename=$(basename "$MERGED")
70 "$merge_tool_path" --auto \
71 --L1 "$basename (A)" \
72 --L2 "$basename (B)" \
73 -o "$MERGED" "$LOCAL" "$REMOTE" \
74 > /dev/null 2>&1
75 ;;
76
77 tkdiff)
78 "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
79 ;;
80
81 meld)
82 "$merge_tool_path" "$LOCAL" "$REMOTE"
83 ;;
84
85 vimdiff)
86 "$merge_tool_path" -c "wincmd l" "$LOCAL" "$REMOTE"
87 ;;
88
89 gvimdiff)
90 "$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$REMOTE"
91 ;;
92
93 xxdiff)
94 "$merge_tool_path" \
95 -X \
96 -R 'Accel.SaveAsMerged: "Ctrl-S"' \
97 -R 'Accel.Search: "Ctrl+F"' \
98 -R 'Accel.SearchForward: "Ctrl-G"' \
99 --merged-file "$MERGED" \
100 "$LOCAL" "$REMOTE"
101 ;;
102
103 opendiff)
104 "$merge_tool_path" "$LOCAL" "$REMOTE" \
105 -merge "$MERGED" | cat
106 ;;
107
108 ecmerge)
109 "$merge_tool_path" "$LOCAL" "$REMOTE" \
110 --default --mode=merge2 --to="$MERGED"
111 ;;
112
113 emerge)
114 "$merge_tool_path" -f emerge-files-command \
115 "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
116 ;;
117
118 *)
119 if test -n "$merge_tool_cmd"; then
120 ( eval $merge_tool_cmd )
121 fi
122 ;;
123 esac
124
125 cleanup_temp_files
126}
127
128# Verifies that mergetool.<tool>.cmd exists
129valid_custom_tool() {
130 merge_tool_cmd="$(git config mergetool.$1.cmd)"
131 test -n "$merge_tool_cmd"
132}
133
134# Verifies that the chosen merge tool is properly setup.
135# Built-in merge tools are always valid.
136valid_tool() {
137 case "$1" in
138 kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
139 ;; # happy
140 *)
141 if ! valid_custom_tool "$1"
142 then
143 return 1
144 fi
145 ;;
146 esac
147}
148
149# Sets up the merge_tool_path variable.
150# This handles the mergetool.<tool>.path configuration.
151init_merge_tool_path() {
152 merge_tool_path=$(git config mergetool."$1".path)
153 if test -z "$merge_tool_path"; then
154 case "$1" in
155 emerge)
156 merge_tool_path=emacs
157 ;;
158 *)
159 merge_tool_path="$1"
160 ;;
161 esac
162 fi
163}
164
165# Allow the GIT_MERGE_TOOL variable to provide a default value
166test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
167
168# If not merge tool was specified then use the merge.tool
169# configuration variable. If that's invalid then reset merge_tool.
170if test -z "$merge_tool"; then
171 merge_tool=$(git config merge.tool)
172 if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
173 echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
174 echo >&2 "Resetting to default..."
175 unset merge_tool
176 fi
177fi
178
179# Try to guess an appropriate merge tool if no tool has been set.
180if test -z "$merge_tool"; then
181
182 # We have a $DISPLAY so try some common UNIX merge tools
183 if test -n "$DISPLAY"; then
184 merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
185 # If gnome then prefer meld
186 if test -n "$GNOME_DESKTOP_SESSION_ID"; then
187 merge_tool_candidates="meld $merge_tool_candidates"
188 fi
189 # If KDE then prefer kdiff3
190 if test "$KDE_FULL_SESSION" = "true"; then
191 merge_tool_candidates="kdiff3 $merge_tool_candidates"
192 fi
193 fi
194
195 # $EDITOR is emacs so add emerge as a candidate
196 if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
197 merge_tool_candidates="$merge_tool_candidates emerge"
198 fi
199
200 # $EDITOR is vim so add vimdiff as a candidate
201 if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
202 merge_tool_candidates="$merge_tool_candidates vimdiff"
203 fi
204
205 merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
206 echo "merge tool candidates: $merge_tool_candidates"
207
208 # Loop over each candidate and stop when a valid merge tool is found.
209 for i in $merge_tool_candidates
210 do
211 init_merge_tool_path $i
212 if type "$merge_tool_path" > /dev/null 2>&1; then
213 merge_tool=$i
214 break
215 fi
216 done
217
218 if test -z "$merge_tool" ; then
219 echo "No known merge resolution program available."
220 exit 1
221 fi
222
223else
224 # A merge tool has been set, so verify that it's valid.
225 if ! valid_tool "$merge_tool"; then
226 echo >&2 "Unknown merge tool $merge_tool"
227 exit 1
228 fi
229
230 init_merge_tool_path "$merge_tool"
231
232 if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
233 echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
234 exit 1
235 fi
236fi
237
238
239# Launch the merge tool on each path provided by 'git diff'
240while test $# -gt 6
241do
242 launch_merge_tool "$1" "$2" "$5"
243 shift 7
244done