{"thread":{"id":"65978","subject":"[PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","startedAt":"2026-07-12T00:39:04Z","lastAt":"2026-08-27T18:24:13Z","messageCount":10,"participants":["Farid Zakaria","Junio C Hamano","Phillip Wood"],"isPatch":true,"patchVersion":3,"patchTotal":null},"messages":[{"id":"547871","messageId":"20260711-fz-autosquash-empty-v3-1-d227b63eb511@gmail.com","threadId":"65978","inReplyTo":null,"subject":"[PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Farid Zakaria","fromEmail":"farid.m.zakaria@gmail.com","sentAt":"2026-07-12T00:38:26Z","receivedAt":"2026-07-12T00:39:04Z","isPatch":true,"body":"When \"git rebase --autosquash\" melds a \"fixup!\" or \"squash!\" commit into\nits target, the result can be a commit that no longer changes anything\nrelative to its parent, for example when the melded change reverts the\ntarget.  Rather than dropping or keeping this empty commit, the rebase\nstops with\n\n\tYou asked to amend the most recent commit, but doing so would\n\tmake it empty. ...\n\nand the \"--empty\" option has no effect on it.  This makes backing a\nchange out of a series awkward: reverting a commit as a \"fixup!\" and\nrunning \"git rebase --autosquash --empty=drop\" ought to remove both the\ncommit and its revert, but it halts instead.\n\nA \"fixup!\" is applied by amending HEAD, so the melded commit has HEAD's\nparent as its parent and is empty when the index matches the tree of that\nparent, not of HEAD.  do_pick_commit() only compares against HEAD, so it\nnever notices that the meld cancelled the commit out and falls through to\n\"git commit --amend\", which refuses to create an empty commit.\n\nAfter melding a fixup or squash, check whether the amended commit is\nempty -- its index matches the tree of HEAD's parent -- and, if so, honor\n\"--empty\" just as for a commit that becomes empty when picked: keep it,\ndrop it, or halt.\n\nWhen \"--empty=drop\" applies, the emptied commit has already been created\nby the preceding \"pick\", so drop it by moving HEAD back to its parent.\nThe commit is dropped rather than rewritten, so discard the pending\nrewrite records and do not record the fixup either, leaving nothing for\nthe post-rewrite machinery; a following \"label\" or \"update-ref\" then sees\nHEAD at the parent.\n\nSigned-off-by: Farid Zakaria <farid.m.zakaria@gmail.com>\n---\nAt Meta we maintain a fork of LLVM that we regularly rebase onto\nupstream.  A set of internal patches rides on top, and we keep each one\nas a single commit by folding follow-up changes into it with autosquash\n\"fixup!\" commits.  That works well for evolving a patch, but not for\nretiring one: to back an internal patch out today we hand-edit the\ninteractive rebase todo list to delete the commit and its scattered\nfixups, which is fiddly and easy to get wrong.  (The history is rewritten\neither way, so a force-push is still needed; what this avoids is the\nmanual todo surgery.)\n\nIt would be nicer to retire a patch the same way we amend one: commit a\nrevert of it as a \"fixup!\" and let autosquash fold the two together.\nThe net change is empty, so the commit should just drop out of the\nseries.  Today it does not -- the rebase stops instead.\n\nFor example, starting from a commit we want to retire:\n\n    $ git log --oneline\n    4d5e6f7 add feature patch\n    9a1b2c3 base\n\n    # revert the feature and mark the revert as a fixup of it\n    $ git revert --no-edit HEAD\n    $ git commit --amend -m \"fixup! add feature patch\"\n\n    $ git rebase -i --autosquash --empty=drop 9a1b2c3\n    Rebasing (2/2)\n    You asked to amend the most recent commit, but doing so would\n    make it empty. You can repeat your command with --allow-empty [...]\n    Could not apply 8e9f0a1... # fixup! add feature patch\n\nThe \"--empty=drop\" is ignored.  \"--empty\" only governs commits that are\npicked empty, whereas a \"fixup!\" is applied by amending, and the\nemptiness of an amended commit is measured against the wrong parent.  So\nthe rebase falls through to \"git commit --amend\", which refuses to\ncreate an empty commit, and halts.\n\nWith this patch the emptied commit is recognized and handled according\nto \"--empty\", the same as any other commit that becomes empty during a\nrebase:\n\n    $ git rebase -i --autosquash --empty=drop 9a1b2c3\n    Rebasing (2/2)\n    dropping 8e9f0a1... fixup! add feature patch -- resulting commit is empty\n    Successfully rebased and updated refs/heads/main.\n\n    $ git log --oneline\n    9a1b2c3 base\n\n\"--empty=keep\" retains it as an empty commit, and \"--empty=stop\" (the\ndefault under \"-i\") halts so the user can decide -- matching how these\noptions already behave for commits that become empty when picked.\n\nChanges in v3:\n * Switch the new tests' assertions from grep to test_grep for better\n   diagnostics (per review).\n * Link to v2: https://lore.kernel.org/r/20260710-fz-autosquash-empty-v2-1-fa1e277e05f8@gmail.com\n\nChanges in v2 (thanks to Phillip Wood's review):\n * An emptied fixup/squash now honors --empty in all cases, including\n   when the commit it was folded into started out empty; v1 kept that\n   case regardless of --empty.\n * On drop, the dropped commit and its fixup are no longer recorded as\n   rewritten, so nothing spurious reaches the post-rewrite machinery.\n * Added tests for the empty-placeholder + fixup cases and for the\n   not-recorded-as-rewritten behavior; adjusted t3415 \"abort last squash\".\n * Link to v1: https://lore.kernel.org/r/20260709-fz-autosquash-empty-v1-1-84cb494c3613@gmail.com\n---\nbase-commit: f60db8d575adb79761d363e026fb49bddf330c73\n---\n Documentation/git-rebase.adoc |  12 ++++\n sequencer.c                   | 148 +++++++++++++++++++++++++++++++++++-------\n t/t3415-rebase-autosquash.sh  | 140 ++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 276 insertions(+), 24 deletions(-)\n\ndiff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc\nindex f6c22d1598..7eb8bbe95f 100644\n--- a/Documentation/git-rebase.adoc\n+++ b/Documentation/git-rebase.adoc\n@@ -282,6 +282,11 @@ by `git log --cherry-mark ...`) are detected and dropped as a\n preliminary step (unless `--reapply-cherry-picks` or `--keep-base` is\n passed).\n +\n+A commit can also become empty as a result of `--autosquash`, when a\n+`fixup!` or `squash!` commit cancels out all of the changes of the\n+commit it is melded into.  Such a commit is treated the same way and is\n+dropped, kept, or stopped at according to this option.\n++\n See also INCOMPATIBLE OPTIONS below.\n \n --no-keep-empty::\n@@ -591,6 +596,13 @@ changed from `pick` to `squash`, `fixup` or `fixup -C`, respectively, and they\n are moved right after the commit they modify.  The `--interactive` option can\n be used to review and edit the todo list before proceeding.\n +\n+If melding a `fixup!` or `squash!` commit cancels out all of the changes of\n+the commit it is applied to, the result is an empty commit.  The handling of\n+these empty commits can be configured with the `--empty` option: the emptied\n+commit is dropped, kept, or stopped at.  This makes it possible to back a\n+change out of a series by committing a revert of it as a `fixup!` and letting\n+`--autosquash --empty=drop` remove both.\n++\n The recommended way to create commits with squash markers is by using the\n `--squash`, `--fixup`, `--fixup=amend:` or `--fixup=reword:` options of\n linkgit:git-commit[1], which take the target commit as an argument and\ndiff --git a/sequencer.c b/sequencer.c\nindex 0fe8fed6c3..bc24132c7c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1817,6 +1817,39 @@ static int allow_empty(struct repository *r,\n \t\treturn 0;\n }\n \n+/*\n+ * Melding a \"fixup!\"/\"squash!\" amends HEAD, so the resulting commit is empty\n+ * when the index matches the tree of HEAD's parent (rather than of HEAD, as a\n+ * plain pick would).  Returns 1 if the amended commit would be empty, 0 if not,\n+ * and negative on error.\n+ */\n+static int amended_commit_is_empty(struct repository *r)\n+{\n+\tstruct object_id head_oid, *cache_tree_oid;\n+\tconst struct object_id *parent_tree_oid;\n+\tstruct commit *head_commit;\n+\n+\tif (repo_get_oid(r, \"HEAD\", &head_oid))\n+\t\treturn error(_(\"could not resolve HEAD commit\"));\n+\thead_commit = lookup_commit_reference(r, &head_oid);\n+\tif (!head_commit || repo_parse_commit(r, head_commit))\n+\t\treturn -1;\n+\n+\tif (head_commit->parents) {\n+\t\tstruct commit *parent = head_commit->parents->item;\n+\t\tif (repo_parse_commit(r, parent))\n+\t\t\treturn -1;\n+\t\tparent_tree_oid = get_commit_tree_oid(parent);\n+\t} else {\n+\t\tparent_tree_oid = the_hash_algo->empty_tree;\n+\t}\n+\n+\tif (!(cache_tree_oid = get_cache_tree_oid(r->index)))\n+\t\treturn -1;\n+\n+\treturn oideq(cache_tree_oid, parent_tree_oid);\n+}\n+\n static struct {\n \tchar c;\n \tconst char *str;\n@@ -2260,10 +2293,34 @@ static const char *reflog_message(struct replay_opts *opts,\n \treturn buf.buf;\n }\n \n+/*\n+ * A \"fixup!\"/\"squash!\" that melds into HEAD may empty it out.  In that case,\n+ * with --empty=drop, we want to drop the commit entirely.  Since the commit\n+ * being amended has already been created (by the preceding \"pick\"), and the\n+ * index and worktree already match the tree of its parent, dropping it is a\n+ * matter of moving HEAD back to that parent.\n+ */\n+static int reset_head_to_parent(struct repository *r, struct replay_opts *opts,\n+\t\t\t\tstruct object_id *head)\n+{\n+\tstruct commit *head_commit = lookup_commit_reference(r, head);\n+\n+\tif (!head_commit || repo_parse_commit(r, head_commit))\n+\t\treturn error(_(\"could not parse HEAD commit\"));\n+\tif (!head_commit->parents)\n+\t\treturn error(_(\"cannot drop the root commit\"));\n+\n+\treturn refs_update_ref(get_main_ref_store(r),\n+\t\t\t       reflog_message(opts, \"fixup\",\n+\t\t\t\t\t      \"dropping emptied commit\"),\n+\t\t\t       \"HEAD\", &head_commit->parents->item->object.oid,\n+\t\t\t       head, 0, UPDATE_REFS_MSG_ON_ERR);\n+}\n+\n static int do_pick_commit(struct repository *r,\n \t\t\t  struct todo_item *item,\n \t\t\t  struct replay_opts *opts,\n-\t\t\t  int final_fixup, int *check_todo)\n+\t\t\t  int final_fixup, int *check_todo, int *dropped)\n {\n \tstruct replay_ctx *ctx = opts->ctx;\n \tunsigned int flags = should_edit(opts) ? EDIT_MSG : 0;\n@@ -2277,6 +2334,9 @@ static int do_pick_commit(struct repository *r,\n \tenum todo_command command = item->command;\n \tstruct commit *commit = item->commit;\n \n+\tif (dropped)\n+\t\t*dropped = 0;\n+\n \tif (is_rebase_i(opts))\n \t\treflog_action = reflog_message(\n \t\t\topts, command_to_string(item->command), NULL);\n@@ -2493,23 +2553,67 @@ static int do_pick_commit(struct repository *r,\n \t}\n \n \tdrop_commit = 0;\n-\tallow = allow_empty(r, opts, commit);\n-\tif (allow < 0) {\n-\t\tres = allow;\n-\t\tgoto leave;\n-\t} else if (allow == 1) {\n-\t\tflags |= ALLOW_EMPTY;\n-\t} else if (allow == 2) {\n-\t\tdrop_commit = 1;\n-\t\trefs_delete_ref(get_main_ref_store(r), \"\", \"CHERRY_PICK_HEAD\",\n-\t\t\t\tNULL, REF_NO_DEREF);\n-\t\tunlink(git_path_merge_msg(r));\n-\t\trefs_delete_ref(get_main_ref_store(r), \"\", \"AUTO_MERGE\",\n-\t\t\t\tNULL, REF_NO_DEREF);\n-\t\tfprintf(stderr,\n-\t\t\t_(\"dropping %s %s -- patch contents already upstream\\n\"),\n-\t\t\toid_to_hex(&commit->object.oid), msg.subject);\n-\t} /* else allow == 0 and there's nothing special to do */\n+\tif (flags & AMEND_MSG) {\n+\t\t/*\n+\t\t * A \"fixup!\"/\"squash!\" amends HEAD.  Separately from the usual\n+\t\t * empty-commit handling, check whether applying it leaves the\n+\t\t * commit empty and, if so, honor --empty (keep, drop, or -- when\n+\t\t * neither is requested -- halt below in do_commit), just as for a\n+\t\t * commit that becomes empty when picked.\n+\t\t */\n+\t\tint melded_empty = amended_commit_is_empty(r);\n+\t\tif (melded_empty < 0) {\n+\t\t\tres = melded_empty;\n+\t\t\tgoto leave;\n+\t\t} else if (melded_empty && opts->keep_redundant_commits) {\n+\t\t\tflags |= ALLOW_EMPTY;\n+\t\t} else if (melded_empty && opts->drop_redundant_commits) {\n+\t\t\tdrop_commit = 1;\n+\t\t\trefs_delete_ref(get_main_ref_store(r), \"\", \"CHERRY_PICK_HEAD\",\n+\t\t\t\t\tNULL, REF_NO_DEREF);\n+\t\t\tunlink(git_path_merge_msg(r));\n+\t\t\trefs_delete_ref(get_main_ref_store(r), \"\", \"AUTO_MERGE\",\n+\t\t\t\t\tNULL, REF_NO_DEREF);\n+\t\t\t/*\n+\t\t\t * The commit the fixup was melded into was already\n+\t\t\t * created by the preceding \"pick\", so drop it by moving\n+\t\t\t * HEAD back to its parent.  Since the commit is being\n+\t\t\t * dropped rather than rewritten, discard the pending\n+\t\t\t * rewrite records and tell our caller not to add one, so\n+\t\t\t * that neither the dropped commit nor the fixup is\n+\t\t\t * recorded as rewritten.\n+\t\t\t */\n+\t\t\tres = reset_head_to_parent(r, opts, &head);\n+\t\t\tif (res)\n+\t\t\t\tgoto leave;\n+\t\t\tunlink(rebase_path_rewritten_pending());\n+\t\t\tif (dropped)\n+\t\t\t\t*dropped = 1;\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"dropping %s %s -- resulting commit is empty\\n\"),\n+\t\t\t\toid_to_hex(&commit->object.oid), msg.subject);\n+\t\t}\n+\t\t/* else the meld is non-empty, or empty but neither kept nor\n+\t\t * dropped, in which case do_commit halts on the empty result. */\n+\t} else {\n+\t\tallow = allow_empty(r, opts, commit);\n+\t\tif (allow < 0) {\n+\t\t\tres = allow;\n+\t\t\tgoto leave;\n+\t\t} else if (allow == 1) {\n+\t\t\tflags |= ALLOW_EMPTY;\n+\t\t} else if (allow == 2) {\n+\t\t\tdrop_commit = 1;\n+\t\t\trefs_delete_ref(get_main_ref_store(r), \"\", \"CHERRY_PICK_HEAD\",\n+\t\t\t\t\tNULL, REF_NO_DEREF);\n+\t\t\tunlink(git_path_merge_msg(r));\n+\t\t\trefs_delete_ref(get_main_ref_store(r), \"\", \"AUTO_MERGE\",\n+\t\t\t\t\tNULL, REF_NO_DEREF);\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"dropping %s %s -- patch contents already upstream\\n\"),\n+\t\t\t\toid_to_hex(&commit->object.oid), msg.subject);\n+\t\t} /* else allow == 0 and there's nothing special to do */\n+\t}\n \tif (!opts->no_commit && !drop_commit) {\n \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n \t\t\tres = do_commit(r, msg_file, author, reflog_action,\n@@ -4958,12 +5062,12 @@ static int pick_one_commit(struct repository *r,\n \t\t\t   struct replay_opts *opts,\n \t\t\t   int *check_todo, int* reschedule)\n {\n-\tint res;\n+\tint res, dropped = 0;\n \tstruct todo_item *item = todo_list->items + todo_list->current;\n \tconst char *arg = todo_item_get_arg(todo_list, item);\n \n \tres = do_pick_commit(r, item, opts, is_final_fixup(todo_list),\n-\t\t\t     check_todo);\n+\t\t\t     check_todo, &dropped);\n \tif (is_rebase_i(opts) && res < 0) {\n \t\t/* Reschedule */\n \t\t*reschedule = 1;\n@@ -4980,7 +5084,7 @@ static int pick_one_commit(struct repository *r,\n \t\treturn error_with_patch(r, commit,\n \t\t\t\t\targ, item->arg_len, opts, res, !res);\n \t}\n-\tif (is_rebase_i(opts) && !res)\n+\tif (is_rebase_i(opts) && !res && !dropped)\n \t\trecord_in_rewritten(&item->commit->object.oid,\n \t\t\t\t    peek_command(todo_list, 1));\n \tif (res && is_fixup(item->command)) {\n@@ -5545,7 +5649,7 @@ static int single_pick(struct repository *r,\n \t\t\tTODO_PICK : TODO_REVERT;\n \titem.commit = cmit;\n \n-\treturn do_pick_commit(r, &item, opts, 0, &check_todo);\n+\treturn do_pick_commit(r, &item, opts, 0, &check_todo, NULL);\n }\n \n int sequencer_pick_revisions(struct repository *r,\ndiff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh\nindex 5033411a43..d8085abf1d 100755\n--- a/t/t3415-rebase-autosquash.sh\n+++ b/t/t3415-rebase-autosquash.sh\n@@ -461,13 +461,15 @@ test_expect_success 'abort last squash' '\n \tgit commit --allow-empty -m second &&\n \tgit commit --allow-empty --squash HEAD &&\n \n+\t: \"squashing empty onto empty leaves an empty commit; --empty=keep\" &&\n+\t: \"keeps it so the squash still reaches the editor, which aborts\" &&\n \ttest_must_fail git -c core.editor=\"grep -q ^pick\" \\\n-\t\trebase -ki --autosquash HEAD~4 &&\n+\t\trebase -ki --autosquash --empty=keep HEAD~4 &&\n \t: do not finish the squash, but resolve it manually &&\n \tgit commit --allow-empty --amend -m edited-first &&\n \tgit rebase --skip &&\n \tgit show >actual &&\n-\t! grep first actual\n+\ttest_grep ! first actual\n '\n \n test_expect_success 'fixup a fixup' '\n@@ -510,4 +512,138 @@ test_expect_success 'pick and fixup respect commit.cleanup' '\n \ttest_commit_message HEAD -m \"something\"\n '\n \n+test_expect_success 'fixup! that empties its target is dropped with --empty=drop' '\n+\tgit reset --hard base &&\n+\ttest_commit --no-tag addX fileX 1 &&\n+\ttest_commit --no-tag changeX fileX 2 &&\n+\ttest_commit --no-tag later fileW hello &&\n+\techo 1 >fileX &&\n+\tgit commit -m \"fixup! changeX\" fileX &&\n+\n+\tgit rebase -i --autosquash --empty=drop HEAD~4 &&\n+\n+\tgit log --format=%s >actual &&\n+\ttest_grep ! changeX actual &&\n+\ttest_grep addX actual &&\n+\ttest_grep later actual &&\n+\techo 1 >expect &&\n+\ttest_cmp expect fileX &&\n+\techo hello >expect &&\n+\ttest_cmp expect fileW\n+'\n+\n+test_expect_success 'fixup! that empties its target is kept with --empty=keep' '\n+\tgit reset --hard base &&\n+\ttest_commit --no-tag addY fileY 1 &&\n+\ttest_commit --no-tag changeY fileY 2 &&\n+\techo 1 >fileY &&\n+\tgit commit -m \"fixup! changeY\" fileY &&\n+\n+\tgit rebase -i --autosquash --empty=keep HEAD~3 &&\n+\n+\tgit log --format=%s >actual &&\n+\ttest_grep changeY actual &&\n+\t: \"the retained commit is empty\" &&\n+\tgit diff --exit-code HEAD~1 HEAD &&\n+\techo 1 >expect &&\n+\ttest_cmp expect fileY\n+'\n+\n+test_expect_success 'fixup! that empties its target stops with --empty=stop' '\n+\tgit reset --hard base &&\n+\ttest_commit --no-tag addZ fileZ 1 &&\n+\ttest_commit --no-tag changeZ fileZ 2 &&\n+\techo 1 >fileZ &&\n+\tgit commit -m \"fixup! changeZ\" fileZ &&\n+\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_must_fail git rebase -i --autosquash --empty=stop HEAD~3\n+'\n+\n+test_expect_success 'squash! that empties its target is dropped with --empty=drop' '\n+\tgit reset --hard base &&\n+\ttest_commit --no-tag addS fileS 1 &&\n+\ttest_commit --no-tag changeS fileS 2 &&\n+\techo 1 >fileS &&\n+\tgit commit -m \"squash! changeS\" fileS &&\n+\n+\tgit rebase -i --autosquash --empty=drop HEAD~3 &&\n+\n+\tgit log --format=%s >actual &&\n+\ttest_grep ! changeS actual &&\n+\ttest_grep addS actual &&\n+\techo 1 >expect &&\n+\ttest_cmp expect fileS\n+'\n+\n+test_expect_success 'fixup! filling in an empty commit keeps a non-empty commit' '\n+\tgit reset --hard base &&\n+\tgit commit --allow-empty -m placeholder &&\n+\ttest_commit --no-tag \"fixup! placeholder\" fileP content &&\n+\n+\tgit rebase -i --autosquash --empty=stop HEAD~2 &&\n+\n+\tgit log --format=%s >actual &&\n+\ttest_grep placeholder actual &&\n+\techo content >expect &&\n+\ttest_cmp expect fileP &&\n+\t: \"the once-empty placeholder is no longer empty\" &&\n+\ttest_must_fail git diff --exit-code HEAD~1 HEAD\n+'\n+\n+test_expect_success 'fixup! leaving an empty commit empty stops with --empty=stop' '\n+\tgit reset --hard base &&\n+\tgit commit --allow-empty -m placeholder &&\n+\tgit commit --allow-empty -m \"fixup! placeholder\" &&\n+\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_must_fail git rebase -i --autosquash --empty=stop HEAD~2\n+'\n+\n+test_expect_success 'fixup! leaving an empty commit empty is dropped with --empty=drop' '\n+\tgit reset --hard base &&\n+\tgit commit --allow-empty -m placeholder &&\n+\tgit commit --allow-empty -m \"fixup! placeholder\" &&\n+\n+\tgit rebase -i --autosquash --empty=drop HEAD~2 &&\n+\n+\tgit log --format=%s >actual &&\n+\ttest_grep ! placeholder actual\n+'\n+\n+test_expect_success 'fixup! leaving an empty commit empty is kept with --empty=keep' '\n+\tgit reset --hard base &&\n+\tgit commit --allow-empty -m placeholder &&\n+\tgit commit --allow-empty -m \"fixup! placeholder\" &&\n+\n+\tgit rebase -i --autosquash --empty=keep HEAD~2 &&\n+\n+\tgit log --format=%s >actual &&\n+\ttest_grep placeholder actual &&\n+\tgit diff --exit-code HEAD~1 HEAD\n+'\n+\n+test_expect_success 'a dropped emptied fixup is not recorded as rewritten' '\n+\tgit reset --hard base &&\n+\ttest_commit --no-tag preR fileR 1 &&\n+\ttest_commit --no-tag changeR fileR 2 &&\n+\tR=$(git rev-parse HEAD) &&\n+\techo 1 >fileR &&\n+\tgit commit -m \"fixup! changeR\" fileR &&\n+\tF=$(git rev-parse HEAD) &&\n+\ttest_commit --no-tag keepR fileK keep &&\n+\n+\ttest_when_finished \"rm -f .git/hooks/post-rewrite actual.rewrites\" &&\n+\twrite_script .git/hooks/post-rewrite <<-\\EOF &&\n+\tcat >actual.rewrites\n+\tEOF\n+\n+\tgit rebase -i --autosquash --empty=drop HEAD~4 &&\n+\n+\t: \"changeR and its fixup were dropped, so must not be reported as\" &&\n+\t: \"rewritten, but the surviving keepR must be\" &&\n+\ttest_grep ! -e \"$R\" -e \"$F\" actual.rewrites &&\n+\ttest_grep \"$(git rev-parse HEAD)\" actual.rewrites\n+'\n+\n test_done\n\n\n\n"},{"id":"547874","messageId":"xmqqh5m494yh.fsf@gitster.g","threadId":"65978","inReplyTo":"20260711-fz-autosquash-empty-v3-1-d227b63eb511@gmail.com","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-12T05:01:26Z","receivedAt":"2026-07-12T05:01:29Z","isPatch":true,"body":"Farid Zakaria <farid.m.zakaria@gmail.com> writes:\n\n> When \"git rebase --autosquash\" melds a \"fixup!\" or \"squash!\" commit into\n> its target, the result can be a commit that no longer changes anything\n> relative to its parent, for example when the melded change reverts the\n> target.  Rather than dropping or keeping this empty commit, the rebase\n> stops with\n>\n> \tYou asked to amend the most recent commit, but doing so would\n> \tmake it empty. ...\n>\n> and the \"--empty\" option has no effect on it.  This makes backing a\n> change out of a series awkward: reverting a commit as a \"fixup!\" and\n> running \"git rebase --autosquash --empty=drop\" ought to remove both the\n> commit and its revert, but it halts instead.\n> ...\n> Changes in v3:\n>  * Switch the new tests' assertions from grep to test_grep for better\n>    diagnostics (per review).\n>  * Link to v2: https://lore.kernel.org/r/20260710-fz-autosquash-empty-v2-1-fa1e277e05f8@gmail.com\n\nI see you are already working well with Phillip, which is great.\n\nThis topic, when merged to 'seen', seems to have quite a lot of\noverlaps with his pw/rebase-drop-notes-with-commit topic.  We are\nexpecting the topic to be rerolled, and I was under the impression\nthat the remaining issues in that topic were all minor (Phillip,\ncorrect me if I am wrong) and hopefully we will see it in 'next'\nnot in so distant future.\n\nSo it might make sense for you to coordinate with Phillip, and wait\nfor his topic to be merged to 'next'.  After that happens, you would\nprepare a merge commit of the other branch into f85a7e6620 (Start\nGit 2.56 cycle, 2026-07-06) or some other stable point, and rebuild\nthis patch on top of it.  That way, it will be much less likely that\nI'd make stupid and unnecessary mismerges when attempting to\nintegrate this topic into my tree.\n\nThanks.\n"},{"id":"547989","messageId":"7a1e5111-185e-4390-afa1-c19908c9bd86@gmail.com","threadId":"65978","inReplyTo":"xmqqh5m494yh.fsf@gitster.g","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-07-13T13:18:34Z","receivedAt":"2026-07-13T13:18:41Z","isPatch":true,"body":"On 12/07/2026 06:01, Junio C Hamano wrote:\n> Farid Zakaria <farid.m.zakaria@gmail.com> writes:\n> \n>> When \"git rebase --autosquash\" melds a \"fixup!\" or \"squash!\" commit into\n>> its target, the result can be a commit that no longer changes anything\n>> relative to its parent, for example when the melded change reverts the\n>> target.  Rather than dropping or keeping this empty commit, the rebase\n>> stops with\n>>\n>> \tYou asked to amend the most recent commit, but doing so would\n>> \tmake it empty. ...\n>>\n>> and the \"--empty\" option has no effect on it.  This makes backing a\n>> change out of a series awkward: reverting a commit as a \"fixup!\" and\n>> running \"git rebase --autosquash --empty=drop\" ought to remove both the\n>> commit and its revert, but it halts instead.\n>> ...\n>> Changes in v3:\n>>   * Switch the new tests' assertions from grep to test_grep for better\n>>     diagnostics (per review).\n>>   * Link to v2: https://lore.kernel.org/r/20260710-fz-autosquash-empty-v2-1-fa1e277e05f8@gmail.com\n> \n> I see you are already working well with Phillip, which is great.\n> \n> This topic, when merged to 'seen', seems to have quite a lot of\n> overlaps with his pw/rebase-drop-notes-with-commit topic.\n\nOh, I should have thought of that\n\n> We are\n> expecting the topic to be rerolled, and I was under the impression\n> that the remaining issues in that topic were all minor (Phillip,\n> correct me if I am wrong) and hopefully we will see it in 'next'\n> not in so distant future.\n\nI've just sent a new version and cc'd Farid, I'll try and take look at \nthis patch tomorrow\n\n> So it might make sense for you to coordinate with Phillip, and wait\n> for his topic to be merged to 'next'.  After that happens, you would\n> prepare a merge commit of the other branch into f85a7e6620 (Start\n> Git 2.56 cycle, 2026-07-06) or some other stable point, and rebuild\n> this patch on top of it.  That way, it will be much less likely that\n> I'd make stupid and unnecessary mismerges when attempting to\n> integrate this topic into my tree.\n\nThat makes sense, assuming no-one has any more comments on \n'pw/rebase-drop-notes-with-commit' it should in be 'next' fairly soon.\n\nThanks\n\nPhillip\n"},{"id":"548020","messageId":"DJXL4KSUEAD4.1EE4ERHJZ00TR@gmail.com","threadId":"65978","inReplyTo":"7a1e5111-185e-4390-afa1-c19908c9bd86@gmail.com","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Farid Zakaria","fromEmail":"farid.m.zakaria@gmail.com","sentAt":"2026-07-13T16:30:40Z","receivedAt":"2026-07-13T16:30:43Z","isPatch":true,"body":"On Mon Jul 13, 2026 at 6:18 AM PDT, Phillip Wood wrote:\n> On 12/07/2026 06:01, Junio C Hamano wrote:\n>> Farid Zakaria <farid.m.zakaria@gmail.com> writes:\n>> \n>>> When \"git rebase --autosquash\" melds a \"fixup!\" or \"squash!\" commit into\n>>> its target, the result can be a commit that no longer changes anything\n>>> relative to its parent, for example when the melded change reverts the\n>>> target.  Rather than dropping or keeping this empty commit, the rebase\n>>> stops with\n>>>\n>>> \tYou asked to amend the most recent commit, but doing so would\n>>> \tmake it empty. ...\n>>>\n>>> and the \"--empty\" option has no effect on it.  This makes backing a\n>>> change out of a series awkward: reverting a commit as a \"fixup!\" and\n>>> running \"git rebase --autosquash --empty=drop\" ought to remove both the\n>>> commit and its revert, but it halts instead.\n>>> ...\n>>> Changes in v3:\n>>>   * Switch the new tests' assertions from grep to test_grep for better\n>>>     diagnostics (per review).\n>>>   * Link to v2: https://lore.kernel.org/r/20260710-fz-autosquash-empty-v2-1-fa1e277e05f8@gmail.com\n>> \n>> I see you are already working well with Phillip, which is great.\n>> \n>> This topic, when merged to 'seen', seems to have quite a lot of\n>> overlaps with his pw/rebase-drop-notes-with-commit topic.\n>\n> Oh, I should have thought of that\n>\n>> We are\n>> expecting the topic to be rerolled, and I was under the impression\n>> that the remaining issues in that topic were all minor (Phillip,\n>> correct me if I am wrong) and hopefully we will see it in 'next'\n>> not in so distant future.\n>\n> I've just sent a new version and cc'd Farid, I'll try and take look at \n> this patch tomorrow\n>\n\nThanks for cc'd. I'm not familiar with the workflow (I read the docs)\nbut is there an email reply when it's accepted into 'next' that I will\njust look-out for ? I'm not subscribed to the mailing list in general\notherwise.\n\n>> So it might make sense for you to coordinate with Phillip, and wait\n>> for his topic to be merged to 'next'.  After that happens, you would\n>> prepare a merge commit of the other branch into f85a7e6620 (Start\n>> Git 2.56 cycle, 2026-07-06) or some other stable point, and rebuild\n>> this patch on top of it.  That way, it will be much less likely that\n>> I'd make stupid and unnecessary mismerges when attempting to\n>> integrate this topic into my tree.\n>\n> That makes sense, assuming no-one has any more comments on \n> 'pw/rebase-drop-notes-with-commit' it should in be 'next' fairly soon.\n>\n> Thanks\n>\n> Phillip\n\nPhillip,\n\nLet me know if you have any more comments. I suspect not much will\nchanges logic-wise once I rebase it onto 'next'.\n\nFor clarity, is the f85a7e6620 commit the 'next' branch ? I would have\nthought to just rebase ontop of 'next' and I'm a bit confused with this\ncommit hash.\n\nIf there is anything else I should be aware of, I would appreciate a CC\nif you can remember :)\n\nThank you!\n"},{"id":"548300","messageId":"690b965e-5f07-4aa4-a64c-96e60a86d73b@gmail.com","threadId":"65978","inReplyTo":"20260711-fz-autosquash-empty-v3-1-d227b63eb511@gmail.com","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-07-15T15:30:19Z","receivedAt":"2026-07-15T15:30:25Z","isPatch":true,"body":"Hi Farid\n\nOn 12/07/2026 01:38, Farid Zakaria wrote:\n> When \"git rebase --autosquash\" melds a \"fixup!\" or \"squash!\" commit into\n> its target, the result can be a commit that no longer changes anything\n> relative to its parent, for example when the melded change reverts the\n> target.  Rather than dropping or keeping this empty commit, the rebase\n> stops with\n> \n> \tYou asked to amend the most recent commit, but doing so would\n> \tmake it empty. ...\n> \n> and the \"--empty\" option has no effect on it.  This makes backing a\n> change out of a series awkward: reverting a commit as a \"fixup!\" and\n> running \"git rebase --autosquash --empty=drop\" ought to remove both the\n> commit and its revert, but it halts instead.\n> \n> A \"fixup!\" is applied by amending HEAD, so the melded commit has HEAD's\n> parent as its parent and is empty when the index matches the tree of that\n> parent, not of HEAD.  do_pick_commit() only compares against HEAD, so it\n> never notices that the meld cancelled the commit out and falls through to\n> \"git commit --amend\", which refuses to create an empty commit.\n> \n> After melding a fixup or squash, check whether the amended commit is\n> empty -- its index matches the tree of HEAD's parent -- and, if so, honor\n> \"--empty\" just as for a commit that becomes empty when picked: keep it,\n> drop it, or halt.\n\nTo honor --empty we need to know if the commit that is being fixed up \nwas originally empty or not, as we should only drop commits that become \nempty. That means we cannot just check if the commit has become empty \nafter applying the fixup - we somehow need to remember whether the \noriginal commit was empty as well.\n\nHaving thought about it a little more, there are a quite a few corner \ncases which we need to think about. If there are conflicts when applying \nthe revert  the user might run \"git reset HEAD^\" to drop the commit \nthemselves which makes our life easy because we don't need to do \nanything special when they continue the rebase. However, they could run \n\"git checkout HEAD^ :/\" to reset all the files in the worktree without \ndropping the commit, in which case we need to update \ncommit_staged_changes() to drop HEAD if it wasn't originally empty.\n\nIf HEAD becomes empty in the middle of a sequence of fixups, for example\n\n     pick C\n     fixup revert-C\n     fixup D\n\nwe don't want to squash D into the previous commit, so I think we should \nonly drop commits that become empty after applying the all the fixups \ntargeting it. do_pick_commit() has a final_fixup function argument so \nthat should not be a problem.\n\nIf the original commit is empty then\n\n     pick empty\n     fixup commit-that-becomes-empty\n\nor\n\n     pick empty\n     fixup empty-fixup\n\nshould not drop the fixed up commit. In the first example we should \ncontinue to respect --empty=stop for the fixup becoming empty. The \nlatter only really makes sense with \"fixup -C\", or \"fixup -c\".\n\nThere isn't necessarily a pick command before a fixup for example\n\n     reset C\n     fixup revert-C\n\nor\n\n     exec some command\n     fixup revert-HEAD\n\nor\n\n     break\n     fixup revert-HEAD\n\nare all possible if the user edits the todo list. For these three cases \none option is to say that because there is not a \"pick\" command before \nthe \"fixup\" command we don't drop the commit. I think that probably \nmakes it easier to determine if the original commit was empty because we \ncan record that when we see the \"pick\" command. That does feels a bit \ninconsistent though. It is possible that a commit can become empty after \nthe user has reworded or edited it\n\n     reword C # or edit C\n     fixup revert-C\n\nbut it is a bit strange for the user to ask to edit a commit if they \nreally want to drop it, so maybe requiring a \"pick\" command in order for \nthe commit to be dropped is a good idea.\n\nI think we can record whether a pick is empty at the beginning of \ndo_pick_commit() and store that in a new member of struct replay_ctx. \nWe'll need to save and restore that new member when we stop for the user \nto resolve conflicts. The state reading is done in read_populate_opts(). \nTo save it we'll need to create a file when we stop for conflicts.\n\n> diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc\n> index f6c22d1598..7eb8bbe95f 100644\n> --- a/Documentation/git-rebase.adoc\n> +++ b/Documentation/git-rebase.adoc\n> @@ -282,6 +282,11 @@ by `git log --cherry-mark ...`) are detected and dropped as a\n>   preliminary step (unless `--reapply-cherry-picks` or `--keep-base` is\n>   passed).\n>   +\n> +A commit can also become empty as a result of `--autosquash`, when a\n> +`fixup!` or `squash!` commit cancels out all of the changes of the\n> +commit it is melded into.\n\nThe rebase man page does not currently use \"melded\", it talks about \nsquashing commits together - we should probably make the new text \nconsistent with that.\n\n> diff --git a/sequencer.c b/sequencer.c\n> index 0fe8fed6c3..bc24132c7c 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -1817,6 +1817,39 @@ static int allow_empty(struct repository *r,\n>   \t\treturn 0;\n>   }\n>   \n> +/*\n> + * Melding a \"fixup!\"/\"squash!\" amends HEAD, so the resulting commit is empty\n> + * when the index matches the tree of HEAD's parent (rather than of HEAD, as a\n> + * plain pick would).  Returns 1 if the amended commit would be empty, 0 if not,\n> + * and negative on error.\n> + */\n> +static int amended_commit_is_empty(struct repository *r)\n> +{\n> +\tstruct object_id head_oid, *cache_tree_oid;\n> +\tconst struct object_id *parent_tree_oid;\n> +\tstruct commit *head_commit;\n> +\n> +\tif (repo_get_oid(r, \"HEAD\", &head_oid))\n> +\t\treturn error(_(\"could not resolve HEAD commit\"));\n> +\thead_commit = lookup_commit_reference(r, &head_oid);\n\nYou can simplify this slightly with\n\n\thead = lookup_commit_reference_by_name(r, \"HEAD\");\n> +\tif (!head_commit || repo_parse_commit(r, head_commit))\n> +\t\treturn -1;\n> +\n> +\tif (head_commit->parents) {\n> +\t\tstruct commit *parent = head_commit->parents->item;\n> +\t\tif (repo_parse_commit(r, parent))\n> +\t\t\treturn -1;\n> +\t\tparent_tree_oid = get_commit_tree_oid(parent);\n> +\t} else {\n> +\t\tparent_tree_oid = the_hash_algo->empty_tree;\n> +\t}\n> +\n> +\tif (!(cache_tree_oid = get_cache_tree_oid(r->index)))\n> +\t\treturn -1;\n> +\n> +\treturn oideq(cache_tree_oid, parent_tree_oid);\n> +}\n> +\n>   static struct {\n>   \tchar c;\n>   \tconst char *str;\n> @@ -2260,10 +2293,34 @@ static const char *reflog_message(struct replay_opts *opts,\n> [...]\n>   static int do_pick_commit(struct repository *r,\n>   \t\t\t  struct todo_item *item,\n>   \t\t\t  struct replay_opts *opts,\n> -\t\t\t  int final_fixup, int *check_todo)\n> +\t\t\t  int final_fixup, int *check_todo, int *dropped)\n\nRather than adding a new parameter, I think we should extend the return \nenum added in pw/rebase-drop-notes-with-commit with a new member to \nindicate that we dropped HEAD.\n\n> @@ -2493,23 +2553,67 @@ static int do_pick_commit(struct repository *r,\n>   \t}\n>   \n>   \tdrop_commit = 0;\n> -\tallow = allow_empty(r, opts, commit);\n> -\tif (allow < 0) {\n> -\t\tres = allow;\n> -\t\tgoto leave;\n> -\t} else if (allow == 1) {\n> -\t\tflags |= ALLOW_EMPTY;\n> -\t} else if (allow == 2) {\n> -\t\tdrop_commit = 1;\n> -\t\trefs_delete_ref(get_main_ref_store(r), \"\", \"CHERRY_PICK_HEAD\",\n> -\t\t\t\tNULL, REF_NO_DEREF);\n> -\t\tunlink(git_path_merge_msg(r));\n> -\t\trefs_delete_ref(get_main_ref_store(r), \"\", \"AUTO_MERGE\",\n> -\t\t\t\tNULL, REF_NO_DEREF);\n> -\t\tfprintf(stderr,\n> -\t\t\t_(\"dropping %s %s -- patch contents already upstream\\n\"),\n> -\t\t\toid_to_hex(&commit->object.oid), msg.subject);\n> -\t} /* else allow == 0 and there's nothing special to do */\n\nI don't think we want to delete this - we still want to tell the user if \na fixup became empty, but we want an additional check along the lines of\n\n\tif (final_fixup) {\n\t\t/*\n\t\t * If the original commit was not empty and HEAD is now\n\t\t * empty then drop HEAD.\n\t\t */\n  \t}\n\n> @@ -4980,7 +5084,7 @@ static int pick_one_commit(struct repository *r,\n>   \t\treturn error_with_patch(r, commit,\n>   \t\t\t\t\targ, item->arg_len, opts, res, !res);\n>   \t}\n> -\tif (is_rebase_i(opts) && !res)\n> +\tif (is_rebase_i(opts) && !res && !dropped)\n>   \t\trecord_in_rewritten(&item->commit->object.oid,\n>   \t\t\t\t    peek_command(todo_list, 1));\n\nDon't we need to clear the pending list of rewritten commits from the \noriginal pick and any intermediate fixups, rather than just to skipping \nrecording the final fixup as rewritten? It is probably worth adding a \ntest to 5407 to check that (there is an example in \npw/rebase-drop-notes-with-commit).\n> diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh\n> index 5033411a43..d8085abf1d 100755\n> --- a/t/t3415-rebase-autosquash.sh\n> +++ b/t/t3415-rebase-autosquash.sh\n> @@ -461,13 +461,15 @@ test_expect_success 'abort last squash' '\n>   \tgit commit --allow-empty -m second &&\n>   \tgit commit --allow-empty --squash HEAD &&\n>   \n> +\t: \"squashing empty onto empty leaves an empty commit; --empty=keep\" &&\n> +\t: \"keeps it so the squash still reaches the editor, which aborts\" &&\n>   \ttest_must_fail git -c core.editor=\"grep -q ^pick\" \\\n> -\t\trebase -ki --autosquash HEAD~4 &&\n> +\t\trebase -ki --autosquash --empty=keep HEAD~4 &&\n\nAre we adding --empty=keep for clarity here? I wonder if the original \nwas deliberately testing the default.\n>   \t: do not finish the squash, but resolve it manually &&\n>   \tgit commit --allow-empty --amend -m edited-first &&\n>   \tgit rebase --skip &&\n>   \tgit show >actual &&\n> -\t! grep first actual\n> +\ttest_grep ! first actual\n>   '\n\n\n> +test_expect_success 'fixup! leaving an empty commit empty stops with --empty=stop' '\n> +\tgit reset --hard base &&\n> +\tgit commit --allow-empty -m placeholder &&\n> +\tgit commit --allow-empty -m \"fixup! placeholder\" &&\n\nAs both commits start off empty we shouldn't stop. --empty only applies \nto commits that become empty when they are rebased. The same applies to \nthe next couple of tests.\n\nThanks\n\nPhillip\n\n> +\ttest_when_finished \"git rebase --abort\" &&\n> +\ttest_must_fail git rebase -i --autosquash --empty=stop HEAD~2\n> +'\n> +\n> +test_expect_success 'fixup! leaving an empty commit empty is dropped with --empty=drop' '\n> +\tgit reset --hard base &&\n> +\tgit commit --allow-empty -m placeholder &&\n> +\tgit commit --allow-empty -m \"fixup! placeholder\" &&\n> +\n> +\tgit rebase -i --autosquash --empty=drop HEAD~2 &&\n> +\n> +\tgit log --format=%s >actual &&\n> +\ttest_grep ! placeholder actual\n> +'\n> +\n> +test_expect_success 'fixup! leaving an empty commit empty is kept with --empty=keep' '\n> +\tgit reset --hard base &&\n> +\tgit commit --allow-empty -m placeholder &&\n> +\tgit commit --allow-empty -m \"fixup! placeholder\" &&\n> +\n> +\tgit rebase -i --autosquash --empty=keep HEAD~2 &&\n> +\n> +\tgit log --format=%s >actual &&\n> +\ttest_grep placeholder actual &&\n> +\tgit diff --exit-code HEAD~1 HEAD\n> +'\n> +\n> +test_expect_success 'a dropped emptied fixup is not recorded as rewritten' '\n> +\tgit reset --hard base &&\n> +\ttest_commit --no-tag preR fileR 1 &&\n> +\ttest_commit --no-tag changeR fileR 2 &&\n> +\tR=$(git rev-parse HEAD) &&\n> +\techo 1 >fileR &&\n> +\tgit commit -m \"fixup! changeR\" fileR &&\n> +\tF=$(git rev-parse HEAD) &&\n> +\ttest_commit --no-tag keepR fileK keep &&\n> +\n> +\ttest_when_finished \"rm -f .git/hooks/post-rewrite actual.rewrites\" &&\n> +\twrite_script .git/hooks/post-rewrite <<-\\EOF &&\n> +\tcat >actual.rewrites\n> +\tEOF\n> +\n> +\tgit rebase -i --autosquash --empty=drop HEAD~4 &&\n> +\n> +\t: \"changeR and its fixup were dropped, so must not be reported as\" &&\n> +\t: \"rewritten, but the surviving keepR must be\" &&\n> +\ttest_grep ! -e \"$R\" -e \"$F\" actual.rewrites &&\n> +\ttest_grep \"$(git rev-parse HEAD)\" actual.rewrites\n> +'\n> +\n>   test_done\n> \n> \n> \n> \n\n"},{"id":"548303","messageId":"b4cf8f14-1ffa-4395-bc3e-936538574665@gmail.com","threadId":"65978","inReplyTo":"DJXL4KSUEAD4.1EE4ERHJZ00TR@gmail.com","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-07-15T15:44:35Z","receivedAt":"2026-07-15T15:44:39Z","isPatch":true,"body":"Hi Farid\n\nOn 13/07/2026 17:30, Farid Zakaria wrote:\n> On Mon Jul 13, 2026 at 6:18 AM PDT, Phillip Wood wrote:\n>> On 12/07/2026 06:01, Junio C Hamano wrote:\n> \n> Thanks for cc'd. I'm not familiar with the workflow (I read the docs)\n> but is there an email reply when it's accepted into 'next' that I will\n> just look-out for ? I'm not subscribed to the mailing list in general\n> otherwise.\n\nThere isn't a specific notification for each topic, but the status of \nall topics is in the regular \"what's cooking in git.git\" email on the list.\n\n>>> So it might make sense for you to coordinate with Phillip, and wait\n>>> for his topic to be merged to 'next'.  After that happens, you would\n>>> prepare a merge commit of the other branch into f85a7e6620 (Start\n>>> Git 2.56 cycle, 2026-07-06) or some other stable point, and rebuild\n>>> this patch on top of it.  That way, it will be much less likely that\n>>> I'd make stupid and unnecessary mismerges when attempting to\n>>> integrate this topic into my tree.\n>>\n>> That makes sense, assuming no-one has any more comments on\n>> 'pw/rebase-drop-notes-with-commit' it should in be 'next' fairly soon.\n>>\n>> Thanks\n>>\n>> Phillip\n> \n> Phillip,\n> \n> Let me know if you have any more comments. I suspect not much will\n> changes logic-wise once I rebase it onto 'next'.\n\nI've left some comments on the patch in a separate mail.\n\n> For clarity, is the f85a7e6620 commit the 'next' branch ? I would have\n> thought to just rebase ontop of 'next' and I'm a bit confused with this\n> commit hash.\n\nIn general it is better to base patches directly on top of the topic \nthey build on rather than on top of next. Once a topic is merged to next \nit should be stable, whereas the tip of next is periodically rebuilt and \nforce-pushed. The tip of pw/rebase-drop-notes-with-commit is currently \n7e70d12417d (sequencer: do not record dropped commits as rewritten, \n2026-07-13) but that will change when Junio picks up v3. I find the \nbranch tips in seen and next with\n\n     git show $(git log --merges --format=%H --grep 'pw/.*drop-notes/' \\\n                -1  origin/seen)^2\n\n> If there is anything else I should be aware of, I would appreciate a CC\n> if you can remember :)\nElsewhere you asked about using AI. There are some notes about that in \nDocumentation/SubmittingPatches. TLDR it is fine so long as it does not \nconflict with your obligations under the Developer Certificate of Origin.\n\nThanks\n\nPhillip\n"},{"id":"549889","messageId":"xmqq8q6jhtws.fsf@gitster.g","threadId":"65978","inReplyTo":"DJXL4KSUEAD4.1EE4ERHJZ00TR@gmail.com","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-06T20:32:19Z","receivedAt":"2026-08-06T20:32:21Z","isPatch":true,"body":"\"Farid Zakaria\" <farid.m.zakaria@gmail.com> writes:\n\n> Let me know if you have any more comments. I suspect not much will\n> changes logic-wise once I rebase it onto 'next'.\n>\n> For clarity, is the f85a7e6620 commit the 'next' branch ? I would have\n> thought to just rebase ontop of 'next' and I'm a bit confused with this\n> commit hash.\n>\n> If there is anything else I should be aware of, I would appreciate a CC\n> if you can remember :)\n\nIt has been quite a while since you received a reply from Phillip to\nthe quoted message above.  Has there been any progress to share?\n\nThanks.\n\n"},{"id":"550069","messageId":"DKJ2CZKJC6P0.VHLMCUDH6Z44@gmail.com","threadId":"65978","inReplyTo":"xmqq8q6jhtws.fsf@gitster.g","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Farid Zakaria","fromEmail":"farid.m.zakaria@gmail.com","sentAt":"2026-08-07T22:26:57Z","receivedAt":"2026-08-07T22:27:01Z","isPatch":true,"body":"On Thu Aug 6, 2026 at 1:32 PM PDT, Junio C Hamano wrote:\n> \"Farid Zakaria\" <farid.m.zakaria@gmail.com> writes:\n>\n>> Let me know if you have any more comments. I suspect not much will\n>> changes logic-wise once I rebase it onto 'next'.\n>>\n>> For clarity, is the f85a7e6620 commit the 'next' branch ? I would have\n>> thought to just rebase ontop of 'next' and I'm a bit confused with this\n>> commit hash.\n>>\n>> If there is anything else I should be aware of, I would appreciate a CC\n>> if you can remember :)\n>\n> It has been quite a while since you received a reply from Phillip to\n> the quoted message above.  Has there been any progress to share?\n>\n> Thanks.\n\nHi Junio,\n\nSorry I let this slip. I was waiting for the work to be accepted to\navoid rebasing on top of a moving target -- I am still a little new to\nmailing list workflow & I have been using b4 (recommended from Linux).\n\nI will pick this up again soon.\n\n\n"},{"id":"551316","messageId":"xmqq8q5seh5h.fsf@gitster.g","threadId":"65978","inReplyTo":"DKJ2CZKJC6P0.VHLMCUDH6Z44@gmail.com","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-26T20:55:06Z","receivedAt":"2026-08-26T20:55:09Z","isPatch":true,"body":"\"Farid Zakaria\" <farid.m.zakaria@gmail.com> writes:\n\n> On Thu Aug 6, 2026 at 1:32 PM PDT, Junio C Hamano wrote:\n>> \"Farid Zakaria\" <farid.m.zakaria@gmail.com> writes:\n>> ...\n>> It has been quite a while since you received a reply from Phillip to\n>> the quoted message above.  Has there been any progress to share?\n> ...\n> Sorry I let this slip. I was waiting for the work to be accepted to\n> avoid rebasing on top of a moving target -- I am still a little new to\n> mailing list workflow & I have been using b4 (recommended from Linux).\n>\n> I will pick this up again soon.\n\nAny change of plans or situation since then?\n\nThanks.\n"},{"id":"551385","messageId":"DKZXQ0ACZO4D.172D72QO3E3QF@gmail.com","threadId":"65978","inReplyTo":"xmqq8q5seh5h.fsf@gitster.g","subject":"Re: [PATCH v3] sequencer: honor --empty when a fixup!/squash! empties its target","fromName":"Farid Zakaria","fromEmail":"farid.m.zakaria@gmail.com","sentAt":"2026-08-27T18:24:11Z","receivedAt":"2026-08-27T18:24:13Z","isPatch":true,"body":"On Wed Aug 26, 2026 at 1:55 PM PDT, Junio C Hamano wrote:\n> \"Farid Zakaria\" <farid.m.zakaria@gmail.com> writes:\n>\n>> On Thu Aug 6, 2026 at 1:32 PM PDT, Junio C Hamano wrote:\n>>> \"Farid Zakaria\" <farid.m.zakaria@gmail.com> writes:\n>>> ...\n>>> It has been quite a while since you received a reply from Phillip to\n>>> the quoted message above.  Has there been any progress to share?\n>> ...\n>> Sorry I let this slip. I was waiting for the work to be accepted to\n>> avoid rebasing on top of a moving target -- I am still a little new to\n>> mailing list workflow & I have been using b4 (recommended from Linux).\n>>\n>> I will pick this up again soon.\n>\n> Any change of plans or situation since then?\n>\n> Thanks.\n\nSorry for the delay.\n\nI just published V4 but I see it attached to another thread on lore\n(I migth have mixed up the threads).\n\nhttps://lore.kernel.org/git/20260709-fz-autosquash-empty-v1-1-84cb494c3613@gmail.com/T/#m54a4af96c468c4f2b94fc51f10b7d8325ae62654\n"}]}