git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC PATCH 7/7] rebase -i: Teach do_pick the options --amend and --file

From
Fabian Ruch <bafain@gmail.com>
Date
Jun 19, 2014, 03:28 UTC
Message-ID
<53A258F2.8050300@gmail.com>
In-Reply-To
<cover.1403146774.git.bafain@gmail.com>

The to-do list command `squash` and its close relative `fixup` replay the changes of a commit like `pick` but do not recreate the commit. Instead they replace the previous commit with a new commit that also introduces the changes of the squashed commit. This is roughly like cherry-picking without committing and using git-commit to amend the previous commit.

The to-do list
    pick   a Some changes
    squash b Some more changes
gets translated into the sequence of git commands
    git cherry-pick a
    git cherry-pick -n b
    git commit --amend

and if `cherry-pick` supported `--amend` this would look even more like the to-do list it is based on

    git cherry-pick a
    git cherry-pick --amend b.

Since `do_pick` takes care of `pick` entries and the above suggests `squash` as an alias for `pick --amend`, teach `do_pick` to handle the option `--amend` and reimplement `squash` in terms of `do_pick --amend`. Also teach it the option `--file` which is used to specify `$squash_msg` as commit message.

Both `--amend` and `--file` are commit rewriting options. If they are encountered during options parsing, assign `rewrite` and pass `--amend` (`--file` respectively) to the rewrite command. Be careful when `--amend` is used to pick a root commit because HEAD might point to the sentinel commit but there is still nothing to amend. Be sure to initialize `$amend` so that commits are squashed even when `rebase` is interrupted for resolving conflicts. It is not a mistake to do the initialization regardless of any conflicts because `$amend` is always cleared before the next to-do item is processed.

Signed-off-by: Fabian Ruch <bafain@gmail.com>
---
Notes:
    A question about when to enable the post-rewrite hook.
    
    `rebase` collects the hashes of all processed commits using
    `record_in_rewritten` and runs the post-rewrite script after the rebase
    is complete. Two points seem to confuse me.
    
     1) For a `pick` the hash is `record_in_rewritten` regardless of whether
        the hash changed or not (the commit was recreated or the head was
        fast-forwarded). Ok, the hook can figure that out. Is this behaviour
        intended?
    
     2) For a `reword` the amend disables the post-rewrite hook but for a
        `squash` (or `fixup`) the hook is executed each time the squash
        commit is amended. Does not this result in the hook being executed
        twice for each scheduled `squash` command? Once for the amend and
        once for the rebase. The hook most likely does not figure that out.
    
    This patch never executes the post-rewrite hook when processing the
    to-do list. The execution after the rebase is finished is still
    conducted. I am uncertain whether this is correct. The tests seem to
    succeed with both implementations.
 git-rebase--interactive.sh | 65 ++++++++++++++++++++++++++++------------------
 1 file changed, 40 insertions(+), 25 deletions(-)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index ada520d..5ddc59d 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -493,7 +493,7 @@ record_in_rewritten() {
 
 # Apply the changes introduced by the given commit to the current head.
 #
-# do_pick [--edit] <commit>
+# do_pick [--file <file>] [--amend] [--edit] <commit>
 #
 # Wrapper around git-cherry-pick.
 #
@@ -505,6 +505,17 @@ record_in_rewritten() {
 #     commit message. The editor contents becomes the commit message of
 #     the new head.
 #
+# --amend
+#     After picking <commit>, replace the current head commit with a new
+#     commit that also introduces the changes of <commit>.
+#
+#     _This is not a git-cherry-pick option._
+#
+# -F <file>, --file <file>
+#     Take the commit message from the given file.
+#
+#     _This is not a git-cherry-pick option._
+#
 # The return value is 1 if applying the changes resulted in a conflict
 # and 2 if the specified arguments were incorrect. If the changes could
 # be applied successfully but creating the commit failed, a value
@@ -514,9 +525,30 @@ do_pick () {
 	rewrite=
 	rewrite_amend=
 	rewrite_edit=
+	rewrite_message=
 	while test $# -gt 0
 	do
 		case "$1" in
+		-F|--file)
+			if test $# -eq 0
+			then
+				warn "do_pick: option --file specified but no <file> given"
+				return 2
+			fi
+			rewrite=y
+			rewrite_message=$2
+			shift
+			;;
+		--amend)
+			if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse --verify HEAD
+			then
+				warn "do_pick: nothing to amend"
+				return 2
+			fi
+			rewrite=y
+			rewrite_amend=y
+			git rev-parse --verify HEAD >"$amend"
+			;;
 		-e|--edit)
 			rewrite=y
 			rewrite_edit=y
