Revision 044fb190f75cdec35e56bde30ec214ab144311d9 authored by Johannes Schindelin on 09 July 2016, 07:23:55 UTC, committed by Junio C Hamano on 11 July 2016, 18:55:53 UTC
When comparing two lines, ignoring any whitespace at the end, we first
try to match as many bytes as possible and break out of the loop only
upon mismatch, to let the remainder be handled by the code shared with
the other whitespace-ignoring code paths.

When comparing the bytes, however, we incremented the counters always,
even if the bytes did not match. And because we fall through to  the
space-at-eol handling at that point, it is as if that mismatch never
happened.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent a5229cc
Raw File
git-mergetool.sh
#!/bin/sh
#
# This program resolves merge conflicts in git
#
# Copyright (c) 2006 Theodore Y. Ts'o
#
# This file is licensed under the GPL v2, or a later version
# at the discretion of Junio C Hamano.
#

USAGE='[--tool=tool] [--tool-help] [-y|--no-prompt|--prompt] [file to merge] ...'
SUBDIRECTORY_OK=Yes
NONGIT_OK=Yes
OPTIONS_SPEC=
TOOL_MODE=merge
. git-sh-setup
. git-mergetool--lib

# Returns true if the mode reflects a symlink
is_symlink () {
	test "$1" = 120000
}

is_submodule () {
	test "$1" = 160000
}

local_present () {
	test -n "$local_mode"
}

remote_present () {
	test -n "$remote_mode"
}

base_present () {
	test -n "$base_mode"
}

mergetool_tmpdir_init () {
	if test "$(git config --bool mergetool.writeToTemp)" != true
	then
		MERGETOOL_TMPDIR=.
		return 0
	fi
	if MERGETOOL_TMPDIR=$(mktemp -d -t "git-mergetool-XXXXXX" 2>/dev/null)
	then
		return 0
	fi
	die "error: mktemp is needed when 'mergetool.writeToTemp' is true"
}

cleanup_temp_files () {
	if test "$1" = --save-backup
	then
		rm -rf -- "$MERGED.orig"
		test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
		rm -f -- "$LOCAL" "$REMOTE" "$BASE"
	else
		rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
	fi
	if test "$MERGETOOL_TMPDIR" != "."
	then
		rmdir "$MERGETOOL_TMPDIR"
	fi
}

describe_file () {
	mode="$1"
	branch="$2"
	file="$3"

	printf "  {%s}: " "$branch"
	if test -z "$mode"
	then
		echo "deleted"
	elif is_symlink "$mode"
	then
		echo "a symbolic link -> '$(cat "$file")'"
	elif is_submodule "$mode"
	then
		echo "submodule commit $file"
	elif base_present
	then
		echo "modified file"
	else
		echo "created file"
	fi
}

resolve_symlink_merge () {
	while true
	do
		printf "Use (l)ocal or (r)emote, or (a)bort? "
		read ans || return 1
		case "$ans" in
		[lL]*)
			git checkout-index -f --stage=2 -- "$MERGED"
			git add -- "$MERGED"
			cleanup_temp_files --save-backup
			return 0
			;;
		[rR]*)
			git checkout-index -f --stage=3 -- "$MERGED"
			git add -- "$MERGED"
			cleanup_temp_files --save-backup
			return 0
			;;
		[aA]*)
			return 1
			;;
		esac
	done
}

resolve_deleted_merge () {
	while true
	do
		if base_present
		then
			printf "Use (m)odified or (d)eleted file, or (a)bort? "
		else
			printf "Use (c)reated or (d)eleted file, or (a)bort? "
		fi
		read ans || return 1
		case "$ans" in
		[mMcC]*)
			git add -- "$MERGED"
			cleanup_temp_files --save-backup
			return 0
			;;
		[dD]*)
			git rm -- "$MERGED" > /dev/null
			cleanup_temp_files
			return 0
			;;
		[aA]*)
			return 1
			;;
		esac
	done
}

resolve_submodule_merge () {
	while true
	do
		printf "Use (l)ocal or (r)emote, or (a)bort? "
		read ans || return 1
		case "$ans" in
		[lL]*)
			if ! local_present
			then
				if test -n "$(git ls-tree HEAD -- "$MERGED")"
				then
					# Local isn't present, but it's a subdirectory
					git ls-tree --full-name -r HEAD -- "$MERGED" |
					git update-index --index-info || exit $?
				else
					test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
					git update-index --force-remove "$MERGED"
					cleanup_temp_files --save-backup
				fi
			elif is_submodule "$local_mode"
			then
				stage_submodule "$MERGED" "$local_sha1"
			else
				git checkout-index -f --stage=2 -- "$MERGED"
				git add -- "$MERGED"
			fi
			return 0
			;;
		[rR]*)
			if ! remote_present
			then
				if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"
				then
					# Remote isn't present, but it's a subdirectory
					git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" |
					git update-index --index-info || exit $?
				else
					test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
					git update-index --force-remove "$MERGED"
				fi
			elif is_submodule "$remote_mode"
			then
				! is_submodule "$local_mode" &&
				test -e "$MERGED" &&
				mv -- "$MERGED" "$BACKUP"
				stage_submodule "$MERGED" "$remote_sha1"
			else
				test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
				git checkout-index -f --stage=3 -- "$MERGED"
				git add -- "$MERGED"
			fi
			cleanup_temp_files --save-backup
			return 0
			;;
		[aA]*)
			return 1
			;;
		esac
	done
}

