mirror of
https://github.com/git/git
synced 2024-08-28 12:09:19 +00:00
![Junio C Hamano](/assets/img/avatar_default.png)
Traditionally git-rebase was implemented in terms of "format-patch" piped to "am -3", to strike balance between speed (because it avoids a rather expensive read-tree/merge-recursive machinery most of the time) and flexibility (the magic "-3" allows it to fall back to 3-way merge as necessary). However, this combination has one flaw when dealing with a nonstandard commit log message format that has more than one lines in the first paragraph. This teaches "git am --rebasing" to take advantage of the fact that the mbox message "git rebase" prepares for it records the original commit object name, to get the log message from the original commit object instead. Signed-off-by: Junio C Hamano <gitster@pobox.com>
511 lines
12 KiB
Bash
Executable file
511 lines
12 KiB
Bash
Executable file
#!/bin/sh
|
|
#
|
|
# Copyright (c) 2005, 2006 Junio C Hamano
|
|
|
|
SUBDIRECTORY_OK=Yes
|
|
OPTIONS_KEEPDASHDASH=
|
|
OPTIONS_SPEC="\
|
|
git-am [options] <mbox>|<Maildir>...
|
|
git-am [options] --resolved
|
|
git-am [options] --skip
|
|
--
|
|
d,dotest= (removed -- do not use)
|
|
i,interactive run interactively
|
|
b,binary pass --allo-binary-replacement to git-apply
|
|
3,3way allow fall back on 3way merging if needed
|
|
s,signoff add a Signed-off-by line to the commit message
|
|
u,utf8 recode into utf8 (default)
|
|
k,keep pass -k flag to git-mailinfo
|
|
whitespace= pass it through git-apply
|
|
C= pass it through git-apply
|
|
p= pass it through git-apply
|
|
resolvemsg= override error message when patch failure occurs
|
|
r,resolved to be used after a patch failure
|
|
skip skip the current patch
|
|
rebasing (internal use for git-rebase)"
|
|
|
|
. git-sh-setup
|
|
prefix=$(git rev-parse --show-prefix)
|
|
set_reflog_action am
|
|
require_work_tree
|
|
cd_to_toplevel
|
|
|
|
git var GIT_COMMITTER_IDENT >/dev/null || exit
|
|
|
|
stop_here () {
|
|
echo "$1" >"$dotest/next"
|
|
exit 1
|
|
}
|
|
|
|
stop_here_user_resolve () {
|
|
if [ -n "$resolvemsg" ]; then
|
|
printf '%s\n' "$resolvemsg"
|
|
stop_here $1
|
|
fi
|
|
cmdline=$(basename $0)
|
|
if test '' != "$interactive"
|
|
then
|
|
cmdline="$cmdline -i"
|
|
fi
|
|
if test '' != "$threeway"
|
|
then
|
|
cmdline="$cmdline -3"
|
|
fi
|
|
echo "When you have resolved this problem run \"$cmdline --resolved\"."
|
|
echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
|
|
|
|
stop_here $1
|
|
}
|
|
|
|
go_next () {
|
|
rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
|
|
"$dotest/patch" "$dotest/info"
|
|
echo "$next" >"$dotest/next"
|
|
this=$next
|
|
}
|
|
|
|
cannot_fallback () {
|
|
echo "$1"
|
|
echo "Cannot fall back to three-way merge."
|
|
exit 1
|
|
}
|
|
|
|
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.
|
|
git apply --build-fake-ancestor "$dotest/patch-merge-tmp-index" \
|
|
"$dotest/patch" &&
|
|
GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
|
|
git write-tree >"$dotest/patch-merge-base+" ||
|
|
cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
|
|
|
|
echo Using index info to reconstruct a base tree...
|
|
if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
|
|
git apply $binary --cached <"$dotest/patch"
|
|
then
|
|
mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
|
|
mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
|
|
else
|
|
cannot_fallback "Did you hand edit your patch?
|
|
It does not apply to blobs recorded in its index."
|
|
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 recursive ends up canceling them,
|
|
# saying that we reverted all those changes.
|
|
|
|
eval GITHEAD_$his_tree='"$FIRSTLINE"'
|
|
export GITHEAD_$his_tree
|
|
git-merge-recursive $orig_tree -- HEAD $his_tree || {
|
|
git rerere
|
|
echo Failed to merge in the changes.
|
|
exit 1
|
|
}
|
|
unset GITHEAD_$his_tree
|
|
}
|
|
|
|
prec=4
|
|
dotest=".dotest"
|
|
sign= utf8=t keep= skip= interactive= resolved= binary= rebasing=
|
|
resolvemsg= resume=
|
|
git_apply_opt=
|
|
|
|
while test $# != 0
|
|
do
|
|
case "$1" in
|
|
-i|--interactive)
|
|
interactive=t ;;
|
|
-b|--binary)
|
|
binary=t ;;
|
|
-3|--3way)
|
|
threeway=t ;;
|
|
-s|--signoff)
|
|
sign=t ;;
|
|
-u|--utf8)
|
|
utf8=t ;; # this is now default
|
|
--no-utf8)
|
|
utf8= ;;
|
|
-k|--keep)
|
|
keep=t ;;
|
|
-r|--resolved)
|
|
resolved=t ;;
|
|
--skip)
|
|
skip=t ;;
|
|
--rebasing)
|
|
rebasing=t threeway=t keep=t binary=t ;;
|
|
-d|--dotest)
|
|
die "-d option is no longer supported. Do not use."
|
|
;;
|
|
--resolvemsg)
|
|
shift; resolvemsg=$1 ;;
|
|
--whitespace)
|
|
git_apply_opt="$git_apply_opt $1=$2"; shift ;;
|
|
-C|-p)
|
|
git_apply_opt="$git_apply_opt $1$2"; shift ;;
|
|
--)
|
|
shift; break ;;
|
|
*)
|
|
usage ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# If the dotest directory exists, but we have finished applying all the
|
|
# patches in them, clear it out.
|
|
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
|
|
case "$#,$skip$resolved" in
|
|
0,*t*)
|
|
# Explicit resume command and we do not have file, so
|
|
# we are happy.
|
|
: ;;
|
|
0,)
|
|
# No file input but without resume parameters; catch
|
|
# user error to feed us a patch from standard input
|
|
# when there is already $dotest. This is somewhat
|
|
# unreliable -- stdin could be /dev/null for example
|
|
# and the caller did not intend to feed us a patch but
|
|
# wanted to continue unattended.
|
|
tty -s
|
|
;;
|
|
*)
|
|
false
|
|
;;
|
|
esac ||
|
|
die "previous dotest directory $dotest still exists but mbox given."
|
|
resume=yes
|
|
else
|
|
# Make sure we are not given --skip nor --resolved
|
|
test ",$skip,$resolved," = ,,, ||
|
|
die "Resolve operation not in progress, we are not resuming."
|
|
|
|
# Start afresh.
|
|
mkdir -p "$dotest" || exit
|
|
|
|
if test -n "$prefix" && test $# != 0
|
|
then
|
|
first=t
|
|
for arg
|
|
do
|
|
test -n "$first" && {
|
|
set x
|
|
first=
|
|
}
|
|
case "$arg" in
|
|
/*)
|
|
set "$@" "$arg" ;;
|
|
*)
|
|
set "$@" "$prefix$arg" ;;
|
|
esac
|
|
done
|
|
shift
|
|
fi
|
|
git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" || {
|
|
rm -fr "$dotest"
|
|
exit 1
|
|
}
|
|
|
|
# -b, -s, -u, -k and --whitespace flags are kept for the
|
|
# resuming session after a patch failure.
|
|
# -3 and -i can and must be given when resuming.
|
|
echo "$binary" >"$dotest/binary"
|
|
echo " $ws" >"$dotest/whitespace"
|
|
echo "$sign" >"$dotest/sign"
|
|
echo "$utf8" >"$dotest/utf8"
|
|
echo "$keep" >"$dotest/keep"
|
|
echo 1 >"$dotest/next"
|
|
if test -n "$rebasing"
|
|
then
|
|
: >"$dotest/rebasing"
|
|
else
|
|
: >"$dotest/applying"
|
|
fi
|
|
fi
|
|
|
|
case "$resolved" in
|
|
'')
|
|
files=$(git diff-index --cached --name-only HEAD --) || exit
|
|
if [ "$files" ]; then
|
|
echo "Dirty index: cannot apply patches (dirty: $files)" >&2
|
|
exit 1
|
|
fi
|
|
esac
|
|
|
|
if test "$(cat "$dotest/binary")" = t
|
|
then
|
|
binary=--allow-binary-replacement
|
|
fi
|
|
if test "$(cat "$dotest/utf8")" = t
|
|
then
|
|
utf8=-u
|
|
else
|
|
utf8=-n
|
|
fi
|
|
if test "$(cat "$dotest/keep")" = t
|
|
then
|
|
keep=-k
|
|
fi
|
|
ws=`cat "$dotest/whitespace"`
|
|
if test "$(cat "$dotest/sign")" = t
|
|
then
|
|
SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
|
|
s/>.*/>/
|
|
s/^/Signed-off-by: /'
|
|
`
|
|
else
|
|
SIGNOFF=
|
|
fi
|
|
|
|
last=`cat "$dotest/last"`
|
|
this=`cat "$dotest/next"`
|
|
if test "$skip" = t
|
|
then
|
|
git rerere clear
|
|
this=`expr "$this" + 1`
|
|
resume=
|
|
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" || {
|
|
resume=
|
|
go_next
|
|
continue
|
|
}
|
|
|
|
# If we are not resuming, parse and extract the patch information
|
|
# into separate files:
|
|
# - info records the authorship and title
|
|
# - msg is the rest of commit log message
|
|
# - patch is the patch body.
|
|
#
|
|
# When we are resuming, these files are either already prepared
|
|
# by the user, or the user can tell us to do so by --resolved flag.
|
|
case "$resume" in
|
|
'')
|
|
git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
|
|
<"$dotest/$msgnum" >"$dotest/info" ||
|
|
stop_here $this
|
|
|
|
# skip pine's internal folder data
|
|
grep '^Author: Mail System Internal Data$' \
|
|
<"$dotest"/info >/dev/null &&
|
|
go_next && continue
|
|
|
|
test -s $dotest/patch || {
|
|
echo "Patch is empty. Was it split wrong?"
|
|
stop_here $this
|
|
}
|
|
if test -f "$dotest/rebasing" &&
|
|
commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
|
|
-e q "$dotest/$msgnum") &&
|
|
test "$(git cat-file -t "$commit")" = commit
|
|
then
|
|
git cat-file commit "$commit" |
|
|
sed -e '1,/^$/d' >"$dotest/msg-clean"
|
|
else
|
|
SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
|
|
case "$keep_subject" in -k) SUBJECT="[PATCH] $SUBJECT" ;; esac
|
|
|
|
(echo "$SUBJECT" ; echo ; cat "$dotest/msg") |
|
|
git stripspace > "$dotest/msg-clean"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
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")"
|
|
|
|
if test -z "$GIT_AUTHOR_EMAIL"
|
|
then
|
|
echo "Patch does not have a valid e-mail address."
|
|
stop_here $this
|
|
fi
|
|
|
|
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
|
|
|
|
case "$resume" in
|
|
'')
|
|
if test '' != "$SIGNOFF"
|
|
then
|
|
LAST_SIGNED_OFF_BY=`
|
|
sed -ne '/^Signed-off-by: /p' \
|
|
"$dotest/msg-clean" |
|
|
sed -ne '$p'
|
|
`
|
|
ADD_SIGNOFF=`
|
|
test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
|
|
test '' = "$LAST_SIGNED_OFF_BY" && echo
|
|
echo "$SIGNOFF"
|
|
}`
|
|
else
|
|
ADD_SIGNOFF=
|
|
fi
|
|
{
|
|
if test -s "$dotest/msg-clean"
|
|
then
|
|
cat "$dotest/msg-clean"
|
|
fi
|
|
if test '' != "$ADD_SIGNOFF"
|
|
then
|
|
echo "$ADD_SIGNOFF"
|
|
fi
|
|
} >"$dotest/final-commit"
|
|
;;
|
|
*)
|
|
case "$resolved$interactive" in
|
|
tt)
|
|
# This is used only for interactive view option.
|
|
git diff-index -p --cached HEAD -- >"$dotest/patch"
|
|
;;
|
|
esac
|
|
esac
|
|
FIRSTLINE=$(head -1 "$dotest/final-commit")
|
|
|
|
resume=
|
|
if test "$interactive" = t
|
|
then
|
|
test -t 0 ||
|
|
die "cannot be interactive without stdin connected to a terminal."
|
|
action=again
|
|
while test "$action" = again
|
|
do
|
|
echo "Commit Body is:"
|
|
echo "--------------------------"
|
|
cat "$dotest/final-commit"
|
|
echo "--------------------------"
|
|
printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
|
|
read reply
|
|
case "$reply" in
|
|
[yY]*) action=yes ;;
|
|
[aA]*) action=yes interactive= ;;
|
|
[nN]*) action=skip ;;
|
|
[eE]*) git_editor "$dotest/final-commit"
|
|
FIRSTLINE=$(head -1 "$dotest/final-commit")
|
|
action=again ;;
|
|
[vV]*) action=again
|
|
LESS=-S ${PAGER:-less} "$dotest/patch" ;;
|
|
*) 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
|
|
|
|
printf 'Applying %s\n' "$FIRSTLINE"
|
|
|
|
case "$resolved" in
|
|
'')
|
|
git apply $git_apply_opt $binary --index "$dotest/patch"
|
|
apply_status=$?
|
|
;;
|
|
t)
|
|
# Resolved means the user did all the hard work, and
|
|
# we do not have to do any patch application. Just
|
|
# trust what the user has in the index file and the
|
|
# working tree.
|
|
resolved=
|
|
git diff-index --quiet --cached HEAD -- && {
|
|
echo "No changes - did you forget to use 'git add'?"
|
|
stop_here_user_resolve $this
|
|
}
|
|
unmerged=$(git ls-files -u)
|
|
if test -n "$unmerged"
|
|
then
|
|
echo "You still have unmerged paths in your index"
|
|
echo "did you forget to use 'git add'?"
|
|
stop_here_user_resolve $this
|
|
fi
|
|
apply_status=0
|
|
git rerere
|
|
;;
|
|
esac
|
|
|
|
if test $apply_status = 1 && test "$threeway" = t
|
|
then
|
|
if (fall_back_3way)
|
|
then
|
|
# Applying the patch to an earlier tree and merging the
|
|
# result may have produced the same tree as ours.
|
|
git diff-index --quiet --cached HEAD -- && {
|
|
echo No changes -- Patch already applied.
|
|
go_next
|
|
continue
|
|
}
|
|
# clear apply_status -- we have successfully merged.
|
|
apply_status=0
|
|
fi
|
|
fi
|
|
if test $apply_status != 0
|
|
then
|
|
echo Patch failed at $msgnum.
|
|
stop_here_user_resolve $this
|
|
fi
|
|
|
|
if test -x "$GIT_DIR"/hooks/pre-applypatch
|
|
then
|
|
"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
|
|
fi
|
|
|
|
tree=$(git write-tree) &&
|
|
parent=$(git rev-parse --verify HEAD) &&
|
|
commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
|
|
git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
|
|
stop_here $this
|
|
|
|
if test -x "$GIT_DIR"/hooks/post-applypatch
|
|
then
|
|
"$GIT_DIR"/hooks/post-applypatch
|
|
fi
|
|
|
|
go_next
|
|
done
|
|
|
|
git gc --auto
|
|
|
|
rm -fr "$dotest"
|