# updated. It is deleted just before the combined commit is made.
SQUASH_MSG="$DOTEST"/message-squash
+# If the current series of squash/fixups has not yet included a squash
+# command, then this file exists and holds the commit message of the
+# original "pick" commit. (If the series ends without a "squash"
+# command, then this can be used as the commit message of the combined
+# commit without opening the editor.)
+FIXUP_MSG="$DOTEST"/message-fixup
+
# $REWRITTEN is the name of a directory containing files for each
# commit that is reachable by at least one merge base of $HEAD and
# $UPSTREAM. They are not necessarily rewritten, but their children
esac
}
+# Output the commit message for the specified commit.
+commit_message () {
+ git cat-file commit "$1" | sed "1,/^$/d"
+}
+
run_pre_rebase_hook () {
if test -z "$OK_TO_SKIP_PRE_REBASE" &&
test -x "$GIT_DIR/hooks/pre-rebase"
;;
esac > "$DOTEST"/patch
test -f "$MSG" ||
- git cat-file commit "$1" | sed "1,/^$/d" > "$MSG"
+ commit_message "$1" > "$MSG"
test -f "$AUTHOR_SCRIPT" ||
get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT"
}
sane_grep '^[^#]' "$1" >/dev/null
}
+# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
+# GIT_AUTHOR_DATE exported from the current environment.
+do_with_author () {
+ GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
+ GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
+ GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
+ "$@"
+}
+
pick_one () {
no_ff=
case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
# redo merge
author_script=$(get_author_ident_from_commit $sha1)
eval "$author_script"
- msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
+ msg="$(commit_message $sha1)"
# No point in merging the first parent, that's HEAD
new_parents=${new_parents# $first_parent}
- if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
- GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
- GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
- output git merge $STRATEGY -m "$msg" \
- $new_parents
+ if ! do_with_author output \
+ git merge $STRATEGY -m "$msg" $new_parents
then
printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
die_with_patch $sha1 "Error redoing merge $sha1"
esac
}
-make_squash_message () {
+update_squash_messages () {
if test -f "$SQUASH_MSG"; then
+ mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit
COUNT=$(($(sed -n \
-e "1s/^# This is a combination of \(.*\) commits\./\1/p" \
- -e "q" < "$SQUASH_MSG")+1))
- echo "# This is a combination of $COUNT commits."
- sed -e 1d -e '2,/^./{
- /^$/d
- }' <"$SQUASH_MSG"
+ -e "q" < "$SQUASH_MSG".bak)+1))
+ {
+ echo "# This is a combination of $COUNT commits."
+ sed -e 1d -e '2,/^./{
+ /^$/d
+ }' <"$SQUASH_MSG".bak
+ } >$SQUASH_MSG
else
+ commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG"
COUNT=2
- echo "# This is a combination of 2 commits."
- echo "# The first commit's message is:"
- echo
- git cat-file commit HEAD | sed -e '1,/^$/d'
+ {
+ echo "# This is a combination of 2 commits."
+ echo "# The first commit's message is:"
+ echo
+ cat "$FIXUP_MSG"
+ } >$SQUASH_MSG
fi
case $1 in
squash)
+ rm -f "$FIXUP_MSG"
echo
echo "# This is the $(nth_string $COUNT) commit message:"
echo
- git cat-file commit $2 | sed -e '1,/^$/d'
+ commit_message $2
;;
fixup)
echo
echo "# The $(nth_string $COUNT) commit message will be skipped:"
echo
- git cat-file commit $2 | sed -e '1,/^$/d' -e 's/^/# /'
+ commit_message $2 | sed -e 's/^/# /'
;;
- esac
+ esac >>$SQUASH_MSG
}
peek_next_command () {
die "Cannot '$squash_style' without a previous commit"
mark_action_done
- make_squash_message $squash_style $sha1 > "$MSG"
+ update_squash_messages $squash_style $sha1
failed=f
author_script=$(get_author_ident_from_commit HEAD)
+ echo "$author_script" > "$AUTHOR_SCRIPT"
+ eval "$author_script"
output git reset --soft HEAD^
pick_one -n $sha1 || failed=t
case "$(peek_next_command)" in
squash|s|fixup|f)
- USE_OUTPUT=output
- MSG_OPT=-F
- EDIT_OR_FILE="$MSG"
- cp "$MSG" "$SQUASH_MSG"
+ # This is an intermediate commit; its message will only be
+ # used in case of trouble. So use the long version:
+ if test $failed = f
+ then
+ do_with_author output git commit --no-verify -F "$SQUASH_MSG" ||
+ failed=t
+ fi
+ if test $failed = t
+ then
+ cp "$SQUASH_MSG" "$MSG" || exit
+ # After any kind of hiccup, prevent committing without
+ # opening the commit message editor:
+ rm -f "$FIXUP_MSG"
+ cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit
+ warn
+ warn "Could not apply $sha1... $rest"
+ die_with_patch $sha1 ""
+ fi
;;
*)
- USE_OUTPUT=
- MSG_OPT=
- EDIT_OR_FILE=-e
- rm -f "$SQUASH_MSG" || exit
- cp "$MSG" "$GIT_DIR"/SQUASH_MSG
- rm -f "$GIT_DIR"/MERGE_MSG || exit
+ # This is the final command of this squash/fixup group
+ if test $failed = f
+ then
+ if test -f "$FIXUP_MSG"
+ then
+ do_with_author git commit --no-verify -F "$FIXUP_MSG" ||
+ failed=t
+ else
+ cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit
+ rm -f "$GIT_DIR"/MERGE_MSG
+ do_with_author git commit --no-verify -e ||
+ failed=t
+ fi
+ fi
+ rm -f "$FIXUP_MSG"
+ if test $failed = t
+ then
+ mv "$SQUASH_MSG" "$MSG" || exit
+ cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit
+ warn
+ warn "Could not apply $sha1... $rest"
+ die_with_patch $sha1 ""
+ fi
+ rm -f "$SQUASH_MSG"
;;
esac
- echo "$author_script" > "$AUTHOR_SCRIPT"
- if test $failed = f
- then
- # This is like --amend, but with a different message
- eval "$author_script"
- GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
- GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
- GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
- $USE_OUTPUT git commit --no-verify \
- $MSG_OPT "$EDIT_OR_FILE" || failed=t
- fi
- if test $failed = t
- then
- cp "$MSG" "$GIT_DIR"/MERGE_MSG
- warn
- warn "Could not apply $sha1... $rest"
- die_with_patch $sha1 ""
- fi
;;
*)
warn "Unknown command: $command $sha1 $rest"
git reset --soft HEAD^ ||
die "Cannot rewind the HEAD"
fi
- export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
- git commit --no-verify -F "$MSG" -e || {
+ do_with_author git commit --no-verify -F "$MSG" -e || {
test -n "$amend" && git reset --soft $amend
die "Could not commit staged changes."
}