stage_submodule () {
	path="$1"
	submodule_sha1="$2"
	mkdir -p "$path" ||
	die "fatal: unable to create directory for module at $path"
	# Find $path relative to work tree
	work_tree_root=$(cd_to_toplevel && pwd)
	work_rel_path=$(cd "$path" &&
		GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix
	)
	test -n "$work_rel_path" ||
	die "fatal: unable to get path of module $path relative to work tree"
	git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
}

checkout_staged_file () {
	tmpfile=$(expr \
		"$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \
		: '\([^	]*\)	')

	if test $? -eq 0 && test -n "$tmpfile"
	then
		mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
	else
		>"$3"
	fi
}

merge_file () {
	MERGED="$1"

	f=$(git ls-files -u -- "$MERGED")
	if test -z "$f"
	then
		if test ! -f "$MERGED"
		then
			echo "$MERGED: file not found"
		else
			echo "$MERGED: file does not need merging"
		fi
		return 1
	fi

	if BASE=$(expr "$MERGED" : '\(.*\)\.[^/]*$')
	then
		ext=$(expr "$MERGED" : '.*\(\.[^/]*\)$')
	else
		BASE=$MERGED
		ext=
	fi

	mergetool_tmpdir_init

	if test "$MERGETOOL_TMPDIR" != "."
	then
		# If we're using a temporary directory then write to the
		# top-level of that directory.
		BASE=${BASE##*/}
	fi

	BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext"
	LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext"
	REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext"
	BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext"

	base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
	local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
	remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')

	if is_submodule "$local_mode" || is_submodule "$remote_mode"
	then
		echo "Submodule merge conflict for '$MERGED':"
		local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}')
		remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}')
		describe_file "$local_mode" "local" "$local_sha1"
		describe_file "$remote_mode" "remote" "$remote_sha1"
		resolve_submodule_merge
		return
	fi

	mv -- "$MERGED" "$BACKUP"
	cp -- "$BACKUP" "$MERGED"

	checkout_staged_file 1 "$MERGED" "$BASE"
	checkout_staged_file 2 "$MERGED" "$LOCAL"
	checkout_staged_file 3 "$MERGED" "$REMOTE"

	if test -z "$local_mode" || test -z "$remote_mode"
	then
		echo "Deleted merge conflict for '$MERGED':"
		describe_file "$local_mode" "local" "$LOCAL"
		describe_file "$remote_mode" "remote" "$REMOTE"
		resolve_deleted_merge
		return
	fi

	if is_symlink "$local_mode" || is_symlink "$remote_mode"
	then
		echo "Symbolic link merge conflict for '$MERGED':"
		describe_file "$local_mode" "local" "$LOCAL"
		describe_file "$remote_mode" "remote" "$REMOTE"
		resolve_symlink_merge
		return
	fi

	echo "Normal merge conflict for '$MERGED':"
	describe_file "$local_mode" "local" "$LOCAL"
	describe_file "$remote_mode" "remote" "$REMOTE"
	if test "$guessed_merge_tool" = true || test "$prompt" = true
	then
		printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
		read ans || return 1
	fi

	if base_present
	then
		present=true
	else
		present=false
	fi

	if ! run_merge_tool "$merge_tool" "$present"
	then
		echo "merge of $MERGED failed" 1>&2
		mv -- "$BACKUP" "$MERGED"

		if test "$merge_keep_temporaries" = "false"
		then
			cleanup_temp_files
		fi

		return 1
	fi

	if test "$merge_keep_backup" = "true"
	then
		mv -- "$BACKUP" "$MERGED.orig"
	else
		rm -- "$BACKUP"
	fi

	git add -- "$MERGED"
	cleanup_temp_files
	return 0
}

prompt=$(git config --bool mergetool.prompt)
guessed_merge_tool=false

while test $# != 0
do
	case "$1" in
	--tool-help=*)
		TOOL_MODE=${1#--tool-help=}
		show_tool_help
		;;
	--tool-help)
		show_tool_help
		;;
	-t|--tool*)
		case "$#,$1" in
		*,*=*)
			merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
			;;
		1,*)
			usage ;;
		*)
			merge_tool="$2"
			shift ;;
		esac
		;;
	-y|--no-prompt)
		prompt=false
		;;
	--prompt)
		prompt=true
		;;
	--)
		shift
		break
		;;
	-*)
		usage
		;;
	*)
		break
		;;
	esac
	shift
done

prompt_after_failed_merge () {
	while true
	do
		printf "Continue merging other unresolved paths (y/n) ? "
		read ans || return 1
		case "$ans" in
		[yY]*)
			return 0
			;;
		[nN]*)
			return 1
			;;
		esac
	done
}

git_dir_init
require_work_tree

if test -z "$merge_tool"
then
	# Check if a merge tool has been configured
	merge_tool=$(get_configured_merge_tool)
	# Try to guess an appropriate merge tool if no tool has been set.
	if test -z "$merge_tool"
	then
		merge_tool=$(guess_merge_tool) || exit
		guessed_merge_tool=true
	fi
fi
merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"

files=

if test $# -eq 0
then
	cd_to_toplevel

	if test -e "$GIT_DIR/MERGE_RR"
	then
		files=$(git rerere remaining)
	else
		files=$(git ls-files -u | sed -e 's/^[^	]*	//' | sort -u)
	fi
else
	files=$(git ls-files -u -- "$@" | sed -e 's/^[^	]*	//' | sort -u)
fi

if test -z "$files"
then
	echo "No files need merging"
	exit 0
fi

printf "Merging:\n"
printf "%s\n" "$files"

IFS='
'
rc=0
for i in $files
do
	printf "\n"
	if ! merge_file "$i"
	then
		rc=1
		prompt_after_failed_merge || exit 1
	fi
done

exit $rc
back to top