aboutsummaryrefslogtreecommitdiff
path: root/contrib/difftool/git-difftool-helper
blob: 9c0a13452a60059b504f07cb94a3cc16e1a2e6e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#!/bin/sh
# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
# It supports kdiff3, kompare, tkdiff, xxdiff, meld, opendiff,
# emerge, ecmerge, vimdiff, gvimdiff, and custom user-configurable tools.
# This script is typically launched by using the 'git difftool'
# convenience command.
#
# Copyright (c) 2009 David Aguilar

# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt.
should_prompt () {
	! test -n "$GIT_DIFFTOOL_NO_PROMPT"
}

# Should we keep the backup .orig file?
keep_backup_mode="$(git config --bool merge.keepBackup || echo true)"
keep_backup () {
	test "$keep_backup_mode" = "true"
}

# This function manages the backup .orig file.
# A backup $MERGED.orig file is created if changes are detected.
cleanup_temp_files () {
	if test -n "$MERGED"; then
		if keep_backup && test "$MERGED" -nt "$BACKUP"; then
			test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
		else
			rm -f -- "$BACKUP"
		fi
	fi
}

# This is called when users Ctrl-C out of git-difftool-helper
sigint_handler () {
	cleanup_temp_files
	exit 1
}

# This function prepares temporary files and launches the appropriate
# merge tool.
launch_merge_tool () {
	# Merged is the filename as it appears in the work tree
	# Local is the contents of a/filename
	# Remote is the contents of b/filename
	# Custom merge tool commands might use $BASE so we provide it
	MERGED="$1"
	LOCAL="$2"
	REMOTE="$3"
	BASE="$1"
	ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
	BACKUP="$MERGED.BACKUP.$ext"

	# Create and ensure that we clean up $BACKUP
	test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
	trap sigint_handler INT

	# $LOCAL and $REMOTE are temporary files so prompt
	# the user with the real $MERGED name before launching $merge_tool.
	if should_prompt; then
		printf "\nViewing: '$MERGED'\n"
		printf "Hit return to launch '%s': " "$merge_tool"
		read ans
	fi

	# Run the appropriate merge tool command
	case "$merge_tool" in
	kdiff3)
		basename=$(basename "$MERGED")
		"$merge_tool_path" --auto \
			--L1 "$basename (A)" \
			--L2 "$basename (B)" \
			-o "$MERGED" "$LOCAL" "$REMOTE" \
			> /dev/null 2>&1
		;;

	kompare)
		"$merge_tool_path" "$LOCAL" "$REMOTE"
		;;

	tkdiff)
		"$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
		;;

	meld)
		"$merge_tool_path" "$LOCAL" "$REMOTE"
		;;

	vimdiff)
		"$merge_tool_path" -c "wincmd l" "$LOCAL" "$REMOTE"
		;;

	gvimdiff)
		"$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$REMOTE"
		;;

	xxdiff)
		"$merge_tool_path" \
			-X \
			-R 'Accel.SaveAsMerged: "Ctrl-S"' \
			-R 'Accel.Search: "Ctrl+F"' \
			-R 'Accel.SearchForward: "Ctrl-G"' \
			--merged-file "$MERGED" \
			"$LOCAL" "$REMOTE"
		;;

	opendiff)
		"$merge_tool_path" "$LOCAL" "$REMOTE" \
			-merge "$MERGED" | cat
		;;

	ecmerge)
		"$merge_tool_path" "$LOCAL" "$REMOTE" \
			--default --mode=merge2 --to="$MERGED"
		;;

	emerge)
		"$merge_tool_path" -f emerge-files-command \
			"$LOCAL" "$REMOTE" "$(basename "$MERGED")"
		;;

	*)
		if test -n "$merge_tool_cmd"; then
			( eval $merge_tool_cmd )
		fi
		;;
	esac

	cleanup_temp_files
}

# Verifies that (difftool|mergetool).<tool>.cmd exists
valid_custom_tool() {
	merge_tool_cmd="$(git config difftool.$1.cmd)"
	test -z "$merge_tool_cmd" &&
	merge_tool_cmd="$(git config mergetool.$1.cmd)"
	test -n "$merge_tool_cmd"
}

# Verifies that the chosen merge tool is properly setup.
# Built-in merge tools are always valid.
valid_tool() {
	case "$1" in
	kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
		;; # happy
	*)
		if ! valid_custom_tool "$1"
		then
			return 1
		fi
		;;
	esac
}

# Sets up the merge_tool_path variable.
# This handles the difftool.<tool>.path configuration.
# This also falls back to mergetool defaults.
init_merge_tool_path() {
	merge_tool_path=$(git config difftool."$1".path)
	test -z "$merge_tool_path" &&
	merge_tool_path=$(git config mergetool."$1".path)
	if test -z "$merge_tool_path"; then
		case "$1" in
		emerge)
			merge_tool_path=emacs
			;;
		*)
			merge_tool_path="$1"
			;;
		esac
	fi
}

# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values
test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL"

# If merge tool was not specified then use the diff.tool
# configuration variable.  If that's invalid then reset merge_tool.
# Fallback to merge.tool.
if test -z "$merge_tool"; then
	merge_tool=$(git config diff.tool)
	test -z "$merge_tool" &&
	merge_tool=$(git config merge.tool)
	if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
		echo >&2 "git config option diff.tool set to unknown tool: $merge_tool"
		echo >&2 "Resetting to default..."
		unset merge_tool
	fi
fi

# Try to guess an appropriate merge tool if no tool has been set.
if test -z "$merge_tool"; then
	# We have a $DISPLAY so try some common UNIX merge tools
	if test -n "$DISPLAY"; then
		# If gnome then prefer meld, otherwise, prefer kdiff3 or kompare
		if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
			merge_tool_candidates="meld kdiff3 kompare tkdiff xxdiff gvimdiff"
		else
			merge_tool_candidates="kdiff3 kompare tkdiff xxdiff meld gvimdiff"
		fi
	fi
	if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
		# $EDITOR is emacs so add emerge as a candidate
		merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
	elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
		# $EDITOR is vim so add vimdiff as a candidate
		merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
	else
		merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
	fi
	echo "merge tool candidates: $merge_tool_candidates"

	# Loop over each candidate and stop when a valid merge tool is found.
	for i in $merge_tool_candidates
	do
		init_merge_tool_path $i
		if type "$merge_tool_path" > /dev/null 2>&1; then
			merge_tool=$i
			break
		fi
	done

	if test -z "$merge_tool" ; then
		echo "No known merge resolution program available."
		exit 1
	fi

else
	# A merge tool has been set, so verify that it's valid.
	if ! valid_tool "$merge_tool"; then
		echo >&2 "Unknown merge tool $merge_tool"
		exit 1
	fi

	init_merge_tool_path "$merge_tool"

	if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
		echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
		exit 1
	fi
fi


# Launch the merge tool on each path provided by 'git diff'
while test $# -gt 6
do
	launch_merge_tool "$1" "$2" "$5"
	shift 7
done