diff options
Diffstat (limited to 'git-applypatch.sh')
-rwxr-xr-x | git-applypatch.sh | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/git-applypatch.sh b/git-applypatch.sh new file mode 100755 index 0000000000..8df2aee4c2 --- /dev/null +++ b/git-applypatch.sh @@ -0,0 +1,212 @@ +#!/bin/sh +## +## applypatch takes four file arguments, and uses those to +## apply the unpacked patch (surprise surprise) that they +## represent to the current tree. +## +## The arguments are: +## $1 - file with commit message +## $2 - file with the actual patch +## $3 - "info" file with Author, email and subject +## $4 - optional file containing signoff to add +## + +USAGE='<msg> <patch> <info> [<signoff>]' +. git-sh-setup + +case "$#" in 3|4) ;; *) usage ;; esac + +final=.dotest/final-commit +## +## If this file exists, we ask before applying +## +query_apply=.dotest/.query_apply + +## We do not munge the first line of the commit message too much +## 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 +INFO=$3 +SIGNOFF=$4 +EDIT=${VISUAL:-${EDITOR:-vi}} + +export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")" +export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")" +export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")" +export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")" + +if test '' != "$SIGNOFF" +then + if test -f "$SIGNOFF" + then + SIGNOFF=`cat "$SIGNOFF"` || exit + elif case "$SIGNOFF" in yes | true | me | please) : ;; *) false ;; esac + then + SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e ' + s/>.*/>/ + s/^/Signed-off-by: /' + ` + else + SIGNOFF= + fi + if test '' != "$SIGNOFF" + then + LAST_SIGNED_OFF_BY=` + sed -ne '/^Signed-off-by: /p' "$MSGFILE" | + tail -n 1 + ` + test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || { + test '' = "$LAST_SIGNED_OFF_BY" && echo + echo "$SIGNOFF" + } >>"$MSGFILE" + fi +fi + +patch_header= +test -f "$keep_subject" || patch_header='[PATCH] ' + +{ + echo "$patch_header$SUBJECT" + if test -s "$MSGFILE" + then + echo + cat "$MSGFILE" + fi +} >"$final" + +interactive=yes +test -f "$query_apply" || interactive=no + +while [ "$interactive" = yes ]; do + echo "Commit Body is:" + echo "--------------------------" + cat "$final" + echo "--------------------------" + printf "Apply? [y]es/[n]o/[e]dit/[a]ccept all " + read reply + case "$reply" in + y|Y) interactive=no;; + n|N) exit 2;; # special value to tell dotest to keep going + e|E) "$EDIT" "$final";; + a|A) rm -f "$query_apply" + interactive=no ;; + esac +done + +if test -x "$GIT_DIR"/hooks/applypatch-msg +then + "$GIT_DIR"/hooks/applypatch-msg "$final" || exit +fi + +echo +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. + + O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd` + rm -fr .patch-merge-* + + if git-apply -z --index-info "$PATCHFILE" \ + >.patch-merge-index-info 2>/dev/null && + GIT_INDEX_FILE=.patch-merge-tmp-index \ + git-update-index -z --index-info <.patch-merge-index-info && + GIT_INDEX_FILE=.patch-merge-tmp-index \ + git-write-tree >.patch-merge-tmp-base && + ( + mkdir .patch-merge-tmp-dir && + cd .patch-merge-tmp-dir && + GIT_INDEX_FILE="../.patch-merge-tmp-index" \ + GIT_OBJECT_DIRECTORY="$O_OBJECT" \ + git-apply $binary --index + ) <"$PATCHFILE" + then + echo Using index info to reconstruct a base tree... + mv .patch-merge-tmp-base .patch-merge-base + mv .patch-merge-tmp-index .patch-merge-index + else + ( + N=10 + + # Otherwise, try nearby trees that can be used to apply the + # patch. + 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 + fi + + 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 canceling 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 +then + "$GIT_DIR"/hooks/pre-applypatch || exit +fi + +tree=$(git-write-tree) || exit 1 +echo Wrote tree $tree +parent=$(git-rev-parse --verify HEAD) && +commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1 +echo Committed: $commit +git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit + +if test -x "$GIT_DIR"/hooks/post-applypatch +then + "$GIT_DIR"/hooks/post-applypatch +fi |