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

[PATCH v3 02/27] rebase -i: allow squashing empty commits without complaints

From
Fabian Ruch <bafain@gmail.com>
Date
Aug 18, 2014, 21:22 UTC
Message-ID
<c6c1060f8f646b9e2dd0966370dde9ed70361194.1408396036.git.bafain@gmail.com>
In-Reply-To
<cover.1408396036.git.bafain@gmail.com>

The to-do list commands `squash` and `fixup` apply the changes introduced by the named commit to the tree but instead of creating a new commit on top of the current head it replaces the previous commit with a new commit that records the updated tree. If the result is an empty commit git-rebase stops with the error message

   You asked to amend the most recent commit, but doing so would make
   it empty. You can repeat your command with --allow-empty, or you can
   remove the commit entirely with "git reset HEAD^".

This message is not very helpful because neither does git-rebase support an option `--allow-empty` nor does the messages say how to resume the rebase. Firstly, change the error message to

   The squash result is empty and --keep-empty was not specified.
   You can remove the squash commit now with
     git reset HEAD^
   Once you are done, run
     git rebase --continue

If the user wishes to squash a sequence of commits into one commit, f. i.

   pick A
   squash Revert "A"
   squash A'

, it does not matter for the end result that the first squash result, or any sub-sequence in general, is going to be empty. The squash message is not affected at all by which commits are created and only the commit created by the last line in the sequence will end up in the final history. Secondly, print the error message only if the whole squash sequence produced an empty commit.

Lastly, since an empty squash commit is not a failure to rewrite the history as planned, issue the message above as a mere warning and interrupt the rebase with the return value zero. The interruption should be considered as a notification with the chance to undo it on the spot. Specifying the `--keep-empty` option tells git-rebase to keep empty squash commits in the rebased history without notification.

Add tests.
Reported-by: Peter Krefting <peter@softwolves.pp.se>
Signed-off-by: Fabian Ruch <bafain@gmail.com>
---
 git-rebase--interactive.sh    | 20 +++++++++++---
 t/t3404-rebase-interactive.sh | 62 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 3 deletions(-)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3222bf6..ada340c 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -549,7 +549,7 @@ 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:
-			do_with_author output git commit --allow-empty-message \
+			do_with_author output git commit --allow-empty-message --allow-empty \
 				--amend --no-verify -F "$squash_msg" \
 				${gpg_sign_opt:+"$gpg_sign_opt"} ||
 				die_failed_squash $sha1 "$rest"
