git-mergetool--lib.sh 7.6 KB
Newer Older
1
#!/bin/sh
2
# git-mergetool--lib is a library for common merge tool functions
3 4

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

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
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
}

23 24 25 26 27 28 29 30 31 32 33 34 35 36
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
}

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

	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

59 60 61 62 63 64 65 66 67 68 69 70 71
		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

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

		test -n "$shown_any"
	}
}

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

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

translate_merge_tool_path () {
90
	echo "$1"
91 92 93
}

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

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

setup_tool () {
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
	tool="$1"

	# Fallback definitions, to be overriden by tools.
	can_merge () {
		return 0
	}

	can_diff () {
		return 0
	}

	diff_cmd () {
		status=1
		return $status
	}

	merge_cmd () {
		status=1
		return $status
	}
138

139 140 141 142 143
	translate_merge_tool_path () {
		echo "$1"
	}

	if ! test -f "$MERGE_TOOLS_DIR/$tool"
144
	then
145 146 147 148 149
		# Use a special return code for this case since we want to
		# source "defaults" even when an explicit tool path is
		# configured since the user can use that to override the
		# default path in the scriptlet.
		return 2
150 151 152
	fi

	# Load the redefined functions
153
	. "$MERGE_TOOLS_DIR/$tool"
154 155 156 157

	if merge_mode && ! can_merge
	then
		echo "error: '$tool' can not be used to resolve merges" >&2
158
		return 1
159 160 161
	elif diff_mode && ! can_diff
	then
		echo "error: '$tool' can only be used to resolve merges" >&2
162
		return 1
163 164
	fi
	return 0
165 166 167
}

get_merge_tool_cmd () {
168
	merge_tool="$1"
169 170
	if diff_mode
	then
171 172
		git config "difftool.$merge_tool.cmd" ||
		git config "mergetool.$merge_tool.cmd"
173
	else
174
		git config "mergetool.$merge_tool.cmd"
175
	fi
176 177
}

178
# Entry point for running tools
179
run_merge_tool () {
180 181 182 183 184
	# 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

185
	merge_tool_path=$(get_merge_tool_path "$1") || exit
186 187 188
	base_present="$2"
	status=0

189 190
	# Bring tool-specific functions into scope
	setup_tool "$1"
191 192 193 194 195 196 197 198 199 200 201 202 203
	exitcode=$?
	case $exitcode in
	0)
		:
		;;
	2)
		# The configured tool is not a built-in tool.
		test -n "$merge_tool_path" || return 1
		;;
	*)
		return $exitcode
		;;
	esac
204 205 206

	if merge_mode
	then
207
		run_merge_cmd "$1"
208
	else
209
		run_diff_cmd "$1"
210
	fi
211 212 213
	return $status
}

214 215
# Run a either a configured or built-in diff tool
run_diff_cmd () {
216
	merge_tool_cmd=$(get_merge_tool_cmd "$1")
217 218 219 220 221 222 223 224 225 226 227 228
	if test -n "$merge_tool_cmd"
	then
		( eval $merge_tool_cmd )
		status=$?
		return $status
	else
		diff_cmd "$1"
	fi
}

# Run a either a configured or built-in merge tool
run_merge_cmd () {
229
	merge_tool_cmd=$(get_merge_tool_cmd "$1")
230 231
	if test -n "$merge_tool_cmd"
	then
232 233
		trust_exit_code=$(git config --bool \
			"mergetool.$1.trustExitCode" || echo false)
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
		if test "$trust_exit_code" = "false"
		then
			touch "$BACKUP"
			( eval $merge_tool_cmd )
			status=$?
			check_unchanged
		else
			( eval $merge_tool_cmd )
			status=$?
		fi
		return $status
	else
		merge_cmd "$1"
	fi
}

250
list_merge_tool_candidates () {
251 252
	if merge_mode
	then
253 254 255 256
		tools="tortoisemerge"
	else
		tools="kompare"
	fi
257 258 259 260
	if test -n "$DISPLAY"
	then
		if test -n "$GNOME_DESKTOP_SESSION_ID"
		then
261 262 263 264
			tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
		else
			tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
		fi
265
		tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3 codecompare"
266
	fi
267 268
	case "${VISUAL:-$EDITOR}" in
	*vim*)
269
		tools="$tools vimdiff emerge"
270 271
		;;
	*)
272
		tools="$tools emerge vimdiff"
273 274
		;;
	esac
275 276
}

277
show_tool_help () {
278
	tool_opt="'git ${TOOL_MODE}tool --tool-<tool>'"
279

280 281 282 283
	tab='	'
	LF='
'
	any_shown=no
284 285

	cmd_name=${TOOL_MODE}tool
286 287 288 289 290 291 292 293 294 295
	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

296 297
	show_tool_names 'mode_ok && is_available' "$tab$tab" \
		"$tool_opt may be set to one of the following:" \
298 299
		"No suitable tool for 'git $cmd_name --tool=<tool>' found." \
		"$extra_content" &&
300 301 302 303 304 305 306
		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
307 308 309 310 311 312 313 314
	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
}

315 316
guess_merge_tool () {
	list_merge_tool_candidates
317 318 319 320 321 322 323
	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
324 325

	# Loop over each candidate and stop when a valid merge tool is found.
326
	for tool in $tools
327
	do
328
		is_available "$tool" && echo "$tool" && return 0
329 330
	done

331
	echo >&2 "No known ${TOOL_MODE} tool is available."
332
	return 1
333 334 335 336 337
}

get_configured_merge_tool () {
	# Diff mode first tries diff.tool and falls back to merge.tool.
	# Merge mode only checks merge.tool
338 339
	if diff_mode
	then
340 341 342
		merge_tool=$(git config diff.tool || git config merge.tool)
	else
		merge_tool=$(git config merge.tool)
343
	fi
344 345
	if test -n "$merge_tool" && ! valid_tool "$merge_tool"
	then
346 347 348 349
		echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
		echo >&2 "Resetting to default..."
		return 1
	fi
350
	echo "$merge_tool"
351 352 353 354
}

get_merge_tool_path () {
	# A merge tool has been set, so verify that it's valid.
355
	merge_tool="$1"
356 357
	if ! valid_tool "$merge_tool"
	then
358 359 360
		echo >&2 "Unknown merge tool $merge_tool"
		exit 1
	fi
361 362
	if diff_mode
	then
363
		merge_tool_path=$(git config difftool."$merge_tool".path ||
364
				  git config mergetool."$merge_tool".path)
365 366
	else
		merge_tool_path=$(git config mergetool."$merge_tool".path)
367
	fi
368 369
	if test -z "$merge_tool_path"
	then
370
		merge_tool_path=$(translate_merge_tool_path "$merge_tool")
371
	fi
372
	if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
373 374
		! type "$merge_tool_path" >/dev/null 2>&1
	then
375
		echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
376
			 "'$merge_tool_path'"
377 378 379 380 381 382 383
		exit 1
	fi
	echo "$merge_tool_path"
}

get_merge_tool () {
	# Check if a merge tool has been configured
384
	merge_tool=$(get_configured_merge_tool)
385
	# Try to guess an appropriate merge tool if no tool has been set.
386 387
	if test -z "$merge_tool"
	then
388
		merge_tool=$(guess_merge_tool) || exit
389 390 391
	fi
	echo "$merge_tool"
}