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