summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-applymbox.txt10
-rwxr-xr-xgit-applymbox.sh8
-rwxr-xr-xgit-applypatch.sh73
3 files changed, 86 insertions, 5 deletions
diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt
index bb543788c0..8f01ca6a16 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/<num> | <mbox> ) [ <signoff> ]
+'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
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 e2bfd02870..a83246cad8 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/<num> | mbox) [signoff]"
+ echo >&2 "applymbox [-u] [-k] [-q] [-m] (-c .dotest/<num> | 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 14635d9bce..66fd19ae2d 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