git-mergetool--lib.sh 8.85 KB
Newer Older
1
# git-mergetool--lib is a shell library for common merge tool functions
2 3

: ${MERGE_TOOLS_DIR=$(git --exec-path)/mergetools}
4

5 6 7
IFS='
'

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
mode_ok () {
	if diff_mode
	then
		can_diff
	elif merge_mode
	then
		can_merge
	else
		false
	fi
}

is_available () {
	merge_tool_path=$(translate_merge_tool_path "$1") &&
	type "$merge_tool_path" >/dev/null 2>&1
}

25 26 27 28 29 30 31 32 33 34 35 36 37 38
list_config_tools () {
	section=$1
	line_prefix=${2:-}

	git config --get-regexp $section'\..*\.cmd' |
	while read -r key value
	do
		toolname=${key#$section.}
		toolname=${toolname%.cmd}

		printf "%s%s\n" "$line_prefix" "$toolname"
	done
}

39 40 41
show_tool_names () {
	condition=${1:-true} per_line_prefix=${2:-} preamble=${3:-}
	not_found_msg=${4:-}
42
	extra_content=${5:-}
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

	shown_any=
	( cd "$MERGE_TOOLS_DIR" && ls ) | {
		while read toolname
		do
			if setup_tool "$toolname" 2>/dev/null &&
				(eval "$condition" "$toolname")
			then
				if test -n "$preamble"
				then
					printf "%s\n" "$preamble"
					preamble=
				fi
				shown_any=yes
				printf "%s%s\n" "$per_line_prefix" "$toolname"
			fi
		done

61 62 63 64 65 66 67 68 69 70 71 72 73
		if test -n "$extra_content"
		then
			if test -n "$preamble"
			then
				# Note: no '\n' here since we don't want a
				# blank line if there is no initial content.
				printf "%s" "$preamble"
				preamble=
			fi
			shown_any=yes
			printf "\n%s\n" "$extra_content"
		fi

74 75 76 77 78 79 80 81 82
		if test -n "$preamble" && test -n "$not_found_msg"
		then
			printf "%s\n" "$not_found_msg"
		fi

		test -n "$shown_any"
	}
}

83 84 85 86 87 88 89 90 91
diff_mode() {
	test "$TOOL_MODE" = diff
}

merge_mode() {
	test "$TOOL_MODE" = merge
}

translate_merge_tool_path () {
92
	echo "$1"
93 94 95
}

check_unchanged () {
96 97
	if test "$MERGED" -nt "$BACKUP"
	then
98
		return 0
99
	else
100 101
		while true
		do
102
			echo "$MERGED seems unchanged."
103
			printf "Was the merge successful [y/n]? "
104
			read answer || return 1
105
			case "$answer" in
106 107
			y*|Y*) return 0 ;;
			n*|N*) return 1 ;;
108 109 110 111 112 113
			esac
		done
	fi
}

valid_tool () {
114 115 116
	setup_tool "$1" && return 0
	cmd=$(get_merge_tool_cmd "$1")
	test -n "$cmd"
117 118
}

119 120 121 122 123 124 125 126 127
setup_user_tool () {
	merge_tool_cmd=$(get_merge_tool_cmd "$tool")
	test -n "$merge_tool_cmd" || return 1

	diff_cmd () {
		( eval $merge_tool_cmd )
	}

	merge_cmd () {
128
		( eval $merge_tool_cmd )
129 130 131
	}
}

