Add git-am, applymbox replacement.
authorJunio C Hamano <junkio@cox.net>
Fri, 7 Oct 2005 10:44:18 +0000 (03:44 -0700)
committerJunio C Hamano <junkio@cox.net>
Fri, 7 Oct 2005 10:44:18 +0000 (03:44 -0700)
It reorganizes the code and also has saner command line options
syntax.  Unlike git-applymbox, it can take more than one mailbox
file from the command line, as well as reading from the standard
input when '-' is specified.

Signed-off-by: Junio C Hamano <junkio@cox.net>
Makefile
git-am.sh [new file with mode: 0755]

index fd4e163..1bdf4de 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -85,7 +85,7 @@ SCRIPT_SH = \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-resolve.sh git-revert.sh git-sh-setup.sh git-status.sh \
        git-tag.sh git-verify-tag.sh git-whatchanged.sh git.sh \
-       git-applymbox.sh git-applypatch.sh \
+       git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
        git-merge-resolve.sh git-grep.sh
 
diff --git a/git-am.sh b/git-am.sh
new file mode 100755 (executable)
index 0000000..9e41e70
--- /dev/null
+++ b/git-am.sh
@@ -0,0 +1,337 @@
+#!/bin/sh
+#
+#
+. git-sh-setup || die "Not a git archive"
+
+files=$(git-diff-index --cached --name-only HEAD) || exit
+if [ "$files" ]; then
+   echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+   exit 1
+fi
+
+usage () {
+    echo >&2 "usage: $0 [--signoff] [--dotest=<dir>] [--utf8] [--3way] <mbox>"
+    echo >&2 " or, when resuming"
+    echo >&2 " $0 [--skip]"
+    exit 1;
+}
+
+stop_here () {
+    echo "$1" >"$dotest/next"
+    exit 1
+}
+
+go_next () {
+       rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
+               "$dotest/patch" "$dotest/info"
+       echo "$next" >"$dotest/next"
+       this=$next
+}
+
+fall_back_3way () {
+    O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+
+    rm -fr "$dotest"/patch-merge-*
+    mkdir "$dotest/patch-merge-tmp-dir"
+
+    # First see if the patch records the index info that we can use.
+    if git-apply --show-index-info "$dotest/patch" \
+       >"$dotest/patch-merge-index-info" 2>/dev/null &&
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-update-index --index-info <"$dotest/patch-merge-index-info" &&
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-write-tree >"$dotest/patch-merge-base+" &&
+       # index has the base tree now.
+       (
+           cd "$dotest/patch-merge-tmp-dir" &&
+           GIT_INDEX_FILE="../patch-merge-tmp-index" \
+           GIT_OBJECT_DIRECTORY="$O_OBJECT" \
+           git-apply --index <../patch
+        )
+    then
+       echo Using index info to reconstruct a base tree...
+       mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
+       mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
+    else
+       # Otherwise, try nearby trees that can be used to apply the
+       # patch.
+       (
+           N=10
+
+           # 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
+           # See if we have it as a tree...
+           git-cat-file tree "$base" >/dev/null 2>&1 || continue
+
+           rm -fr "$dotest"/patch-merge-* &&
+           mkdir "$dotest/patch-merge-tmp-dir" || break
+           (
+               cd "$dotest/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
+           ) <"$dotest/patch"  2>/dev/null && break
+       done
+    fi
+
+    test -f "$dotest/patch-merge-index" &&
+    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
+    orig_tree=$(cat "$dotest/patch-merge-base") &&
+    rm -fr "$dotest"/patch-merge-* || exit 1
+
+    echo Falling back to patching base and 3-way merge...
+
+    # 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.
+
+    git-merge-resolve $orig_tree -- HEAD $his_tree || {
+           echo Failed to merge in the changes.
+           exit 1
+    }
+}
+
+prec=4
+dotest=.dotest sign= utf8= keep= skip= interactive=
+
+while case "$#" in 0) break;; esac
+do
+       case "$1" in
+       -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
+       dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;;
+       -d|--d|--do|--dot|--dote|--dotes|--dotest)
+       case "$#" in 1) usage ;; esac; shift
+       dotest="$1"; shift;;
+
+       -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
+       --interacti|--interactiv|--interactive)
+       interactive=t; shift ;;
+
+       -3|--3|--3w|--3wa|--3way)
+       threeway=t; shift ;;
+       -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+       sign=t; shift ;;
+       -u|--u|--ut|--utf|--utf8)
+       utf8=t; shift ;;
+       -k|--k|--ke|--kee|--keep)
+       keep=t; shift ;;
+
+       --sk|--ski|--skip)
+       skip=t; shift ;;
+
+       --)
+       shift; break ;;
+       -*)
+       usage ;;
+       *)
+       break ;;
+       esac
+done
+
+if test -d "$dotest" &&
+   last=$(cat "$dotest/last") &&
+   next=$(cat "$dotest/next") &&
+   test $# != 0 &&
+   test "$next" -gt "$last"
+then
+   rm -fr "$dotest"
+fi
+
+if test -d "$dotest"
+then
+       test ",$#," = ",0," ||
+       die "previous dotest directory $dotest still exists but mbox given."
+else
+       # Make sure we are not given --skip
+       test ",$skip," = ,, ||
+       die "we are not resuming."
+
+       # Start afresh.
+       mkdir -p "$dotest" || exit
+
+       # cat does the right thing for us, including '-' to mean
+       # standard input.
+       cat "$@" |
+       git-mailsplit -d$prec "$dotest/" >"$dotest/last" || {
+               rm -fr "$dotest"
+               exit 1
+       }
+
+       echo "$sign" >"$dotest/sign"
+       echo "$utf8" >"$dotest/utf8"
+       echo "$keep" >"$dotest/keep"
+       echo "$threeway" >"$dotest/3way"
+       echo 1 >"$dotest/next"
+fi
+
+if test "$(cat "$dotest/utf8")" = t
+then
+       utf8=-u
+fi
+if test "$(cat "$dotest/keep")" = t
+then
+       keep=-k
+fi
+if test "$(cat "$dotest/sign")" = t
+then
+       SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
+                       s/>.*/>/
+                       s/^/Signed-off-by: /'
+               `
+else
+       SIGNOFF=
+fi
+threeway=$(cat "$dotest/3way")
+
+last=`cat "$dotest/last"`
+this=`cat "$dotest/next"`
+if test "$skip" = t
+then
+       this=`expr "$this" + 1`
+fi
+
+if test "$this" -gt "$last"
+then
+       echo Nothing to do.
+       rm -fr "$dotest"
+       exit
+fi
+
+while test "$this" -le "$last"
+do
+       msgnum=`printf "%0${prec}d" $this`
+       next=`expr "$this" + 1`
+       test -f "$dotest/$msgnum" || {
+               go_next
+               continue
+       }
+       git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
+               <"$dotest/$msgnum" >"$dotest/info" ||
+               stop_here $this
+       git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
+
+       GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
+       GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
+       GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
+       SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
+
+       case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
+       if test '' != "$SIGNOFF"
+       then
+               LAST_SIGNED_OFF_BY=`
+                       sed -ne '/^Signed-off-by: /p' "$dotest/msg-clean" |
+                       tail -n 1
+               `
+               ADD_SIGNOFF=$(test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+                   test '' = "$LAST_SIGNED_OFF_BY" && echo
+                   echo "$SIGNOFF"
+               })
+       else
+               ADD_SIGNOFF=
+       fi
+       {
+               echo "$SUBJECT"
+               if test -s "$dotest/msg-clean"
+               then
+                       echo
+                       cat "$dotest/msg-clean"
+               fi
+               if test '' != "$ADD_SIGNOFF"
+               then
+                       echo "$ADD_SIGNOFF"
+               fi
+       } >"$dotest/final-commit"
+
+       if test "$interactive" = t
+       then
+           action=again
+           while test "$action" = again
+           do
+               echo "Commit Body is:"
+               echo "--------------------------"
+               cat "$dotest/final-commit"
+               echo "--------------------------"
+               echo -n "Apply? [y]es/[n]o/[e]dit/[a]ccept all "
+               read reply
+               case "$reply" in
+               y*|Y*) action=yes ;;
+               a*|A*) action=yes interactive= ;;
+               n*|N*) action=skip ;;
+               e*|E*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
+                      action=again ;;
+               esac
+           done
+       else
+           action=yes
+       fi
+
+       if test $action = skip
+       then
+               go_next
+               continue
+       fi
+
+       if test -x "$GIT_DIR"/hooks/applypatch-msg
+       then
+               "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
+               stop_here $this
+       fi
+
+       echo
+       echo "Applying '$SUBJECT'"
+       echo
+
+       git-apply --index "$dotest/patch"; apply_status=$?
+       if test $apply_status = 1 && test "$threeway" = t
+       then
+               (fall_back_3way) || stop_here $this
+
+               # Applying the patch to an earlier tree and merging the
+               # result may have produced the same tree as ours.
+               if test '' = "$(git-diff-index --cached --name-only -z HEAD)"
+               then
+                       echo No changes -- Patch already applied.
+                       go_next
+                       continue
+               fi
+       fi
+       if test $apply_status != 0
+       then
+               echo Patch failed at $msgnum.
+               stop_here $this
+       fi
+
+       if test -x "$GIT_DIR"/hooks/pre-applypatch
+       then
+               "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+       fi
+
+       tree=$(git-write-tree) &&
+       echo Wrote tree $tree &&
+       parent=$(git-rev-parse --verify HEAD) &&
+       commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
+       echo Committed: $commit &&
+       git-update-ref HEAD $commit $parent ||
+       stop_here $this
+
+       if test -x "$GIT_DIR"/hooks/post-applypatch
+       then
+               "$GIT_DIR"/hooks/post-applypatch
+       fi
+
+       go_next
+done
+
+rm -fr "$dotest"