@@ -561,6 +593,7 @@ do_pick () {
 		git commit --allow-empty --no-post-rewrite -n -q \
 			   ${rewrite_amend:+--amend} \
 			   ${rewrite_edit:+--edit} \
+			   ${rewrite_message:+--file "$rewrite_message"} \
 			   ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3
 	fi
 }
@@ -617,38 +650,20 @@ do_next () {
 		squash|s|fixup|f)
 			# This is an intermediate commit; its message will only be
 			# used in case of trouble.  So use the long version:
-			if ! pick_one -n $sha1
-			then
-				git rev-parse --verify HEAD >"$amend"
-				die_failed_squash $sha1 "Could not apply $sha1... $rest"
-			fi
-			do_with_author output git commit --amend --no-verify -F "$squash_msg" \
-				${gpg_sign_opt:+"$gpg_sign_opt"} ||
-				die_failed_squash $sha1 "$rest"
+			do_with_author do_pick --amend -F "$squash_msg" $sha1 \
+				|| die_failed_squash $sha1 "Could not apply $sha1... $rest"
 			;;
 		*)
 			# This is the final command of this squash/fixup group
 			if test -f "$fixup_msg"
 			then
-				if ! pick_one -n $sha1
-				then
-					git rev-parse --verify HEAD >"$amend"
-					die_failed_squash $sha1 "Could not apply $sha1... $rest"
-				fi
-				do_with_author git commit --amend --no-verify -F "$fixup_msg" \
-					${gpg_sign_opt:+"$gpg_sign_opt"} ||
-					die_failed_squash $sha1 "$rest"
+				do_with_author do_pick --amend -F "$fixup_msg" $sha1 \
+					|| die_failed_squash $sha1 "Could not apply $sha1... $rest"
 			else
 				cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
 				rm -f "$GIT_DIR"/MERGE_MSG
-				if ! pick_one -n $sha1
-				then
-					git rev-parse --verify HEAD >"$amend"
-					die_failed_squash $sha1 "Could not apply $sha1... $rest"
-				fi
-				do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
-					${gpg_sign_opt:+"$gpg_sign_opt"} ||
-					die_failed_squash $sha1 "$rest"
+				do_with_author do_pick --amend -F "$GIT_DIR"/SQUASH_MSG -e $sha1 \
+					|| die_failed_squash $sha1 "Could not apply $sha1... $rest"
 			fi
 			rm -f "$squash_msg" "$fixup_msg"
 			;;
-- 
2.0.0
Previous: Fabian Ruch
Message 16 of 16 in “rebase -i: Make option handling in pick_one more flexible”
  1. 1/7 rebase -i: Make option handling in pick_one more flexibleFabian Ruch, Jun 19, 2014
  2. Michael HaggertyJun 20, 2014
  3. Junio C HamanoJun 20, 2014
  4. Fabian RuchJun 23, 2014
  5. Fabian RuchJun 21, 2014
  6. Johannes SchindelinJun 23, 2014
  7. 2/7 rebase -i: Teach do_pick the option --editFabian Ruch, Jun 19, 2014
  8. Michael HaggertyJun 20, 2014
  9. Fabian RuchJun 22, 2014
  10. 3/7 rebase -i: Stop on root commits with empty log messagesFabian Ruch, Jun 19, 2014
  11. Eric SunshineJun 21, 2014
  12. Fabian RuchJun 22, 2014
  13. 4/7 rebase -i: Commit only once when rewriting picksFabian Ruch, Jun 19, 2014
  14. 5/7 rebase -i: Do not die in do_pickFabian Ruch, Jun 19, 2014
  15. 6/7 rebase -i: Prepare for squash in terms of do_pick --amendFabian Ruch, Jun 19, 2014
  16. 7/7 rebase -i: Teach do_pick the options --amend and --fileFabian Ruch, Jun 19, 2014

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.