132
setup_tool () {
133 134
	tool="$1"

Ondřej Bílka's avatar
Ondřej Bílka committed
135
	# Fallback definitions, to be overridden by tools.
136 137 138 139 140 141 142 143 144
	can_merge () {
		return 0
	}

	can_diff () {
		return 0
	}

	diff_cmd () {
145
		return 1
146 147 148
	}

	merge_cmd () {
149
		return 1
150
	}
151

152 153 154 155
	translate_merge_tool_path () {
		echo "$1"
	}

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
	# Most tools' exit codes cannot be trusted, so By default we ignore
	# their exit code and check the merged file's modification time in
	# check_unchanged() to determine whether or not the merge was
	# successful.  The return value from run_merge_cmd, by default, is
	# determined by check_unchanged().
	#
	# When a tool's exit code can be trusted then the return value from
	# run_merge_cmd is simply the tool's exit code, and check_unchanged()
	# is not called.
	#
	# The return value of exit_code_trustable() tells us whether or not we
	# can trust the tool's exit code.
	#
	# User-defined and built-in tools default to false.
	# Built-in tools advertise that their exit code is trustable by
	# redefining exit_code_trustable() to true.

	exit_code_trustable () {
		false
	}


178
	if ! test -f "$MERGE_TOOLS_DIR/$tool"
179
	then
180 181
		setup_user_tool
		return $?
182 183 184
	fi

	# Load the redefined functions
185
	. "$MERGE_TOOLS_DIR/$tool"
186 187 188
	# Now let the user override the default command for the tool.  If
	# they have not done so then this will return 1 which we ignore.
	setup_user_tool
189 190 191 192

	if merge_mode && ! can_merge
	then
		echo "error: '$tool' can not be used to resolve merges" >&2
193
		return 1
194 195 196
	elif diff_mode && ! can_diff
	then
		echo "error: '$tool' can only be used to resolve merges" >&2
197
		return 1
198 199
	fi
	return 0
200 201 202
}

get_merge_tool_cmd () {
203
	merge_tool="$1"
204 205
	if diff_mode
	then
206 207
		git config "difftool.$merge_tool.cmd" ||
		git config "mergetool.$merge_tool.cmd"
208
	else
209
		git config "mergetool.$merge_tool.cmd"
210
	fi
211 212
}

213 214 215 216 217 218 219 220 221 222 223 224 225
trust_exit_code () {
	if git config --bool "mergetool.$1.trustExitCode"
	then
		:; # OK
	elif exit_code_trustable
	then
		echo true
	else
		echo false
	fi
}


226
# Entry point for running tools
227
run_merge_tool () {
228 229 230 231 232
	# If GIT_PREFIX is empty then we cannot use it in tools
	# that expect to be able to chdir() to its value.
	GIT_PREFIX=${GIT_PREFIX:-.}
	export GIT_PREFIX

233
	merge_tool_path=$(get_merge_tool_path "$1") || exit
234 235
	base_present="$2"

236
	# Bring tool-specific functions into scope
237
	setup_tool "$1" || return 1
238 239 240

	if merge_mode
	then
241
		run_merge_cmd "$1"
242
	else
243
		run_diff_cmd "$1"
244
	fi
245 246
}

247 248
# Run a either a configured or built-in diff tool
run_diff_cmd () {
249
	diff_cmd "$1"
250 251 252 253
}

# Run a either a configured or built-in merge tool
run_merge_cmd () {
254 255 256 257 258 259 260 261 262
	mergetool_trust_exit_code=$(trust_exit_code "$1")
	if test "$mergetool_trust_exit_code" = "true"
	then
		merge_cmd "$1"
	else
		touch "$BACKUP"
		merge_cmd "$1"
		check_unchanged
	fi
263 264
}

265
list_merge_tool_candidates () {
266 267
	if merge_mode
	then
268 269 270 271
		tools="tortoisemerge"
	else
		tools="kompare"
	fi
272 273 274 275
	if test -n "$DISPLAY"
	then
		if test -n "$GNOME_DESKTOP_SESSION_ID"
		then
276 277 278 279
			tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
		else
			tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
		fi
280
		tools="$tools gvimdiff diffuse diffmerge ecmerge"
281
		tools="$tools p4merge araxis bc codecompare"
282
		tools="$tools smerge"
283
	fi
284 285
	case "${VISUAL:-$EDITOR}" in
	*vim*)
286
		tools="$tools vimdiff emerge"
287 288
		;;
	*)
289
		tools="$tools emerge vimdiff"
290 291
		;;
	esac
292 293
}

