Git development
 help / color / mirror / Atom feed
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

  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