From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Phillip Wood <phillip.wood123@gmail.com>,
"D. Ben Knoble" <ben.knoble@gmail.com>,
Patrick Steinhardt <ps@pks.im>, Matt Hunter <m@lfurio.us>,
Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v10 0/5] history: add squash subcommand to fold a range
Date: Mon, 20 Jul 2026 08:26:59 +0000 [thread overview]
Message-ID: <pull.2337.v10.git.git.1784536024.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2337.v9.git.git.1784128573.gitgitgadget@gmail.com>
Adds git history squash <revision-range> to fold a range of commits.
Changes in v10:
* Record the full revision expression in squash reflog.
* Preserve the boundary-walk invariant when sanitizing rev-list options.
* Clarify amend! and --reedit-message documentation.
Changes in v9:
* Use the last amend! targeting the oldest folded commit as the default
squashed message. Ignore amend! markers targeting later commits while
selecting that replacement message.
* Improve tests.
Changes in v8:
* --reedit-message now builds the same editor template as git rebase -i
--autosquash: fixup!, squash! and amend! commits are grouped under the
commit they target instead of shown in commit order, and an amend!
replaces its target's message.
* A fixup!, squash! or amend! is refused only when its target is outside
the range, so several fixups for an in-range commit fold together. A
range that is entirely markers for one below-range target is combined
into a single commit, keeping the last amend! message.
* Merges inside the range are folded when the range has a single base, with
no dedicated opt-in flag, --ancestry-path ensures only commits descended
from the base are folded, and a range reaching more than one base is
rejected.
* Rev-list options are accepted and sanitized the way git replay does,
forcing the walk order back with a warning, which also fixes git history
squash -- --reverse slipping past the previous option check.
* Kept this as an explicit squash subcommand rather than making
--reedit-message the default or renaming the command.
Changes in v7:
* --reedit-message now builds the same editor template git rebase -i shows
for a squash (a combination of N commits banner with each folded message
under its own header) and follows autosquash for markers: a fixup!
message falls out (commented under a will be skipped header), while a
squash! or amend! keeps its body with only the marker subject commented
so its remark can be reworded in. Only the message text is affected,
every commit's changes are always folded in.
* Reuse git rebase -i's squash-message code: a preparatory sequencer:
commit extracts the banner, header and marker-comment helpers so both
rebase and git history squash build the identical template from one
source.
* Refuse a range whose oldest commit is a fixup!, squash! or amend!, since
the marker's target cannot be inside the range.
* Reorder the squash usage so dashed options come before <revision-range>,
and spell out HEAD instead of @ in the documentation and examples.
* Expand the squash commit message and documentation with this overview,
and scope the merge limitation so it no longer contradicts squash folding
a single-base interior merge.
Changes in v6:
* git history squash now accepts multiple revision arguments, read like the
arguments to git-rev-list, so a compound range such as @~3.. ^topic
works.
* The base to reparent onto is now the oldest in-range commit's parent; a
boundary other than that base means the range has more than one base and
is rejected. This also fixes the earlier overly-restrictive handling of
merges and side branches.
* A single-commit range (e.g. @^!) is rejected with "nothing to squash"
(this also covers the @^!-style example that previously succeeded
silently).
* Commit messages reworded: the squash commit now gives an overview of
fixup!/squash!/amend! handling, rewording, merge-parent and ref behavior.
Changes in v5:
* The range walk now uses --ancestry-path, so only commits descended from
the base are folded; a single revision such as HEAD or HEAD~1 is now
rejected as "not a <base>..<tip> range" rather than treated as a squash
down to the root.
* This adopts the --ancestry-path suggestion; the multi-base rejection is
unchanged, so a side branch that forked before the base and merged in is
still refused.
* Added tests covering more merge topologies: two interior merges, a nested
merge, an octopus merge, an octopus arm forked before the base, a merge
among the descendants replayed above the range, and a ref pointing at an
interior merge commit.
Changes in v4:
* git history squash now detects when another ref points at a commit inside
the range being folded and refuses, with an advice.historyUpdateRefs hint
to use --update-refs=head.
* A merge inside the range is folded fine as long as the range has a single
base; a range with merge commit at the tip or base also folds correctly.
Only a range with more than one base is rejected.
Changes in v3:
* Moved the feature out of git rebase and into a new git history squash
<revision-range> subcommand, per the list discussion. git rebase --squash
is dropped.
* Takes an arbitrary range (git history squash @~3.., git history squash
@~5..@~2), folding it into the oldest commit and replaying any
descendants on top.
* Implemented as a single tree operation rather than picking each commit,
so there are no repeated conflict stops (addresses Phillip's efficiency
point).
* A merge inside the range is folded fine, only a range with more than one
base is rejected.
* --reedit-message seeds the editor with every folded-in message, not just
the oldest.
Harald Nordgren (5):
history: extract helper for a commit's parent tree
history: give commit_tree_ext a message template
history: add squash subcommand to fold a range
sequencer: share the squash message marker helpers and flags
history: re-edit a squash with every message
Documentation/config/advice.adoc | 4 +
Documentation/git-history.adoc | 55 ++-
advice.c | 1 +
advice.h | 1 +
builtin/history.c | 552 ++++++++++++++++++++--
sequencer.c | 70 +--
sequencer.h | 30 ++
t/meson.build | 1 +
t/t3455-history-squash.sh | 770 +++++++++++++++++++++++++++++++
9 files changed, 1412 insertions(+), 72 deletions(-)
create mode 100755 t/t3455-history-squash.sh
base-commit: 41365c2a9ba347870b80881c0d67454edd22fd49
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2337%2FHaraldNordgren%2Frebase-fixup-fold-v10
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2337/HaraldNordgren/rebase-fixup-fold-v10
Pull-Request: https://github.com/git/git/pull/2337
Range-diff vs v9:
1: 352c818c29 = 1: f848103497 history: extract helper for a commit's parent tree
2: e06e49095b = 2: 49dadc3410 history: give commit_tree_ext a message template
3: ead974c317 ! 3: 6b5b2c93f2 history: add squash subcommand to fold a range
@@ Documentation/config/advice.adoc: all advice messages.
set as executable.
## Documentation/git-history.adoc ##
-@@ Documentation/git-history.adoc: SYNOPSIS
+@@ Documentation/git-history.adoc: git history drop <commit> [--dry-run] [--update-refs=(branches|head)] [--empty=(
git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)]
git history reword <commit> [--dry-run] [--update-refs=(branches|head)]
git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]
@@ Documentation/git-history.adoc: linkgit:gitglossary[7].
++
+A `fixup!`, `squash!`, or `amend!` commit is refused unless the commit it
+targets is also in the range, so the fold does not silently absorb a
-+marker meant for a commit outside it. The body after an `amend!` subject
-+replaces the oldest commit's message when the marker targets that commit. As
-+an exception, a range made up entirely of markers for one target is combined
-+into a single commit, keeping the last `amend!` message if there is one.
++marker meant for a commit outside it. As an exception, a range made up entirely
++of markers for one target is combined into a single commit, keeping the last
++`amend!` message if there is one.
++
+A branch or tag that points at a commit inside the range would be left
+dangling once those commits are folded away, so with the default
@@ Documentation/git-history.adoc: OPTIONS
`--reedit-message`::
- Open an editor to modify the target commit's message.
-+ Open an editor to modify the rewritten commit's message. For `squash`
-+ the editor is pre-filled with the messages of all the folded commits.
++ Open an editor to modify the rewritten commit's message.
`--empty=(drop|keep|abort)`::
Control what happens when a commit becomes empty as a result of the
@@ builtin/history.c: out:
+
+ if (revs.reverse != 1 || revs.topo_order != 1 ||
+ revs.sort_order != REV_SORT_IN_GRAPH_ORDER ||
-+ revs.simplify_history != 0) {
++ revs.simplify_history != 0 || revs.boundary != 1) {
+ warning(_("ignoring rev-list options that would change how the "
+ "range is walked"));
+ revs.reverse = 1;
+ revs.topo_order = 1;
+ revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
+ revs.simplify_history = 0;
++ revs.boundary = 1;
+ }
+
+ /*
@@ builtin/history.c: out:
+ goto out;
+ }
+
-+ strbuf_addf(&reflog_msg, "squash: updating %s", argv[0]);
++ strbuf_addstr(&reflog_msg, "squash: updating ");
++ strbuf_join_argv(&reflog_msg, argc, argv, ' ');
+
+ ret = handle_reference_updates(&revs, action, tip, rewritten,
+ reflog_msg.buf, dry_run,
@@ builtin/history.c: out:
+ return ret;
+}
+
- int cmd_history(int argc,
- const char **argv,
- const char *prefix,
+ static int update_worktree(struct repository *repo,
+ const struct commit *old_head,
+ const struct commit *new_head,
@@ builtin/history.c: int cmd_history(int argc,
GIT_HISTORY_FIXUP_USAGE,
GIT_HISTORY_REWORD_USAGE,
@@ builtin/history.c: int cmd_history(int argc,
## t/meson.build ##
@@ t/meson.build: integration_tests = [
- 't3451-history-reword.sh',
't3452-history-split.sh',
't3453-history-fixup.sh',
+ 't3454-history-drop.sh',
+ 't3455-history-squash.sh',
't3500-cherry.sh',
't3501-revert-cherry-pick.sh',
@@ t/t3455-history-squash.sh (new)
+
+ git history squash start..HEAD ^keep &&
+
++ git reflog -1 --format=%gs >actual &&
++ echo "squash: updating start..HEAD ^keep" >expect &&
++ test_cmp expect actual &&
++
+ check_log_subjects start..HEAD <<-\EOF &&
+ two
+ one
4: 08915cee51 = 4: 41156c9afb sequencer: share the squash message marker helpers and flags
5: fb76afe31c ! 5: cdbc183428 history: re-edit a squash with every message
@@ Documentation/git-history.adoc: already on `topic`. Rev-list options may also be
+
A `fixup!`, `squash!`, or `amend!` commit is refused unless the commit it
targets is also in the range, so the fold does not silently absorb a
-@@ Documentation/git-history.adoc: marker meant for a commit outside it. The body after an `amend!` subject
- replaces the oldest commit's message when the marker targets that commit. As
- an exception, a range made up entirely of markers for one target is combined
- into a single commit, keeping the last `amend!` message if there is one.
-+The changes from every commit in the range are always folded in. Only the
-+message text differs.
+@@ Documentation/git-history.adoc: marker meant for a commit outside it. As an exception, a range made up entirely
+ of markers for one target is combined into a single commit, keeping the last
+ `amend!` message if there is one.
+ +
+With `--reedit-message` the template mirrors `git rebase -i --autosquash`:
+each `fixup!`, `squash!`, or `amend!` is grouped under the commit it
+targets rather than shown in commit order. A `fixup!` message is dropped
@@ Documentation/git-history.adoc: marker meant for a commit outside it. The body a
+subject commented, and an `amend!` replaces its target's message, unless
+a `squash!` folded into that target first, in which case it keeps its
+body like a `squash!`.
- +
+++
A branch or tag that points at a commit inside the range would be left
dangling once those commits are folded away, so with the default
+ `--update-refs=branches` the command refuses. Rerun with
+@@ Documentation/git-history.adoc: OPTIONS
+ ref updates is generally safe.
+
+ `--reedit-message`::
+- Open an editor to modify the rewritten commit's message.
++ Open an editor to modify the rewritten commit's message. For `squash`
++ the editor is pre-filled with the messages of all the folded commits.
+
+ `--empty=(drop|keep|abort)`::
+ Control what happens when a commit becomes empty as a result of the
## builtin/history.c ##
@@ builtin/history.c: static int find_interior_ref(const struct reference *ref, void *cb_data)
--
gitgitgadget
next prev parent reply other threads:[~2026-07-20 8:27 UTC|newest]
Thread overview: 115+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-14 19:25 [PATCH 0/2] rebase: add --fixup to fold a range into its oldest commit Harald Nordgren via GitGitGadget
2026-06-14 19:25 ` [PATCH 1/2] t3415: remove prepare-commit-msg hook after use Harald Nordgren via GitGitGadget
2026-06-14 19:25 ` [PATCH 2/2] rebase: add --fixup-all to fold a range Harald Nordgren via GitGitGadget
2026-06-15 2:01 ` [PATCH 0/2] rebase: add --fixup to fold a range into its oldest commit Junio C Hamano
2026-06-15 8:18 ` Harald Nordgren
2026-06-15 15:17 ` D. Ben Knoble
2026-06-16 8:34 ` Patrick Steinhardt
2026-06-17 9:30 ` Harald Nordgren
2026-06-15 8:37 ` [PATCH v2 0/2] rebase: add --squash to fold a range into its first commit Harald Nordgren via GitGitGadget
2026-06-15 8:37 ` [PATCH v2 1/2] t3415: remove prepare-commit-msg hook after use Harald Nordgren via GitGitGadget
2026-06-15 8:37 ` [PATCH v2 2/2] rebase: add --squash to fold a range Harald Nordgren via GitGitGadget
2026-06-16 10:10 ` [PATCH v2 0/2] rebase: add --squash to fold a range into its first commit Phillip Wood
2026-06-17 9:11 ` Harald Nordgren
2026-06-17 9:48 ` Phillip Wood
2026-06-18 19:17 ` [PATCH v3 0/4] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-06-18 19:17 ` [PATCH v3 1/4] history: extract helper for a commit's parent tree Harald Nordgren via GitGitGadget
2026-06-18 19:17 ` [PATCH v3 2/4] history: give commit_tree_ext a message template Harald Nordgren via GitGitGadget
2026-06-18 19:17 ` [PATCH v3 3/4] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-06-18 20:30 ` Junio C Hamano
2026-06-18 21:24 ` Junio C Hamano
2026-06-18 21:29 ` D. Ben Knoble
2026-06-19 12:55 ` Patrick Steinhardt
2026-06-18 19:17 ` [PATCH v3 4/4] history: re-edit a squash with every message Harald Nordgren via GitGitGadget
2026-06-18 21:23 ` [PATCH v3 0/4] history: add squash subcommand to fold a range D. Ben Knoble
2026-06-19 0:34 ` Junio C Hamano
2026-06-19 12:37 ` Patrick Steinhardt
2026-06-19 16:11 ` Junio C Hamano
2026-06-21 5:53 ` [PATCH v4 " Harald Nordgren via GitGitGadget
2026-06-21 5:53 ` [PATCH v4 1/4] history: extract helper for a commit's parent tree Harald Nordgren via GitGitGadget
2026-06-21 5:53 ` [PATCH v4 2/4] history: give commit_tree_ext a message template Harald Nordgren via GitGitGadget
2026-06-21 5:53 ` [PATCH v4 3/4] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-06-21 5:53 ` [PATCH v4 4/4] history: re-edit a squash with every message Harald Nordgren via GitGitGadget
2026-06-22 11:54 ` [PATCH v4 0/4] history: add squash subcommand to fold a range Patrick Steinhardt
2026-06-23 10:41 ` Harald Nordgren
2026-06-24 21:54 ` [PATCH v5 " Harald Nordgren via GitGitGadget
2026-06-24 21:54 ` [PATCH v5 1/4] history: extract helper for a commit's parent tree Harald Nordgren via GitGitGadget
2026-06-24 21:55 ` [PATCH v5 2/4] history: give commit_tree_ext a message template Harald Nordgren via GitGitGadget
2026-06-24 21:55 ` [PATCH v5 3/4] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-06-24 21:55 ` [PATCH v5 4/4] history: re-edit a squash with every message Harald Nordgren via GitGitGadget
2026-06-26 8:52 ` [PATCH v5 0/4] history: add squash subcommand to fold a range Phillip Wood
2026-06-26 9:57 ` Harald Nordgren
2026-06-26 13:12 ` Phillip Wood
2026-06-26 14:02 ` Junio C Hamano
2026-06-26 18:36 ` Harald Nordgren
2026-06-29 6:26 ` Patrick Steinhardt
2026-06-29 15:51 ` Phillip Wood
2026-06-29 16:54 ` Junio C Hamano
2026-07-01 13:45 ` Phillip Wood
2026-06-29 18:03 ` Harald Nordgren
2026-06-29 19:48 ` Phillip Wood
2026-06-29 21:13 ` Harald Nordgren
2026-06-30 13:48 ` Phillip Wood
2026-06-30 18:38 ` Harald Nordgren
2026-07-01 10:31 ` Phillip Wood
2026-07-01 13:47 ` Junio C Hamano
2026-07-01 15:14 ` Phillip Wood
2026-07-01 17:41 ` Junio C Hamano
2026-07-02 13:58 ` Phillip Wood
2026-06-30 2:55 ` Matt Hunter
2026-06-30 7:19 ` Harald Nordgren
2026-06-30 9:23 ` Matt Hunter
2026-06-30 14:01 ` Phillip Wood
2026-07-02 12:54 ` Patrick Steinhardt
2026-07-02 20:28 ` Junio C Hamano
2026-06-29 16:09 ` Harald Nordgren
2026-06-28 8:29 ` [PATCH v6 " Harald Nordgren via GitGitGadget
2026-06-28 8:29 ` [PATCH v6 1/4] history: extract helper for a commit's parent tree Harald Nordgren via GitGitGadget
2026-06-28 8:29 ` [PATCH v6 2/4] history: give commit_tree_ext a message template Harald Nordgren via GitGitGadget
2026-06-28 8:29 ` [PATCH v6 3/4] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-06-29 5:50 ` Junio C Hamano
2026-06-28 8:29 ` [PATCH v6 4/4] history: re-edit a squash with every message Harald Nordgren via GitGitGadget
2026-06-29 5:50 ` Junio C Hamano
2026-06-29 13:49 ` Harald Nordgren
2026-06-29 14:49 ` Junio C Hamano
2026-06-29 17:38 ` Junio C Hamano
2026-07-06 8:50 ` [PATCH v7 0/5] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-07-06 8:50 ` [PATCH v7 1/5] history: extract helper for a commit's parent tree Harald Nordgren via GitGitGadget
2026-07-06 8:50 ` [PATCH v7 2/5] history: give commit_tree_ext a message template Harald Nordgren via GitGitGadget
2026-07-06 8:50 ` [PATCH v7 3/5] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-07-06 8:50 ` [PATCH v7 4/5] sequencer: extract helpers for the squash message markers Harald Nordgren via GitGitGadget
2026-07-06 8:50 ` [PATCH v7 5/5] history: re-edit a squash with every message Harald Nordgren via GitGitGadget
2026-07-06 14:06 ` [PATCH v7 0/5] history: add squash subcommand to fold a range Phillip Wood
2026-07-07 7:51 ` Harald Nordgren
2026-07-07 8:55 ` Harald Nordgren
2026-07-07 9:30 ` Phillip Wood
2026-07-07 9:48 ` Phillip Wood
2026-07-06 20:42 ` Junio C Hamano
2026-07-10 9:06 ` [PATCH v8 " Harald Nordgren via GitGitGadget
2026-07-10 9:06 ` [PATCH v8 1/5] history: extract helper for a commit's parent tree Harald Nordgren via GitGitGadget
2026-07-10 9:06 ` [PATCH v8 2/5] history: give commit_tree_ext a message template Harald Nordgren via GitGitGadget
2026-07-10 9:06 ` [PATCH v8 3/5] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-07-10 9:06 ` [PATCH v8 4/5] sequencer: share the squash message marker helpers and flags Harald Nordgren via GitGitGadget
2026-07-10 9:06 ` [PATCH v8 5/5] history: re-edit a squash with every message Harald Nordgren via GitGitGadget
2026-07-14 4:44 ` [PATCH v8 0/5] history: add squash subcommand to fold a range Matt Hunter
2026-07-14 8:38 ` Harald Nordgren
2026-07-14 9:04 ` Harald Nordgren
2026-07-14 12:36 ` Ben Knoble
2026-07-14 18:41 ` Junio C Hamano
2026-07-15 15:16 ` [PATCH v9 " Harald Nordgren via GitGitGadget
2026-07-15 15:16 ` [PATCH v9 1/5] history: extract helper for a commit's parent tree Harald Nordgren via GitGitGadget
2026-07-15 15:16 ` [PATCH v9 2/5] history: give commit_tree_ext a message template Harald Nordgren via GitGitGadget
2026-07-15 15:16 ` [PATCH v9 3/5] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-07-18 8:52 ` Matt Hunter
2026-07-18 9:28 ` Harald Nordgren
2026-07-15 15:16 ` [PATCH v9 4/5] sequencer: share the squash message marker helpers and flags Harald Nordgren via GitGitGadget
2026-07-15 15:16 ` [PATCH v9 5/5] history: re-edit a squash with every message Harald Nordgren via GitGitGadget
2026-07-18 8:52 ` Matt Hunter
2026-07-18 9:36 ` Harald Nordgren
2026-07-20 8:26 ` Harald Nordgren via GitGitGadget [this message]
2026-07-20 8:27 ` [PATCH v10 1/5] history: extract helper for a commit's parent tree Harald Nordgren via GitGitGadget
2026-07-20 8:27 ` [PATCH v10 2/5] history: give commit_tree_ext a message template Harald Nordgren via GitGitGadget
2026-07-20 8:27 ` [PATCH v10 3/5] history: add squash subcommand to fold a range Harald Nordgren via GitGitGadget
2026-07-20 8:27 ` [PATCH v10 4/5] sequencer: share the squash message marker helpers and flags Harald Nordgren via GitGitGadget
2026-07-20 8:27 ` [PATCH v10 5/5] history: re-edit a squash with every message Harald Nordgren via GitGitGadget
2026-07-21 1:33 ` [PATCH v10 0/5] history: add squash subcommand to fold a range Matt Hunter
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2337.v10.git.git.1784536024.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=ben.knoble@gmail.com \
--cc=git@vger.kernel.org \
--cc=haraldnordgren@gmail.com \
--cc=m@lfurio.us \
--cc=phillip.wood123@gmail.com \
--cc=ps@pks.im \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox