From 47f0b6d5d49247b85898083d1ccf4f899ef7294a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 6 Oct 2005 14:25:52 -0700 Subject: Fall back to three-way merge when applying a patch. After git-apply fails, attempt to find a base tree that the patch cleanly applies to, and do a three-way merge using that base tree into the current index, if .dotest/.3way file exists. This flag can be controlled by giving -m flag to git-applymbox command. When the fall-back merge fails, the working tree can be resolved the same way as you would normally hand resolve a conflicting merge. When making commit, use .dotest/final-commit as the log message template. Or you could just choose to 'git-checkout-index -f -a' to revert the failed merge. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt index bb54378..8f01ca6 100644 --- a/Documentation/git-applymbox.txt +++ b/Documentation/git-applymbox.txt @@ -8,7 +8,7 @@ git-applymbox - Apply a series of patches in a mailbox SYNOPSIS -------- -'git-applymbox' [-u] [-k] [-q] ( -c .dotest/ | ) [ ] +'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/ | ) [ ] DESCRIPTION ----------- @@ -33,6 +33,14 @@ OPTIONS munging, and is most useful when used to read back 'git format-patch --mbox' output. +-m:: + Patches are applied with `git-apply` command, and unless + it cleanly applies without fuzz, the processing fails. + With this flag, if a tree that the patch applies cleanly + is found in a repository, the patch is applied to the + tree and then a 3-way merge between the resulting tree + and the current tree. + -u:: By default, the commit log message, author name and author email are taken from the e-mail without any diff --git a/git-applymbox.sh b/git-applymbox.sh index e2bfd02..a83246c 100755 --- a/git-applymbox.sh +++ b/git-applymbox.sh @@ -9,8 +9,6 @@ ## You give it a mbox-format collection of emails, and it will try to ## apply them to the kernel using "applypatch" ## -## applymbox [-u] [-k] [-q] (-c .dotest/msg-number | mail_archive) [Signoff_file]" -## ## The patch application may fail in the middle. In which case: ## (1) look at .dotest/patch and fix it up to apply ## (2) re-run applymbox with -c .dotest/msg-number for the current one. @@ -21,7 +19,7 @@ . git-sh-setup || die "Not a git archive" usage () { - echo >&2 "applymbox [-u] [-k] [-q] (-c .dotest/ | mbox) [signoff]" + echo >&2 "applymbox [-u] [-k] [-q] [-m] (-c .dotest/ | mbox) [signoff]" exit 1 } @@ -33,6 +31,7 @@ do -k) keep_subject=-k ;; -q) query_apply=t ;; -c) continue="$2"; resume=f; shift ;; + -m) fallback_3way=t ;; -*) usage ;; *) break ;; esac @@ -56,6 +55,9 @@ fi case "$query_apply" in t) touch .dotest/.query_apply esac +case "$fall_back_3way" in +t) : >.dotest/.3way +esac case "$keep_subject" in -k) : >.dotest/.keep_subject esac diff --git a/git-applypatch.sh b/git-applypatch.sh index 14635d9..66fd19a 100755 --- a/git-applypatch.sh +++ b/git-applypatch.sh @@ -22,6 +22,8 @@ query_apply=.dotest/.query_apply ## if this file exists. keep_subject=.dotest/.keep_subject +## We do not attempt the 3-way merge fallback unless this file exists. +fall_back_3way=.dotest/.3way MSGFILE=$1 PATCHFILE=$2 @@ -102,10 +104,79 @@ echo Applying "'$SUBJECT'" echo git-apply --index "$PATCHFILE" || { + + # git-apply exits with status 1 when the patch does not apply, + # but it die()s with other failures, most notably upon corrupt + # patch. In the latter case, there is no point to try applying + # it to another tree and do 3-way merge. + test $? = 1 || exit 1 + + test -f "$fall_back_3way" || exit 1 + # Here if we know which revision the patch applies to, # we create a temporary working tree and index, apply the # patch, and attempt 3-way merge with the resulting tree. - exit 1 + + O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd` + rm -fr .patch-merge-* + + ( + N=10 + + # if the patch records the base tree... + sed -ne ' + /^diff /q + /^applies-to: \([0-9a-f]*\)$/{ + s//\1/p + q + } + ' "$PATCHFILE" + + # or hoping the patch is against our recent commits... + git-rev-list --max-count=$N HEAD + + # or hoping the patch is against known tags... + git-ls-remote --tags . + ) | + while read base junk + do + # Try it if we have it as a tree. + git-cat-file tree "$base" >/dev/null 2>&1 || continue + + rm -fr .patch-merge-tmp-* && + mkdir .patch-merge-tmp-dir || break + ( + cd .patch-merge-tmp-dir && + GIT_INDEX_FILE=../.patch-merge-tmp-index && + GIT_OBJECT_DIRECTORY="$O_OBJECT" && + export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY && + git-read-tree "$base" && + git-apply --index && + mv ../.patch-merge-tmp-index ../.patch-merge-index && + echo "$base" >../.patch-merge-base + ) <"$PATCHFILE" 2>/dev/null && break + done + + test -f .patch-merge-index && + his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) && + orig_tree=$(cat .patch-merge-base) && + rm -fr .patch-merge-* || exit 1 + + echo Falling back to patching base and 3-way merge using $orig_tree... + + # This is not so wrong. Depending on which base we picked, + # orig_tree may be wildly different from ours, but his_tree + # has the same set of wildly different changes in parts the + # patch did not touch, so resolve ends up cancelling them, + # saying that we reverted all those changes. + + if git-merge-resolve $orig_tree -- HEAD $his_tree + then + echo Done. + else + echo Failed to merge in the changes. + exit 1 + fi } if test -x "$GIT_DIR"/hooks/pre-applypatch -- cgit v0.10.2-6-g49f6