Revision 3ba2e8653c88d220b0b22f35260477bb0afa7d7b authored by Johannes Sixt on 16 March 2011, 08:18:49 UTC, committed by Junio C Hamano on 17 March 2011, 21:54:11 UTC
'git stash create' must operate with a temporary index. For this purpose,
it used 'cp -p' to create a copy. -p is needed to preserve the timestamp
of the index file. Now Jakob Pfender reported a certain combination of
a Linux NFS client, OpenBSD NFS server, and cp implementation where this
operation failed.

Luckily, the first operation in git-stash after copying the index is to
call 'git read-tree'. Therefore, use --index-output instead of 'cp -p'
to write the copy of the index.

--index-output requires that the specified file is on the same volume as
the source index, so that the lock file can be rename()d. For this reason,
the name of the temporary index is constructed in a way different from the
other temporary files. The code path of 'stash -p' also needs a temporary
index, but we do not use the new name because it does not depend on the
same precondition as --index-output.

Signed-off-by: Johannes Sixt <j6t@kdbg.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 23a32ff
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] [-y|--no-prompt|--prompt] [file to merge] ...'
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
TOOL_MODE=merge
. git-sh-setup
. git-mergetool--lib
require_work_tree

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

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

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

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

cleanup_temp_files () {
    if test "$1" = --save-backup ; then
	mv -- "$BACKUP" "$MERGED.orig"
	rm -f -- "$LOCAL" "$REMOTE" "$BASE"
    else
	rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
    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")'"
    else
	if base_present; then
	    echo "modified"
	else
	    echo "created"
	fi
    fi
}


resolve_symlink_merge () {
    while true; do
	printf "Use (l)ocal or (r)emote, or (a)bort? "
	read ans
	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
	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
}

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

    if test $? -eq 0 -a -n "$tmpfile" ; then
	mv -- "$(git rev-parse --show-cdup)$tmpfile" "$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

    ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
    BACKUP="./$MERGED.BACKUP.$ext"
    LOCAL="./$MERGED.LOCAL.$ext"
    REMOTE="./$MERGED.REMOTE.$ext"
    BASE="./$MERGED.BASE.$ext"

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

    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;}')

    base_present   && checkout_staged_file 1 "$MERGED" "$BASE"
    local_present  && checkout_staged_file 2 "$MERGED" "$LOCAL"
    remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"

    if test -z "$local_mode" -o -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 "$prompt" = true; then
	printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
	read ans
    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 || echo true)

while test $# != 0
do
    case "$1" in
	-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
	case "$ans" in

	    [yY]*)
		return 0
		;;

	    [nN]*)
		return 1
		;;
	esac
    done
}

if test -z "$merge_tool"; then
    merge_tool=$(get_merge_tool "$merge_tool") || exit
fi
merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"

last_status=0
rollup_status=0
rerere=false

files_to_merge() {
    if test "$rerere" = true
    then
	git rerere status
    else
	git ls-files -u | sed -e 's/^[^	]*	//' | sort -u
    fi
}


if test $# -eq 0 ; then
    cd_to_toplevel

    if test -e "$GIT_DIR/MERGE_RR"
    then
	rerere=true
    fi

    files=$(files_to_merge)
    if test -z "$files" ; then
	echo "No files need merging"
	exit 0
    fi

    # Save original stdin
    exec 3<&0

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

    files_to_merge |
    while IFS= read i
    do
	if test $last_status -ne 0; then
	    prompt_after_failed_merge <&3 || exit 1
	fi
	printf "\n"
	merge_file "$i" <&3
	last_status=$?
	if test $last_status -ne 0; then
	    rollup_status=1
	fi
    done
else
    while test $# -gt 0; do
	if test $last_status -ne 0; then
	    prompt_after_failed_merge || exit 1
	fi
	printf "\n"
	merge_file "$1"
	last_status=$?
	if test $last_status -ne 0; then
	    rollup_status=1
	fi
	shift
    done
fi

exit $rollup_status
back to top