From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v2 0/2] rebase: add --squash to fold a range into its first commit
Date: Mon, 15 Jun 2026 08:37:03 +0000 [thread overview]
Message-ID: <pull.2337.v2.git.git.1781512625.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2337.git.git.1781465141.gitgitgadget@gmail.com>
Rename to rebase --squash.
Harald Nordgren (2):
t3415: remove prepare-commit-msg hook after use
rebase: add --squash to fold a range
Documentation/git-rebase.adoc | 11 ++++
builtin/rebase.c | 16 ++++-
sequencer.c | 24 ++++++-
sequencer.h | 2 +-
t/t3415-rebase-autosquash.sh | 118 ++++++++++++++++++++++++++++++++++
5 files changed, 166 insertions(+), 5 deletions(-)
base-commit: ea97ad8d017de0c9037451a78008a0fd60abea0c
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2337%2FHaraldNordgren%2Frebase-fixup-fold-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2337/HaraldNordgren/rebase-fixup-fold-v2
Pull-Request: https://github.com/git/git/pull/2337
Range-diff vs v1:
1: c55b9cd6f7 = 1: c55b9cd6f7 t3415: remove prepare-commit-msg hook after use
2: bd1bc62aa8 ! 2: 22d4276ff5 rebase: add --fixup-all to fold a range
@@ Metadata
Author: Harald Nordgren <haraldnordgren@gmail.com>
## Commit message ##
- rebase: add --fixup-all to fold a range
+ rebase: add --squash to fold a range
Folding a series of commits into one required either an interactive
rebase where each commit after the first was hand-edited to "fixup", or
a "git reset --soft" to the merge base followed by "git commit --amend".
- Add "git rebase --autosquash --fixup-all [<upstream>]" to do this
- directly. It keeps the first commit in the range as a "pick" and turns
- every later commit into a "fixup", so the whole range collapses into a
- single commit that reuses the first commit's message. With no <upstream>
- argument the range is "@{upstream}..HEAD", folding all unpushed commits
- into one.
+ Add "git rebase --squash [<upstream>]" to do this directly. It keeps
+ the first commit in the range as a "pick" and turns every later commit
+ into a "fixup", so the whole range collapses into a single commit that
+ reuses the first commit's message. With no <upstream> argument the range
+ is "@{upstream}..HEAD", folding all unpushed commits into one.
- Fold the commits in their original order, so that any fixup!/squash!
- commits already present in the range are folded in as well. Allow the
- flag only together with --autosquash, and reject --rebase-merges since a
- merge commit cannot be folded into another commit.
+ The option implies the merge backend, so it works on its own without
+ --autosquash. Fold the commits in their original order, so that any
+ fixup!/squash! commits already present in the range are folded in as
+ well. Reject --rebase-merges since a merge commit cannot be folded into
+ another commit.
+ Inspired-by: Sergey Chernov <serega.morph@gmail.com>
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
## Documentation/git-rebase.adoc ##
@@ Documentation/git-rebase.adoc: option can be used to override that setting.
+
See also INCOMPATIBLE OPTIONS below.
-+--fixup-all::
-+ Valid only when used with `--autosquash`. Keep the first commit in
-+ the range as a `pick` and change every later commit to a `fixup`, so
-+ the whole range is folded into a single commit that reuses the first
-+ commit's message. With no `<upstream>` argument this folds all commits
-+ since `@{upstream}` into one. The commits are folded in their original
-+ order, so any `fixup!`/`squash!` commits already in the range are folded
-+ in as well. Cannot be combined with `--rebase-merges`, as a merge
-+ commit cannot be folded into another commit.
++--squash::
++ Keep the first commit in the range as a `pick` and change every later
++ commit to a `fixup`, so the whole range is folded into a single commit
++ that reuses the first commit's message. With no `<upstream>` argument
++ this folds all commits since `@{upstream}` into one. The commits are
++ folded in their original order, so any `fixup!`/`squash!` commits
++ already in the range are folded in as well. Cannot be combined with
++ `--rebase-merges`, as a merge commit cannot be folded into another
++ commit.
+
--autostash::
--no-autostash::
@@ Documentation/git-rebase.adoc: are incompatible with the following options:
* --strategy
* --strategy-option
* --autosquash
-+ * --fixup-all
++ * --squash
* --rebase-merges
* --interactive
* --exec
@@ builtin/rebase.c: struct rebase_options {
int allow_rerere_autoupdate;
int keep_empty;
int autosquash;
-+ int fixup_all;
++ int squash;
char *gpg_sign_opt;
int autostash;
int committer_date_is_author_date;
@@ builtin/rebase.c: static int do_interactive_rebase(struct rebase_options *opts,
shortrevisions, opts->onto_name, opts->onto,
&opts->orig_head->object.oid, &opts->exec,
- opts->autosquash, opts->update_refs, &todo_list);
-+ opts->autosquash, opts->fixup_all, opts->update_refs,
++ opts->autosquash, opts->squash, opts->update_refs,
+ &todo_list);
cleanup:
@@ builtin/rebase.c: int cmd_rebase(int argc,
OPT_BOOL(0, "autosquash", &options.autosquash,
N_("move commits that begin with "
"squash!/fixup! under -i")),
-+ OPT_BOOL(0, "fixup-all", &options.fixup_all,
++ OPT_BOOL(0, "squash", &options.squash,
+ N_("fold all commits in the range into the first one")),
OPT_BOOL(0, "update-refs", &options.update_refs,
N_("update branches that point to commits "
"that are being rebased")),
+@@ builtin/rebase.c: int cmd_rebase(int argc,
+ if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
+ (options.action != ACTION_NONE) ||
+ (options.exec.nr > 0) ||
+- options.autosquash == 1) {
++ options.autosquash == 1 ||
++ options.squash) {
+ allow_preemptive_ff = 0;
+ }
+ if (options.committer_date_is_author_date || options.ignore_date)
@@ builtin/rebase.c: int cmd_rebase(int argc,
options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
-+ if (options.fixup_all && options.autosquash != 1)
-+ die(_("--fixup-all requires --autosquash"));
-+
-+ if (options.fixup_all && options.rebase_merges)
++ if (options.squash && options.rebase_merges)
+ die(_("options '%s' and '%s' cannot be used together"),
-+ "--fixup-all", "--rebase-merges");
++ "--squash", "--rebase-merges");
++
++ if (options.squash)
++ imply_merge(&options, "--squash");
+
if (options.autosquash == 1) {
imply_merge(&options, "--autosquash");
@@ sequencer.c: static int todo_list_add_update_ref_commands(struct todo_list *todo
struct commit *onto, const struct object_id *orig_head,
struct string_list *commands, unsigned autosquash,
- unsigned update_refs,
-+ unsigned fixup_all, unsigned update_refs,
++ unsigned squash, unsigned update_refs,
struct todo_list *todo_list)
{
char shortonto[GIT_MAX_HEXSZ + 1];
@@ sequencer.c: int complete_action(struct repository *r, struct replay_opts *opts,
return -1;
- if (autosquash && todo_list_rearrange_squash(todo_list))
-+ if (fixup_all)
++ if (squash)
+ todo_list_fixup_all_but_first(todo_list);
+ else if (autosquash && todo_list_rearrange_squash(todo_list))
return -1;
@@ sequencer.h: int complete_action(struct repository *r, struct replay_opts *opts,
struct commit *onto, const struct object_id *orig_head,
struct string_list *commands, unsigned autosquash,
- unsigned update_refs,
-+ unsigned fixup_all, unsigned update_refs,
++ unsigned squash, unsigned update_refs,
struct todo_list *todo_list);
int todo_list_rearrange_squash(struct todo_list *todo_list);
@@ t/t3415-rebase-autosquash.sh: test_expect_success 'pick and fixup respect commit
test_commit_message HEAD -m "something"
'
-+test_expect_success '--fixup-all folds the range into the first commit' '
++test_expect_success '--squash folds the range into the first commit' '
+ git reset --hard base &&
+ test_commit --no-tag fold1 file_fold a &&
+ test_commit --no-tag fold2 file_fold b &&
+ test_commit --no-tag fold3 file_fold c &&
-+ git rebase --autosquash --fixup-all HEAD~3 &&
++ git rebase --squash HEAD~3 &&
+ test_cmp_rev base HEAD~1 &&
+ test_commit_message HEAD -m "fold1" &&
+ echo c >expect &&
+ test_cmp expect file_fold
+'
+
-+test_expect_success '--fixup-all folds smoothly when a fixup! commit is in the series' '
++test_expect_success '--squash folds smoothly when a fixup! commit is in the series' '
+ git reset --hard base &&
+ test_commit --no-tag foldA file_fold a &&
+ test_commit --no-tag foldB file_fold b &&
+ git commit --allow-empty --fixup HEAD~1 &&
-+ git rebase --autosquash --fixup-all HEAD~3 &&
++ git rebase --squash HEAD~3 &&
+ test_cmp_rev base HEAD~1 &&
+ test_commit_message HEAD -m "foldA" &&
+ echo b >expect &&
+ test_cmp expect file_fold
+'
+
-+test_expect_success '--fixup-all picks the first commit even if it is a fixup!' '
++test_expect_success '--squash picks the first commit even if it is a fixup!' '
+ git reset --hard base &&
+ test_commit --no-tag fixupbase file_fix a &&
+ git commit --allow-empty --fixup HEAD &&
+ test_commit --no-tag fixuptail file_fix b &&
-+ git rebase --autosquash --fixup-all HEAD~3 &&
++ git rebase --squash HEAD~3 &&
+ test_cmp_rev base HEAD~1 &&
+ echo b >expect &&
+ test_cmp expect file_fix
+'
+
-+test_expect_success '--fixup-all with a single commit in range is a no-op' '
++test_expect_success '--squash with a single commit in range is a no-op' '
+ git reset --hard base &&
+ test_commit --no-tag solo file_solo a &&
+ git rev-parse HEAD >expect &&
-+ git rebase --autosquash --fixup-all HEAD~1 &&
++ git rebase --squash HEAD~1 &&
+ git rev-parse HEAD >actual &&
+ test_cmp expect actual
+'
+
-+test_expect_success '--fixup-all with an empty range succeeds' '
++test_expect_success '--squash with an empty range succeeds' '
+ git reset --hard base &&
-+ git rebase --autosquash --fixup-all HEAD &&
++ git rebase --squash HEAD &&
+ test_cmp_rev base HEAD
+'
+
-+test_expect_success '--fixup-all skips a dropped commit in the range' '
++test_expect_success '--squash skips a dropped commit in the range' '
+ git reset --hard base &&
+ test_commit --no-tag fixdrop1 file_drop a &&
+ git commit --allow-empty -m "empty in the middle" &&
+ test_commit --no-tag fixdrop3 file_drop b &&
-+ git rebase --autosquash --empty=drop --fixup-all HEAD~3 &&
++ git rebase --squash --empty=drop HEAD~3 &&
+ test_cmp_rev base HEAD~1 &&
+ test_commit_message HEAD -m "fixdrop1" &&
+ echo b >expect &&
+ test_cmp expect file_drop
+'
+
-+test_expect_success '--fixup-all folds a merge commit in the middle of the range' '
++test_expect_success '--squash folds a merge commit in the middle of the range' '
+ git reset --hard base &&
+ test_commit --no-tag mid-first &&
+ git checkout -b mid-side &&
@@ t/t3415-rebase-autosquash.sh: test_expect_success 'pick and fixup respect commit
+ git checkout - &&
+ git merge --no-ff -m "merge mid-side" mid-side &&
+ test_commit --no-tag mid-last &&
-+ git rebase --autosquash --fixup-all base &&
++ git rebase --squash base &&
+ test_cmp_rev base HEAD~1 &&
+ test_commit_message HEAD -m "mid-first" &&
+ test_path_is_file mid-merged.t
+'
+
-+test_expect_success '--fixup-all keeps the first flattened commit when a merge sorts first' '
++test_expect_success '--squash keeps the first flattened commit when a merge sorts first' '
+ git reset --hard base &&
+ git checkout -b head-side &&
+ test_commit --no-tag head-merged &&
+ git checkout - &&
+ git merge --no-ff -m "merge head-side" head-side &&
+ test_commit --no-tag head-last &&
-+ git rebase --autosquash --fixup-all base &&
++ git rebase --squash base &&
+ test_cmp_rev base HEAD~1 &&
+ test_commit_message HEAD -m "head-merged" &&
+ test_path_is_file head-merged.t
+'
+
-+test_expect_success '--fixup-all requires --autosquash' '
++test_expect_success '--squash takes precedence over --autosquash' '
+ git reset --hard base &&
-+ test_must_fail git rebase --fixup-all HEAD~1 2>err &&
-+ test_grep "fixup-all requires --autosquash" err &&
-+ test_must_fail git rebase --no-autosquash --fixup-all HEAD~1 2>err &&
-+ test_grep "fixup-all requires --autosquash" err
++ test_commit --no-tag combo-first &&
++ test_commit --no-tag combo-mid &&
++ git commit --allow-empty --fixup HEAD~1 &&
++ test_commit --no-tag combo-last &&
++ git rebase --autosquash --squash base &&
++ test_cmp_rev base HEAD~1 &&
++ test_commit_message HEAD -m "combo-first"
++'
++
++test_expect_success '--squash folds the range with rebase.autosquash set' '
++ test_config rebase.autosquash true &&
++ git reset --hard base &&
++ test_commit --no-tag cfg-first &&
++ test_commit --no-tag cfg-last &&
++ git rebase --squash base &&
++ test_cmp_rev base HEAD~1 &&
++ test_commit_message HEAD -m "cfg-first"
+'
+
-+test_expect_success '--fixup-all and --rebase-merges cannot be combined' '
++test_expect_success '--squash and --rebase-merges cannot be combined' '
+ git reset --hard base &&
-+ test_must_fail git rebase --autosquash --rebase-merges \
-+ --fixup-all HEAD~1 2>err &&
++ test_must_fail git rebase --rebase-merges --squash HEAD~1 2>err &&
+ test_grep "cannot be used together" err &&
+ test_path_is_missing .git/rebase-merge
+'
--
gitgitgadget
next prev parent reply other threads:[~2026-06-15 8:37 UTC|newest]
Thread overview: 8+ 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 8:37 ` Harald Nordgren via GitGitGadget [this message]
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
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.v2.git.git.1781512625.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=haraldnordgren@gmail.com \
/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