@@ -558,18 +558,32 @@ do_next () {
 			# This is the final command of this squash/fixup group
 			if test -f "$fixup_msg"
 			then
-				do_with_author git commit --allow-empty-message \
+				do_with_author git commit --allow-empty-message --allow-empty \
 					--amend --no-verify -F "$fixup_msg" \
 					${gpg_sign_opt:+"$gpg_sign_opt"} ||
 					die_failed_squash $sha1 "$rest"
 			else
 				cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
 				rm -f "$GIT_DIR"/MERGE_MSG
-				do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
+				do_with_author git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
 					${gpg_sign_opt:+"$gpg_sign_opt"} ||
 					die_failed_squash $sha1 "$rest"
 			fi
 			rm -f "$squash_msg" "$fixup_msg"
+			if test -z "$keep_empty" && is_empty_commit HEAD
+			then
+				echo "$sha1" >"$state_dir"/stopped-sha
+				warn "The squash result is empty and --keep-empty was not specified."
+				warn
+				warn "You can remove the squash commit now with"
+				warn
+				warn "  git reset HEAD^"
+				warn
+				warn "Once you are done, run"
+				warn
+				warn "  git rebase --continue"
+				exit 0
+			fi
 			;;
 		esac
 		record_in_rewritten $sha1
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 9c71835..a95cb2a 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -237,6 +237,68 @@ test_expect_success 'retain authorship' '
 	git show HEAD | grep "^Author: Twerp Snog"
 '
 
+test_expect_success 'setup squash/fixup reverted and fixed feature' '
+	git checkout -b reverted-feature master &&
+	test_commit feature &&
+	git revert feature &&
+	git checkout -b fixed-feature reverted-feature &&
+	test_commit featurev2
+'
+
+test_expect_success 'fixup fixed feature (empty interim commit)' '
+	git checkout -b fixup-fixed-feature fixed-feature &&
+	set_fake_editor &&
+	FAKE_LINES="1 fixup 2 fixup 3" git rebase -i master &&
+	git log --oneline master.. >actual &&
+	test_line_count = 1 actual &&
+	git diff --exit-code featurev2
+'
+
+test_expect_success 'squash fixed feature (empty interim commit)' '
+	git checkout -b squash-fixed-feature fixed-feature &&
+	set_fake_editor &&
+	FAKE_LINES="1 squash 2 squash 3" git rebase -i master &&
+	git log --oneline master.. >actual &&
+	test_line_count = 1 actual &&
+	git diff --exit-code featurev2
+'
+
+test_expect_success 'fixup reverted feature (empty final commit)' '
+	git checkout -b fixup-reverted-feature reverted-feature &&
+	set_fake_editor &&
+	FAKE_LINES="1 fixup 2" git rebase -i master &&
+	git reset HEAD^ &&
+	git rebase --continue &&
+	test_cmp_rev master HEAD
+'
+
+test_expect_success 'squash reverted feature (empty final commit)' '
+	git checkout -b squash-reverted-feature reverted-feature &&
+	set_fake_editor &&
+	FAKE_LINES="1 squash 2" git rebase -i master &&
+	git reset HEAD^ &&
+	git rebase --continue &&
+	test_cmp_rev master HEAD
+'
+
+test_expect_success 'fixup reverted feature (empty final commit with --keep-empty)' '
+	git checkout -b fixup-keep-reverted-feature reverted-feature &&
+	set_fake_editor &&
+	FAKE_LINES="1 fixup 2" git rebase -i --keep-empty master &&
+	git log --oneline master.. >actual &&
+	test_line_count = 1 actual &&
+	git diff --exit-code master
+'
+
+test_expect_success 'squash reverted feature (empty final commit with --keep-empty)' '
+	git checkout -b squash-keep-reverted-feature reverted-feature &&
+	set_fake_editor &&
+	FAKE_LINES="1 squash 2" git rebase -i --keep-empty master &&
+	git log --oneline master.. >actual &&
+	test_line_count = 1 actual &&
+	git diff --exit-code master
+'
+
 test_expect_success 'squash' '
 	git reset --hard twerp &&
 	echo B > file7 &&
-- 
2.0.1
Previous: Fabian RuchNext: Fabian Ruch
Message 123 of 148 in “rebase -i: Implement `reword` and `squash` in terms of `do_pick`”
  1. 0/7 rebase -i: Implement `reword` and `squash` in terms of `do_pick`Fabian Ruch, Jun 19, 2014
  2. 00/19 Enable options --signoff, --reset-author for pick, rewordFabian Ruch, Jul 2, 2014
  3. 01/19 rebase -i: Failed reword prints redundant error messageFabian Ruch, Jul 2, 2014
  4. Junio C HamanoJul 8, 2014
  5. Andrew WongJul 10, 2014
  6. Fabian RuchJul 10, 2014
  7. Andrew WongJul 10, 2014
  8. 02/19 rebase -i: reword complains about empty commit despite --keep-emptyFabian Ruch, Jul 2, 2014
  9. Junio C HamanoJul 8, 2014
  10. Fabian RuchJul 9, 2014
  11. 03/19 rebase -i: reword executes pre-commit hook on interim commitFabian Ruch, Jul 2, 2014
  12. Junio C HamanoJul 8, 2014
  13. Fabian RuchJul 13, 2014
  14. 04/19 rebase -i: Teach do_pick the option --editFabian Ruch, Jul 2, 2014
  15. 05/19 rebase -i: Implement reword in terms of do_pickFabian Ruch, Jul 2, 2014
  16. Matthieu MoyAug 4, 2014
  17. Fabian RuchAug 4, 2014
  18. 06/19 rebase -i: Stop on root commits with empty log messagesFabian Ruch, Jul 2, 2014
  19. Junio C HamanoJul 8, 2014
  20. Fabian RuchJul 10, 2014
  21. Junio C HamanoJul 10, 2014
  22. Junio C HamanoJul 10, 2014
  23. 07/19 rebase -i: The replay of root commits is not shown with --verboseFabian Ruch, Jul 2, 2014
  24. Junio C HamanoJul 8, 2014
  25. Fabian RuchJul 11, 2014
  26. Chris WebbJul 15, 2014
  27. 08/19 rebase -i: Root commits are replayed with an unnecessary optionFabian Ruch, Jul 2, 2014
  28. Junio C HamanoJul 8, 2014
  29. Fabian RuchJul 18, 2014
  30. Junio C HamanoJul 18, 2014
  31. Fabian RuchJul 19, 2014
  32. 09/19 rebase -i: Commit only once when rewriting picksFabian Ruch, Jul 2, 2014
  33. 10/19 rebase -i: Do not die in do_pickFabian Ruch, Jul 2, 2014
  34. 11/19 rebase -i: Teach do_pick the option --amendFabian Ruch, Jul 2, 2014
  35. 12/19 rebase -i: Teach do_pick the option --fileFabian Ruch, Jul 2, 2014
  36. 13/19 rebase -i: Prepare for squash in terms of do_pick --amendFabian Ruch, Jul 2, 2014
  37. 14/19 rebase -i: Implement squash in terms of do_pickFabian Ruch, Jul 2, 2014
  38. 15/19 rebase -i: Explicitly distinguish replay commands and exec tasksFabian Ruch, Jul 2, 2014
  39. Junio C HamanoJul 10, 2014
  40. 16/19 rebase -i: Parse to-do list command line optionsFabian Ruch, Jul 2, 2014
  41. 17/19 rebase -i: Teach do_pick the option --reset-authorFabian Ruch, Jul 2, 2014
  42. 18/19 rebase -i: Teach do_pick the option --signoffFabian Ruch, Jul 2, 2014
  43. 19/19 rebase -i: Enable options --signoff, --reset-author for pick, rewordFabian Ruch, Jul 2, 2014
  44. Michael HaggertyJul 3, 2014
  45. Junio C HamanoJul 8, 2014
  46. Fabian RuchJul 9, 2014
  47. Thomas RastJul 18, 2014
  48. 00/19 Enable options --signoff, --reset-author for pick, rewordFabian Ruch, Jul 28, 2014
  49. 01/19 rebase -i: failed reword prints redundant error messageFabian Ruch, Jul 28, 2014
  50. 02/19 rebase -i: allow rewording an empty commit without complaintsFabian Ruch, Jul 28, 2014
  51. 03/19 rebase -i: reword executes pre-commit hook on interim commitFabian Ruch, Jul 28, 2014
  52. Jeff KingAug 1, 2014
  53. Fabian RuchAug 4, 2014
  54. Jeff KingAug 6, 2014
  55. 04/19 rebase -i: teach do_pick the option --editFabian Ruch, Jul 28, 2014
  56. 05/19 rebase -i: implement reword in terms of do_pickFabian Ruch, Jul 28, 2014
  57. 06/19 rebase -i: allow replaying commits with empty log messagesFabian Ruch, Jul 28, 2014
  58. 07/19 rebase -i: log the replay of root commitsFabian Ruch, Jul 28, 2014
  59. Jeff KingAug 2, 2014
  60. Fabian RuchAug 4, 2014
  61. Jeff KingAug 6, 2014
  62. 08/19 rebase -i: root commits are replayed with an unnecessary optionFabian Ruch, Jul 28, 2014
  63. Jeff KingAug 2, 2014
  64. Fabian RuchAug 4, 2014
  65. 09/19 rebase -i: commit only once when rewriting picksFabian Ruch, Jul 28, 2014
  66. Jeff KingAug 2, 2014
  67. Fabian RuchAug 7, 2014
  68. 10/19 rebase -i: do not die in do_pickFabian Ruch, Jul 28, 2014
  69. 11/19 rebase -i: teach do_pick the option --amendFabian Ruch, Jul 28, 2014
  70. 12/19 rebase -i: teach do_pick the option --fileFabian Ruch, Jul 28, 2014
  71. 13/19 rebase -i: prepare for squash in terms of do_pick --amendFabian Ruch, Jul 28, 2014
  72. 14/19 rebase -i: implement squash in terms of do_pickFabian Ruch, Jul 28, 2014
  73. 15/19 rebase -i: explicitly distinguish replay commands and exec tasksFabian Ruch, Jul 28, 2014
  74. 16/19 rebase -i: parse to-do list command line optionsFabian Ruch, Jul 28, 2014
  75. 17/19 rebase -i: teach do_pick the option --reset-authorFabian Ruch, Jul 28, 2014
  76. 18/19 rebase -i: teach do_pick the option --signoffFabian Ruch, Jul 28, 2014
  77. 19/19 rebase -i: enable options --signoff, --reset-author for pick, rewordFabian Ruch, Jul 28, 2014
  78. Jeff KingAug 2, 2014
  79. Fabian RuchAug 4, 2014
  80. 00/23 Enable options --signoff, --reset-author for pick, rewordFabian Ruch, Aug 6, 2014
  81. 01/23 rebase -i: allow replaying commits with empty log messagesFabian Ruch, Aug 6, 2014
  82. 02/23 rebase -i: allow squashing empty commits without complaintsFabian Ruch, Aug 6, 2014
  83. Peter KreftingAug 7, 2014
  84. Eric SunshineAug 7, 2014
  85. Fabian RuchAug 11, 2014
  86. Phil HordAug 13, 2014
  87. 03/23 rebase -i: allow rewording empty commits without complaintsFabian Ruch, Aug 6, 2014
  88. 04/23 rebase -i: hide interactive command messages in verbose modeFabian Ruch, Aug 6, 2014
  89. Thomas RastAug 8, 2014
  90. Fabian RuchAug 11, 2014
  91. Thomas RastAug 11, 2014
  92. 05/23 rebase -i: failed reword prints redundant error messageFabian Ruch, Aug 6, 2014
  93. 06/23 commit: allow disabling pre-commit and commit-msg separatelyFabian Ruch, Aug 6, 2014
  94. 07/23 rebase -i: squash skips commit-msg hookFabian Ruch, Aug 6, 2014
  95. 08/23 rebase -i: reword executes pre-commit hook on interim commitFabian Ruch, Aug 6, 2014
  96. Thomas RastAug 8, 2014
  97. Fabian RuchAug 11, 2014
  98. Thomas RastAug 11, 2014
  99. 09/23 rebase -i: teach do_pick the option --editFabian Ruch, Aug 6, 2014
  100. 10/23 rebase -i: implement reword in terms of do_pickFabian Ruch, Aug 6, 2014
  101. 11/23 rebase -i: log the replay of root commitsFabian Ruch, Aug 6, 2014
  102. 12/23 rebase -i: root commits are replayed with an unnecessary optionFabian Ruch, Aug 6, 2014
  103. 13/23 rebase -i: commit only once when rewriting picksFabian Ruch, Aug 6, 2014
  104. 14/23 rebase -i: do not die in do_pickFabian Ruch, Aug 6, 2014
  105. 15/23 rebase -i: teach do_pick the option --amendFabian Ruch, Aug 6, 2014
  106. 16/23 rebase -i: teach do_pick the option --fileFabian Ruch, Aug 6, 2014
  107. 17/23 rebase -i: prepare for squash in terms of do_pick --amendFabian Ruch, Aug 6, 2014
  108. 18/23 rebase -i: implement squash in terms of do_pickFabian Ruch, Aug 6, 2014
  109. 19/23 rebase -i: explicitly distinguish replay commands and exec tasksFabian Ruch, Aug 6, 2014
  110. 20/23 rebase -i: parse to-do list command line optionsFabian Ruch, Aug 6, 2014
  111. Thomas RastAug 8, 2014
  112. Fabian RuchAug 11, 2014
  113. 21/23 rebase -i: teach do_pick the option --reset-authorFabian Ruch, Aug 6, 2014
  114. 22/23 rebase -i: teach do_pick the option --signoffFabian Ruch, Aug 6, 2014
  115. 23/23 rebase -i: enable options --signoff, --reset-author for pick, rewordFabian Ruch, Aug 6, 2014
  116. Thomas RastAug 8, 2014
  117. Fabian RuchAug 12, 2014
  118. Michael HaggertyAug 13, 2014
  119. Fabian RuchAug 14, 2014
  120. Fabian RuchSep 21, 2014
  121. 00/27 Enable options --signoff, --reset-author for pick, reword, editFabian Ruch, Aug 18, 2014
  122. 01/27 rebase -i: allow replaying commits with empty log messagesFabian Ruch, Aug 18, 2014
  123. 02/27 rebase -i: allow squashing empty commits without complaintsFabian Ruch, Aug 18, 2014
  124. 03/27 rebase -i: allow rewording empty commits without complaintsFabian Ruch, Aug 18, 2014
  125. 04/27 fake_editor: leave standard output unchangedFabian Ruch, Aug 18, 2014
  126. 05/27 rebase -i: hide interactive command messages in verbose modeFabian Ruch, Aug 18, 2014
  127. 06/27 rebase -i: discard redundant message when rewording failsFabian Ruch, Aug 18, 2014
  128. 07/27 commit: allow disabling pre-commit and commit-msg separatelyFabian Ruch, Aug 18, 2014
  129. 08/27 rebase -i: verify squash messages using commit-msgFabian Ruch, Aug 18, 2014
  130. 09/27 rebase -i: do not verify reworded patches using pre-commitFabian Ruch, Aug 18, 2014
  131. 10/27 rebase -i: teach do_pick the option --editFabian Ruch, Aug 18, 2014
  132. 11/27 rebase -i: implement reword in terms of do_pickFabian Ruch, Aug 18, 2014
  133. 12/27 rebase -i: log the replay of root commitsFabian Ruch, Aug 18, 2014
  134. 13/27 rebase -i: do not use -C when --no-edit is sufficientFabian Ruch, Aug 18, 2014
  135. 14/27 rebase -i: commit only once when rewriting picksFabian Ruch, Aug 18, 2014
  136. 15/27 rebase -i: do not die in do_pickFabian Ruch, Aug 18, 2014
  137. 16/27 rebase -i: teach do_pick the option --amendFabian Ruch, Aug 18, 2014
  138. 17/27 rebase -i: teach do_pick the option --fileFabian Ruch, Aug 18, 2014
  139. 18/27 rebase -i: remove no-op do_with_author git commit --amendFabian Ruch, Aug 18, 2014
  140. 19/27 rebase -i: prepare for squash in terms of do_pick --amendFabian Ruch, Aug 18, 2014
  141. 20/27 rebase -i: implement squash in terms of do_pickFabian Ruch, Aug 18, 2014
  142. 21/27 rebase -i: explicitly distinguish replay commands and exec tasksFabian Ruch, Aug 18, 2014
  143. 22/27 rebase -i: parse to-do list command line optionsFabian Ruch, Aug 18, 2014
  144. 23/27 rebase -i: teach do_pick the option --reset-authorFabian Ruch, Aug 18, 2014
  145. 24/27 rebase -i: teach do_pick the option --signoffFabian Ruch, Aug 18, 2014
  146. 25/27 rebase -i: do not overwrite user author informationFabian Ruch, Aug 18, 2014
  147. 26/27 rebase -i: refuse to commit when resuming with updated headFabian Ruch, Aug 18, 2014
  148. 27/27 rebase -i: enable --signoff, --reset-author for pick, reword, editFabian Ruch, Aug 18, 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.