All of lore.kernel.org
 help / color / mirror / Atom feed
From: Phillip Wood <phillip.wood123@gmail.com>
To: Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>,
	git@vger.kernel.org
Cc: Harald Nordgren <haraldnordgren@gmail.com>,
	"D. Ben Knoble" <ben.knoble@gmail.com>,
	Patrick Steinhardt <ps@pks.im>,
	Junio C Hamano <gitster@pobox.com>
Subject: Re: [PATCH v2 0/2] rebase: add --squash to fold a range into its first commit
Date: Tue, 16 Jun 2026 11:10:27 +0100	[thread overview]
Message-ID: <d55b6600-50f3-4e81-a6bf-d270cd7abd2d@gmail.com> (raw)
In-Reply-To: <pull.2337.v2.git.git.1781512625.gitgitgadget@gmail.com>

Hi Harald

On 15/06/2026 09:37, Harald Nordgren via GitGitGadget wrote:
> Rename to rebase --squash.

Please include the original cover letter as well so people who have not 
read the previous version know what the series is about.

I agree that git-history might be a better place for this but I'm not 
opposed to adding a --squash option to git-rebase. However I do think we 
need to think about the implementation - picking consecutive commits 
when we want to squash them together is inefficient and if we're 
changing the base risks the user having to stop to resolve conflicts 
multiple times. Regardless of whether this ends up in git-rebase or 
git-history I think the implementation should cherry-pick the whole 
commit range by doing the equivalent of

   git merge-tree --merge-base $(git merge-base HEAD @{upstream}) \
                  @{upstream} HEAD

We should also let the user edit the commit message to reflect the 
changes that are being squashed in. We should think about what support 
we want for "amend!" commits that replace the commit message when rebasing.

Thanks

Phillip

> 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
>        +'
> 


      parent reply	other threads:[~2026-06-16 10:10 UTC|newest]

Thread overview: 11+ 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-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   ` Phillip Wood [this message]

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=d55b6600-50f3-4e81-a6bf-d270cd7abd2d@gmail.com \
    --to=phillip.wood123@gmail.com \
    --cc=ben.knoble@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitgitgadget@gmail.com \
    --cc=gitster@pobox.com \
    --cc=haraldnordgren@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.