From a2a92ab17fe120f6b28d123ac2fd41f066f0de1e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 29 Nov 2006 18:53:13 -0800 Subject: [PATCH] git-merge: preserve and merge local changes when doing fast forward The idea and the logic are identical to what "checkout -m" does when switching the branches. Instead of refusing the two-way merge, perform the three-way merge between the old head, the working tree and the new head, and leave the (potentially conflicted) merge result in the working tree. When the resulting conflict were too much to handle for the user, there is no easy way to get that back, so they are stashed away in $GIT_DIR/LOCAL_DIFF file. We do the same for "git checkout -m". If this turns out to be a sane thing to do, we probably should make the common logic between "checkout -m" and this into a built-in command. Signed-off-by: Junio C Hamano --- git-checkout.sh | 7 +++++++ git-merge.sh | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/git-checkout.sh b/git-checkout.sh index 737abd0c09..5bf399a276 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -168,6 +168,9 @@ else exit 1 ;; esac + # First stash away the local changes + git diff-index --binary -p HEAD >"$GIT_DIR"/LOCAL_DIFF + # Match the index to the working tree, and do a three-way. git diff-files --name-only | git update-index --remove --stdin && work=`git write-tree` && @@ -195,6 +198,10 @@ else sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /" echo "$unmerged" ) | git update-index --index-info + + echo >&2 "Conflicts in locally modified files:" + git diff --name-only --diff-filter=U >&2 + echo >&2 "Your local changes are found in $GIT_DIR/LOCAL_DIFF" ;; esac exit 0 diff --git a/git-merge.sh b/git-merge.sh index 272f004622..798aad3576 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -91,6 +91,51 @@ finish () { esac } +merge_local_changes () { + merge_error=$(git-read-tree -m -u $1 $2 2>&1) || ( + + # First stash away the local changes + git diff-index --binary -p HEAD >"$GIT_DIR"/LOCAL_DIFF + + # Match the index to the working tree, and do a three-way. + git diff-files --name-only | + git update-index --remove --stdin && + work=`git write-tree` && + git read-tree --reset -u $2 && + git read-tree -m -u --aggressive $1 $2 $work || exit + + echo >&2 "Carrying local changes forward." + if result=`git write-tree 2>/dev/null` + then + echo >&2 "Trivially automerged." + else + git merge-index -o git-merge-one-file -a + fi + + # Do not register the cleanly merged paths in the index + # yet; this is not a real merge before committing, but + # just carrying the working tree changes along. + unmerged=`git ls-files -u` + git read-tree --reset $2 + case "$unmerged" in + '') ;; + *) + ( + z40=0000000000000000000000000000000000000000 + echo "$unmerged" | + sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /" + echo "$unmerged" + ) | git update-index --index-info + + echo >&2 "Conflicts in locally modified files:" + git diff --name-only --diff-filter=U >&2 + echo >&2 "Your local changes are found in $GIT_DIR/LOCAL_DIFF" + ;; + esac + exit 0 + ) +} + case "$#" in 0) usage ;; esac rloga= have_message= @@ -264,7 +309,7 @@ f,*) echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)" git-update-index --refresh 2>/dev/null new_head=$(git-rev-parse --verify "$1^0") && - git-read-tree -u -v -m $head "$new_head" && + merge_local_changes $head $new_head && finish "$new_head" "Fast forward" dropsave exit 0