294
show_tool_help () {
295
	tool_opt="'git ${TOOL_MODE}tool --tool=<tool>'"
296

297 298 299 300
	tab='	'
	LF='
'
	any_shown=no
301 302

	cmd_name=${TOOL_MODE}tool
303 304 305 306 307 308 309 310 311 312
	config_tools=$({
		diff_mode && list_config_tools difftool "$tab$tab"
		list_config_tools mergetool "$tab$tab"
	} | sort)
	extra_content=
	if test -n "$config_tools"
	then
		extra_content="${tab}user-defined:${LF}$config_tools"
	fi

313 314
	show_tool_names 'mode_ok && is_available' "$tab$tab" \
		"$tool_opt may be set to one of the following:" \
315 316
		"No suitable tool for 'git $cmd_name --tool=<tool>' found." \
		"$extra_content" &&
317 318 319 320 321 322 323
		any_shown=yes

	show_tool_names 'mode_ok && ! is_available' "$tab$tab" \
		"${LF}The following tools are valid, but not currently available:" &&
		any_shown=yes

	if test "$any_shown" = yes
324 325 326 327 328 329 330 331
	then
		echo
		echo "Some of the tools listed above only work in a windowed"
		echo "environment. If run in a terminal-only session, they will fail."
	fi
	exit 0
}

332 333
guess_merge_tool () {
	list_merge_tool_candidates
334 335 336 337 338 339 340
	cat >&2 <<-EOF

	This message is displayed because '$TOOL_MODE.tool' is not configured.
	See 'git ${TOOL_MODE}tool --tool-help' or 'git help config' for more details.
	'git ${TOOL_MODE}tool' will now attempt to use one of the following tools:
	$tools
	EOF
341 342

	# Loop over each candidate and stop when a valid merge tool is found.
343
	IFS=' '
344
	for tool in $tools
345
	do
346
		is_available "$tool" && echo "$tool" && return 0
347 348
	done

349
	echo >&2 "No known ${TOOL_MODE} tool is available."
350
	return 1
351 352 353
}

get_configured_merge_tool () {
354 355 356 357 358 359 360 361
	# If first argument is true, find the guitool instead
	if test "$1" = true
	then
		gui_prefix=gui
	fi

	# Diff mode first tries diff.(gui)tool and falls back to merge.(gui)tool.
	# Merge mode only checks merge.(gui)tool
362 363
	if diff_mode
	then
364
		merge_tool=$(git config diff.${gui_prefix}tool || git config merge.${gui_prefix}tool)
365
	else
366
		merge_tool=$(git config merge.${gui_prefix}tool)
367
	fi
368 369
	if test -n "$merge_tool" && ! valid_tool "$merge_tool"
	then
370
		echo >&2 "git config option $TOOL_MODE.${gui_prefix}tool set to unknown tool: $merge_tool"
371 372 373
		echo >&2 "Resetting to default..."
		return 1
	fi
374
	echo "$merge_tool"
375 376 377 378
}

get_merge_tool_path () {
	# A merge tool has been set, so verify that it's valid.
379
	merge_tool="$1"
380 381
	if ! valid_tool "$merge_tool"
	then
382 383 384
		echo >&2 "Unknown merge tool $merge_tool"
		exit 1
	fi
385 386
	if diff_mode
	then
387
		merge_tool_path=$(git config difftool."$merge_tool".path ||
388
				  git config mergetool."$merge_tool".path)
389 390
	else
		merge_tool_path=$(git config mergetool."$merge_tool".path)
391
	fi
392 393
	if test -z "$merge_tool_path"
	then
394
		merge_tool_path=$(translate_merge_tool_path "$merge_tool")
395
	fi
396
	if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
397 398
		! type "$merge_tool_path" >/dev/null 2>&1
	then
399
		echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
400
			 "'$merge_tool_path'"
401 402 403 404 405 406 407
		exit 1
	fi
	echo "$merge_tool_path"
}

get_merge_tool () {
	# Check if a merge tool has been configured
408
	merge_tool=$(get_configured_merge_tool)
409
	# Try to guess an appropriate merge tool if no tool has been set.
410 411
	if test -z "$merge_tool"
	then
412
		merge_tool=$(guess_merge_tool) || exit
413 414 415
	fi
	echo "$merge_tool"
}
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440

mergetool_find_win32_cmd () {
	executable=$1
	sub_directory=$2

	# Use $executable if it exists in $PATH
	if type -p "$executable" >/dev/null 2>&1
	then
		printf '%s' "$executable"
		return
	fi

	# Look for executable in the typical locations
	for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' |
		cut -d '=' -f 2- | sort -u)
	do
		if test -n "$directory" && test -x "$directory/$sub_directory/$executable"
		then
			printf '%s' "$directory/$sub_directory/$executable"
			return
		fi
	done

	printf '%s' "$executable"
}