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