git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v6 0/4] history: add squash subcommand to fold a range

From
Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>
Date
Jun 28, 2026, 08:29 UTC
Message-ID
<pull.2337.v6.git.git.1782635349.gitgitgadget@gmail.com>
In-Reply-To
<pull.2337.v5.git.git.1782338102.gitgitgadget@gmail.com>
Adds git history squash <revision-range> to fold a range of commits.
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 (4):
  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
  history: re-edit a squash with every message
 Documentation/config/advice.adoc |   4 +
 Documentation/git-history.adoc   |  29 ++
 advice.c                         |   1 +
 advice.h                         |   1 +
 builtin/history.c                | 357 +++++++++++++++++---
 t/meson.build                    |   1 +
 t/t3455-history-squash.sh        | 550 +++++++++++++++++++++++++++++++
 7 files changed, 905 insertions(+), 38 deletions(-)
 create mode 100755 t/t3455-history-squash.sh
base-commit: 6c3d7b73556db708feb3b16232fab1efc4353428
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2337%2FHaraldNordgren%2Frebase-fixup-fold-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2337/HaraldNordgren/rebase-fixup-fold-v6
Pull-Request: https://github.com/git/git/pull/2337
Range-diff vs v5:
 1:  0f1ae9b05a = 1:  fea6b79e60 history: extract helper for a commit's parent tree
 2:  a97ffab1e6 = 2:  e2674e0bc4 history: give commit_tree_ext a message template
 3:  04e18ef979 ! 3:  811e393ab4 history: add squash subcommand to fold a range
     @@ Commit message
      
          Add "git history squash <revision-range>" to do this directly. It folds
          every commit in the range into the oldest one, keeping that commit's
     -    message and authorship and taking the tree of the newest commit, so the
     -    range collapses into a single commit. Commits above the range are
     -    replayed on top of the result.
     +    message and authorship and taking the tree of the newest commit, then
     +    replays the commits above the range on top. fixup!, squash! and amend!
     +    commits are folded like any other and are not interpreted, so the
     +    squashed message comes from the oldest commit, or from an editor with
     +    --reedit-message.
      
     -    The range is given as <base>..<tip>, so "git history squash @~3.."
     -    folds the three most recent commits and "git history squash @~5..@~2"
     -    squashes an interior range. A merge inside the range is folded like any
     -    other commit, but the range must have a single base, so a range with
     -    more than one entry point is rejected.
     -
     -    The folded commits leave the history, so by default the command refuses
     -    when another ref points at one of them. Use "--update-refs=head" to
     -    rewrite only the current branch and leave those refs untouched.
     +    The range is read like the arguments to "git rev-list", so several
     +    arguments such as "@~3.. ^topic" are allowed. A merge inside the range
     +    is folded when its other parent is reachable from the base, otherwise
     +    the range has more than one base and is rejected. By default the command
     +    also refuses when a ref points at a commit that the fold would discard.
     +    Use --update-refs=head to rewrite only the current branch instead.
      
          Inspired-by: Sergey Chernov <serega.morph@gmail.com>
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
     @@ Documentation/git-history.adoc: linkgit:gitglossary[7].
      +the commit just below the oldest commit to squash. For example, `git
      +history squash @~3..` folds the three most recent commits into one, and
      +`git history squash @~5..@~2` squashes an interior range while leaving
     -+the two newest commits in place.
     ++the two newest commits in place. _<revision-range>_ is read like the
     ++arguments to linkgit:git-rev-list[1], so several arguments may be given,
     ++for example `@~3.. ^topic` to additionally exclude what is already on
     ++`topic`.
      ++
      +The oldest commit's message and authorship are preserved by default,
      +unless you specify `--reedit-message`. A merge commit inside the range is
     @@ builtin/history.c: out:
      + * but the range must have a single base and must not reach a root commit.
      + */
      +static int resolve_squash_range(struct repository *repo,
     -+				const char *range,
     ++				const char **argv,
      +				struct commit **base_out,
      +				struct commit **oldest_out,
      +				struct commit **tip_out,
     @@ builtin/history.c: out:
      +{
      +	struct rev_info revs;
      +	struct commit *commit, *base = NULL, *oldest = NULL, *tip = NULL;
     ++	struct commit_list *boundaries = NULL, *b;
      +	struct strvec args = STRVEC_INIT;
      +	size_t i;
      +	int ret;
     @@ builtin/history.c: out:
      +	strvec_push(&args, "--topo-order");
      +	strvec_push(&args, "--boundary");
      +	strvec_push(&args, "--ancestry-path");
     -+	strvec_push(&args, range);
     ++	strvec_pushv(&args, argv);
      +	setup_revisions_from_strvec(&args, &revs, NULL);
      +	if (args.nr != 1) {
     -+		ret = error(_("'%s' does not name a revision range"), range);
     ++		ret = error(_("unrecognized argument: %s"), args.v[1]);
      +		goto out;
      +	}
      +
      +	/*
     -+	 * A squash needs a base to reparent onto, so the argument has to
     -+	 * exclude something, as in "<base>..<tip>". A single revision has no
     -+	 * such bottom commit and cannot be squashed.
     ++	 * A squash needs a base to reparent onto, so the range has to exclude
     ++	 * something, as in "<base>..<tip>". A revision range with no such
     ++	 * bottom commit cannot be squashed.
      +	 */
      +	for (i = 0; i < revs.cmdline.nr; i++)
      +		if (revs.cmdline.rev[i].flags & UNINTERESTING)
      +			break;
      +	if (i == revs.cmdline.nr) {
     -+		ret = error(_("'%s' is not a '<base>..<tip>' range"), range);
     ++		ret = error(_("not a '<base>..<tip>' revision range"));
      +		goto out;
      +	}
      +
     @@ builtin/history.c: out:
      +
      +	while ((commit = get_revision(&revs))) {
      +		if (commit->object.flags & BOUNDARY) {
     -+			if (base) {
     -+				ret = error(_("range '%s' has more than one base; "
     -+					      "cannot squash"), range);
     -+				goto out;
     -+			}
     -+			base = commit;
     ++			commit_list_insert(commit, &boundaries);
      +			continue;
      +		}
      +		if (!oldest)
     @@ builtin/history.c: out:
      +	}
      +
      +	if (!oldest) {
     -+		ret = error(_("the range '%s' is empty"), range);
     ++		ret = error(_("the revision range is empty"));
     ++		goto out;
     ++	}
     ++
     ++	if (oldest == tip) {
     ++		ret = error(_("the revision range holds a single commit; "
     ++			      "nothing to squash"));
      +		goto out;
      +	}
      +
     -+	if (!base)
     -+		BUG("a non-empty range must have a boundary commit");
     ++	if (!oldest->parents)
     ++		BUG("an in-range commit must have a parent");
     ++	base = oldest->parents->item;
     ++
     ++	/*
     ++	 * A boundary other than the base is an in-range commit reaching a
     ++	 * commit outside the range, so the range has more than one base.
     ++	 */
     ++	for (b = boundaries; b; b = b->next) {
     ++		if (b->item != base) {
     ++			ret = error(_("the revision range has more than one base; "
     ++				      "cannot squash"));
     ++			goto out;
     ++		}
     ++	}
      +
      +	*base_out = base;
      +	*oldest_out = oldest;
     @@ builtin/history.c: out:
      +	ret = 0;
      +
      +out:
     ++	commit_list_free(boundaries);
      +	reset_revision_walk();
      +	release_revisions(&revs);
      +	strvec_clear(&args);
     @@ builtin/history.c: out:
      +	int ret;
      +
      +	argc = parse_options(argc, argv, prefix, options, usage, 0);
     -+	if (argc != 1) {
     -+		ret = error(_("command expects a single revision range"));
     ++	if (!argc) {
     ++		ret = error(_("command expects a revision range"));
      +		goto out;
      +	}
      +	repo_config(repo, git_default_config, NULL);
     @@ builtin/history.c: out:
      +	if (action == REF_ACTION_DEFAULT)
      +		action = REF_ACTION_BRANCHES;
      +
     -+	ret = resolve_squash_range(repo, argv[0], &base, &oldest, &tip,
     ++	ret = resolve_squash_range(repo, argv, &base, &oldest, &tip,
      +				   &interior);
      +	if (ret < 0)
      +		goto out;
     @@ t/t3455-history-squash.sh (new)
      +
      +test_expect_success 'errors on missing range argument' '
      +	test_must_fail git history squash 2>err &&
     -+	test_grep "command expects a single revision range" err
     -+'
     -+
     -+test_expect_success 'errors on too many arguments' '
     -+	test_must_fail git history squash start.. HEAD 2>err &&
     -+	test_grep "command expects a single revision range" err
     ++	test_grep "expects a revision range" err
      +'
      +
      +test_expect_success 'errors on an empty range' '
      +	test_must_fail git history squash HEAD..HEAD 2>err &&
     -+	test_grep "the range .* is empty" err
     ++	test_grep "the revision range is empty" err
      +'
      +
      +test_expect_success 'errors on a single revision that is not a range' '
      +	test_must_fail git history squash HEAD 2>err &&
     -+	test_grep "is not a .*range" err &&
     ++	test_grep "not a .*range" err &&
      +	test_must_fail git history squash HEAD~1 2>err &&
     -+	test_grep "is not a .*range" err
     ++	test_grep "not a .*range" err
     ++'
     ++
     ++test_expect_success 'errors on a range holding a single commit' '
     ++	git reset --hard three &&
     ++	head_before=$(git rev-parse HEAD) &&
     ++
     ++	test_must_fail git history squash "HEAD^!" 2>err &&
     ++	test_grep "single commit; nothing to squash" err &&
     ++	test_cmp_rev "$head_before" HEAD
     ++'
     ++
     ++test_expect_success 'accepts multiple revision arguments with an exclusion' '
     ++	git reset --hard three &&
     ++	git branch -f keep HEAD~2 &&
     ++	tip_tree=$(git rev-parse HEAD^{tree}) &&
     ++
     ++	git history squash start..HEAD ^keep &&
     ++
     ++	git log --format="%s" start..HEAD >actual &&
     ++	cat >expect <<-\EOF &&
     ++	two
     ++	one
     ++	EOF
     ++	test_cmp expect actual &&
     ++	test_cmp_rev keep HEAD~1 &&
     ++	test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
     ++
     ++	git branch -D keep
     ++'
     ++
     ++test_expect_success 'squashes a branch the current branch is not on' '
     ++	git reset --hard three &&
     ++	main=$(git symbolic-ref --short HEAD) &&
     ++	head_before=$(git rev-parse HEAD) &&
     ++	git checkout -b off-history start &&
     ++	test_commit --no-tag off-one off a &&
     ++	test_commit --no-tag off-two off b &&
     ++	git checkout "$main" &&
     ++
     ++	git history squash start..off-history &&
     ++
     ++	git rev-list --count start..off-history >count &&
     ++	echo 1 >expect &&
     ++	test_cmp expect count &&
     ++	test_cmp_rev "$head_before" HEAD &&
     ++
     ++	git branch -D off-history
      +'
      +
      +test_expect_success 'squashes a range into a single commit without changing the tree' '
     @@ t/t3455-history-squash.sh (new)
      +	test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
      +'
      +
     -+test_expect_success 'squashing a single-commit range replays the rest' '
     -+	git reset --hard three &&
     -+	tip_tree=$(git rev-parse HEAD^{tree}) &&
     -+
     -+	git history squash start..@~2 &&
     -+
     -+	git log --format="%s" start..HEAD >actual &&
     -+	cat >expect <<-\EOF &&
     -+	three
     -+	two
     -+	one
     -+	EOF
     -+	test_cmp expect actual &&
     -+	test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
     -+'
      +
      +test_expect_success 'reuses the message of a fixup! commit in the range' '
      +	git reset --hard start &&
     @@ t/t3455-history-squash.sh (new)
      +
      +test_expect_success 'squashes a range whose internal merge has a single base' '
      +	git reset --hard start &&
     ++	main=$(git symbolic-ref --short HEAD) &&
      +	test_commit --no-tag before-side file b &&
      +	git checkout -b inner-side &&
      +	test_commit --no-tag on-inner-side inner x &&
     -+	git checkout - &&
     ++	git checkout "$main" &&
      +	test_commit --no-tag after-side file c &&
      +	git merge --no-ff -m merge inner-side &&
      +	git branch -D inner-side &&
     @@ t/t3455-history-squash.sh (new)
      +
      +test_expect_success 'folds a merge of a branch that forked at the base' '
      +	git reset --hard start &&
     ++	main=$(git symbolic-ref --short HEAD) &&
      +	git checkout -b base-fork-side &&
      +	test_commit --no-tag base-fork-side side x &&
     -+	git checkout - &&
     ++	git checkout "$main" &&
      +	test_commit --no-tag base-fork-main file b &&
      +	git merge --no-ff -m "merge base-fork-side" base-fork-side &&
      +	git branch -D base-fork-side &&
     @@ t/t3455-history-squash.sh (new)
      +	test_path_is_file side
      +'
      +
     ++test_expect_success 'refuses a merge whose other parent is outside the range' '
     ++	git reset --hard start &&
     ++	main=$(git symbolic-ref --short HEAD) &&
     ++	git checkout -b outside-parent &&
     ++	test_commit --no-tag outside-parent outside x &&
     ++	git checkout "$main" &&
     ++	test_commit --no-tag outside-main file b &&
     ++	base=$(git rev-parse HEAD) &&
     ++	test_commit --no-tag outside-mid file c &&
     ++	git merge --no-ff -m "merge outside-parent" outside-parent &&
     ++	git branch -D outside-parent &&
     ++	merged=$(git rev-parse HEAD) &&
     ++
     ++	test_must_fail git history squash "$base.." 2>err &&
     ++	test_grep "more than one base" err &&
     ++	test_cmp_rev "$merged" HEAD
     ++'
     ++
      +test_expect_success 'folds a range whose tip is a merge commit' '
      +	git reset --hard start &&
     ++	main=$(git symbolic-ref --short HEAD) &&
      +	test_commit --no-tag tipmerge-base file b &&
      +	git checkout -b tipmerge-side &&
      +	test_commit --no-tag tipmerge-side side x &&
     -+	git checkout - &&
     ++	git checkout "$main" &&
      +	test_commit --no-tag tipmerge-main file c &&
      +	git merge --no-ff -m "merge tipmerge-side" tipmerge-side &&
      +	git branch -D tipmerge-side &&
     @@ t/t3455-history-squash.sh (new)
      +
      +test_expect_success 'folds a range whose base is a merge commit' '
      +	git reset --hard start &&
     ++	main=$(git symbolic-ref --short HEAD) &&
      +	git checkout -b basemerge-side &&
      +	test_commit --no-tag basemerge-side side x &&
     -+	git checkout - &&
     ++	git checkout "$main" &&
      +	test_commit --no-tag basemerge-main file b &&
      +	git merge --no-ff -m "merge basemerge-side" basemerge-side &&
      +	git branch -D basemerge-side &&
     @@ t/t3455-history-squash.sh (new)
      +
      +test_expect_success 'refuses to squash a range with more than one base' '
      +	git reset --hard start &&
     -+	head_before=$(git rev-parse HEAD) &&
     ++	main=$(git symbolic-ref --short HEAD) &&
      +	git checkout -b forked-before &&
      +	test_commit forked-side fside x &&
     -+	git checkout - &&
     -+	test_commit forked-main file b &&
     ++	git checkout "$main" &&
     ++	test_commit forked-base file b &&
     ++	base=$(git rev-parse HEAD) &&
     ++	test_commit forked-main file c &&
      +	git merge --no-ff -m merge forked-before &&
      +	merged=$(git rev-parse HEAD) &&
      +
     -+	test_must_fail git history squash forked-main.. 2>err &&
     ++	test_must_fail git history squash "$base.." 2>err &&
      +	test_grep "more than one base" err &&
      +	test_cmp_rev "$merged" HEAD
      +'
      +
      +test_expect_success 'folds a range with two interior merges' '
      +	git reset --hard start &&
     ++	main=$(git symbolic-ref --short HEAD) &&
      +	test_commit --no-tag two-merge-a file a1 &&
      +	git checkout -b two-merge-s1 &&
      +	test_commit --no-tag two-merge-s1 s1 x &&
     -+	git checkout - &&
     ++	git checkout "$main" &&
      +	git merge --no-ff -m "merge s1" two-merge-s1 &&
      +	test_commit --no-tag two-merge-b file b1 &&
      +	git checkout -b two-merge-s2 &&
      +	test_commit --no-tag two-merge-s2 s2 y &&
     -+	git checkout - &&
     ++	git checkout "$main" &&
      +	git merge --no-ff -m "merge s2" two-merge-s2 &&
      +	git branch -D two-merge-s1 two-merge-s2 &&
      +	tip_tree=$(git rev-parse HEAD^{tree}) &&
     @@ t/t3455-history-squash.sh (new)
      +test_expect_success 'refuses when a descendant above the range is a merge' '
      +	git reset --hard start &&
      +	main=$(git symbolic-ref --short HEAD) &&
     -+	test_commit --no-tag desc-base file b &&
     ++	test_commit --no-tag desc-one file b &&
     ++	test_commit --no-tag desc-two file c &&
      +	git tag desc-tip &&
      +	git checkout -b desc-above &&
      +	test_commit --no-tag desc-above above x &&
      +	git checkout "$main" &&
     -+	test_commit --no-tag desc-main file c &&
     ++	test_commit --no-tag desc-main file d &&
      +	git merge --no-ff -m "merge desc-above" desc-above &&
      +	git branch -D desc-above &&
      +	head_before=$(git rev-parse HEAD) &&
 4:  a758e1f084 ! 4:  4edf012b77 history: re-edit a squash with every message
     @@ Commit message
      
          Gather the messages of every commit in the range, oldest first, and use
          them as the editor template when re-editing, mirroring how "git rebase
     -    -i" presents a squash. The combined message is built before the
     -    descendant walk so it is not disturbed by the flags that walk leaves on
     -    the commits.
     +    -i" presents a squash.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
       ## Documentation/git-history.adoc ##
     -@@ Documentation/git-history.adoc: history squash @~3..` folds the three most recent commits into one, and
     - `git history squash @~5..@~2` squashes an interior range while leaving
     - the two newest commits in place.
     +@@ Documentation/git-history.adoc: arguments to linkgit:git-rev-list[1], so several arguments may be given,
     + for example `@~3.. ^topic` to additionally exclude what is already on
     + `topic`.
       +
      -The oldest commit's message and authorship are preserved by default,
      -unless you specify `--reedit-message`. A merge commit inside the range is
-- 
gitgitgadget
Previous: Harald NordgrenNext: Harald Nordgren via GitGitGadget
Message 66 of 185 in “rebase: add --fixup to fold a range into its oldest commit”
  1. 0/2 rebase: add --fixup to fold a range into its oldest commitHarald Nordgren via GitGitGadget, Jun 14, 2026
  2. 1/2 t3415: remove prepare-commit-msg hook after useHarald Nordgren via GitGitGadget, Jun 14, 2026
  3. 2/2 rebase: add --fixup-all to fold a rangeHarald Nordgren via GitGitGadget, Jun 14, 2026
  4. Junio C HamanoJun 15, 2026
  5. Harald NordgrenJun 15, 2026
  6. D. Ben KnobleJun 15, 2026
  7. Patrick SteinhardtJun 16, 2026
  8. Harald NordgrenJun 17, 2026
  9. 0/2 rebase: add --squash to fold a range into its first commitHarald Nordgren via GitGitGadget, Jun 15, 2026
  10. 1/2 t3415: remove prepare-commit-msg hook after useHarald Nordgren via GitGitGadget, Jun 15, 2026
  11. 2/2 rebase: add --squash to fold a rangeHarald Nordgren via GitGitGadget, Jun 15, 2026
  12. Phillip WoodJun 16, 2026
  13. Harald NordgrenJun 17, 2026
  14. Phillip WoodJun 17, 2026
  15. 0/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jun 18, 2026
  16. 1/4 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Jun 18, 2026
  17. 2/4 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Jun 18, 2026
  18. 3/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jun 18, 2026
  19. Junio C HamanoJun 18, 2026
  20. Junio C HamanoJun 18, 2026
  21. D. Ben KnobleJun 18, 2026
  22. Patrick SteinhardtJun 19, 2026
  23. 4/4 history: re-edit a squash with every messageHarald Nordgren via GitGitGadget, Jun 18, 2026
  24. D. Ben KnobleJun 18, 2026
  25. Junio C HamanoJun 19, 2026
  26. Patrick SteinhardtJun 19, 2026
  27. Junio C HamanoJun 19, 2026
  28. 0/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jun 21, 2026
  29. 1/4 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Jun 21, 2026
  30. 2/4 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Jun 21, 2026
  31. 3/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jun 21, 2026
  32. 4/4 history: re-edit a squash with every messageHarald Nordgren via GitGitGadget, Jun 21, 2026
  33. Patrick SteinhardtJun 22, 2026
  34. Harald NordgrenJun 23, 2026
  35. 0/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jun 24, 2026
  36. 1/4 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Jun 24, 2026
  37. 2/4 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Jun 24, 2026
  38. 3/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jun 24, 2026
  39. 4/4 history: re-edit a squash with every messageHarald Nordgren via GitGitGadget, Jun 24, 2026
  40. Phillip WoodJun 26, 2026
  41. Harald NordgrenJun 26, 2026
  42. Phillip WoodJun 26, 2026
  43. Junio C HamanoJun 26, 2026
  44. Harald NordgrenJun 26, 2026
  45. Patrick SteinhardtJun 29, 2026
  46. Phillip WoodJun 29, 2026
  47. Junio C HamanoJun 29, 2026
  48. Phillip WoodJul 1, 2026
  49. Harald NordgrenJun 29, 2026
  50. Phillip WoodJun 29, 2026
  51. Harald NordgrenJun 29, 2026
  52. Phillip WoodJun 30, 2026
  53. Harald NordgrenJun 30, 2026
  54. Phillip WoodJul 1, 2026
  55. Junio C HamanoJul 1, 2026
  56. Phillip WoodJul 1, 2026
  57. Junio C HamanoJul 1, 2026
  58. Phillip WoodJul 2, 2026
  59. Matt HunterJun 30, 2026
  60. Harald NordgrenJun 30, 2026
  61. Matt HunterJun 30, 2026
  62. Phillip WoodJun 30, 2026
  63. Patrick SteinhardtJul 2, 2026
  64. Junio C HamanoJul 2, 2026
  65. Harald NordgrenJun 29, 2026
  66. 0/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jun 28, 2026
  67. 1/4 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Jun 28, 2026
  68. 2/4 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Jun 28, 2026
  69. 3/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jun 28, 2026
  70. Junio C HamanoJun 29, 2026
  71. 4/4 history: re-edit a squash with every messageHarald Nordgren via GitGitGadget, Jun 28, 2026
  72. Junio C HamanoJun 29, 2026
  73. Harald NordgrenJun 29, 2026
  74. Junio C HamanoJun 29, 2026
  75. Junio C HamanoJun 29, 2026
  76. 0/5 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jul 6, 2026
  77. 1/5 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Jul 6, 2026
  78. 2/5 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Jul 6, 2026
  79. 3/5 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jul 6, 2026
  80. 4/5 sequencer: extract helpers for the squash message markersHarald Nordgren via GitGitGadget, Jul 6, 2026
  81. 5/5 history: re-edit a squash with every messageHarald Nordgren via GitGitGadget, Jul 6, 2026
  82. Phillip WoodJul 6, 2026
  83. Harald NordgrenJul 7, 2026
  84. Harald NordgrenJul 7, 2026
  85. Phillip WoodJul 7, 2026
  86. Phillip WoodJul 7, 2026
  87. Junio C HamanoJul 6, 2026
  88. 0/5 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jul 10, 2026
  89. 1/5 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Jul 10, 2026
  90. 2/5 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Jul 10, 2026
  91. 3/5 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jul 10, 2026
  92. 4/5 sequencer: share the squash message marker helpers and flagsHarald Nordgren via GitGitGadget, Jul 10, 2026
  93. 5/5 history: re-edit a squash with every messageHarald Nordgren via GitGitGadget, Jul 10, 2026
  94. Matt HunterJul 14, 2026
  95. Harald NordgrenJul 14, 2026
  96. Harald NordgrenJul 14, 2026
  97. Ben KnobleJul 14, 2026
  98. Junio C HamanoJul 14, 2026
  99. 0/5 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jul 15, 2026
  100. 1/5 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Jul 15, 2026
  101. 2/5 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Jul 15, 2026
  102. 3/5 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jul 15, 2026
  103. Matt HunterJul 18, 2026
  104. Harald NordgrenJul 18, 2026
  105. 4/5 sequencer: share the squash message marker helpers and flagsHarald Nordgren via GitGitGadget, Jul 15, 2026
  106. 5/5 history: re-edit a squash with every messageHarald Nordgren via GitGitGadget, Jul 15, 2026
  107. Matt HunterJul 18, 2026
  108. Harald NordgrenJul 18, 2026
  109. 0/5 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jul 20, 2026
  110. 1/5 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Jul 20, 2026
  111. 2/5 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Jul 20, 2026
  112. 3/5 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Jul 20, 2026
  113. Phillip WoodAug 3, 2026
  114. 1/3 fixup! history: add squash subcommand to fold a rangePhillip Wood, Aug 3, 2026
  115. 2/3 fixup! history: add squash subcommand to fold a rangePhillip Wood, Aug 3, 2026
  116. 3/3 fixup! history: add squash subcommand to fold a rangePhillip Wood, Aug 3, 2026
  117. 4/3 fixup! history: add squash subcommand to fold a rangePhillip Wood, Aug 3, 2026
  118. 5/3 fixup! history: add squash subcommand to fold a rangePhillip Wood, Aug 3, 2026
  119. 6/3 fixup! history: add squash subcommand to fold a rangePhillip Wood, Aug 3, 2026
  120. 7/3 fixup! history: add squash subcommand to fold a rangePhillip Wood, Aug 3, 2026
  121. Harald NordgrenAug 3, 2026
  122. Phillip WoodAug 4, 2026
  123. Junio C HamanoAug 4, 2026
  124. Harald NordgrenAug 4, 2026
  125. Harald NordgrenAug 4, 2026
  126. Tuomas AholaAug 7, 2026
  127. Junio C HamanoAug 4, 2026
  128. Phillip WoodAug 7, 2026
  129. Harald NordgrenAug 7, 2026
  130. Kristoffer HaugsbakkAug 6, 2026
  131. 4/5 sequencer: share the squash message marker helpers and flagsHarald Nordgren via GitGitGadget, Jul 20, 2026
  132. 5/5 history: re-edit a squash with every messageHarald Nordgren via GitGitGadget, Jul 20, 2026
  133. Matt HunterJul 21, 2026
  134. 0/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Aug 1, 2026
  135. 1/4 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Aug 1, 2026
  136. 2/4 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Aug 1, 2026
  137. 3/4 sequencer: share the squash message marker helpers and flagsHarald Nordgren via GitGitGadget, Aug 1, 2026
  138. 4/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Aug 1, 2026
  139. 0/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Aug 4, 2026
  140. 1/4 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Aug 4, 2026
  141. 2/4 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Aug 4, 2026
  142. 3/4 sequencer: share the squash message marker helpers and flagsHarald Nordgren via GitGitGadget, Aug 4, 2026
  143. 4/4 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Aug 4, 2026
  144. 0/8 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Aug 7, 2026
  145. 1/8 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Aug 7, 2026
  146. 2/8 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Aug 7, 2026
  147. 3/8 sequencer: share the squash message marker helpers and flagsHarald Nordgren via GitGitGadget, Aug 7, 2026
  148. 4/8 history: add skeleton for squash subcommandHarald Nordgren via GitGitGadget, Aug 7, 2026
  149. 5/8 history: validate squash revision rangesHarald Nordgren via GitGitGadget, Aug 7, 2026
  150. 6/8 history: protect branches when squashing a rangeHarald Nordgren via GitGitGadget, Aug 7, 2026
  151. 7/8 history: create squashed commits without editingHarald Nordgren via GitGitGadget, Aug 7, 2026
  152. Junio C HamanoAug 13, 2026
  153. Junio C HamanoAug 13, 2026
  154. Phillip WoodAug 17, 2026
  155. Junio C HamanoAug 17, 2026
  156. Junio C HamanoAug 17, 2026
  157. Junio C HamanoAug 17, 2026
  158. Junio C HamanoAug 17, 2026
  159. Harald NordgrenAug 17, 2026
  160. Kristoffer HaugsbakkAug 17, 2026
  161. 8/8 history: support editing squashed commit messagesHarald Nordgren via GitGitGadget, Aug 7, 2026
  162. 0/8 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Aug 20, 2026
  163. 1/8 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Aug 20, 2026
  164. 2/8 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Aug 20, 2026
  165. 3/8 sequencer: share the squash message marker helpers and flagsHarald Nordgren via GitGitGadget, Aug 20, 2026
  166. 4/8 history: add skeleton for squash subcommandHarald Nordgren via GitGitGadget, Aug 20, 2026
  167. 5/8 history: validate squash revision rangesHarald Nordgren via GitGitGadget, Aug 20, 2026
  168. 6/8 history: protect branches when squashing a rangeHarald Nordgren via GitGitGadget, Aug 20, 2026
  169. 7/8 history: create squashed commits without editingHarald Nordgren via GitGitGadget, Aug 20, 2026
  170. Junio C HamanoAug 20, 2026
  171. Phillip WoodAug 24, 2026
  172. Junio C HamanoAug 24, 2026
  173. Phillip WoodAug 25, 2026
  174. Junio C HamanoAug 25, 2026
  175. 8/8 history: support editing squashed commit messagesHarald Nordgren via GitGitGadget, Aug 20, 2026
  176. 0/8 history: add squash subcommand to fold a rangeHarald Nordgren via GitGitGadget, Sep 8, 2026
  177. 1/8 history: extract helper for a commit's parent treeHarald Nordgren via GitGitGadget, Sep 8, 2026
  178. 2/8 history: give commit_tree_ext a message templateHarald Nordgren via GitGitGadget, Sep 8, 2026
  179. 3/8 sequencer: share the squash message marker helpers and flagsHarald Nordgren via GitGitGadget, Sep 8, 2026
  180. 4/8 history: add skeleton for squash subcommandHarald Nordgren via GitGitGadget, Sep 8, 2026
  181. Junio C HamanoSep 8, 2026
  182. 5/8 history: validate squash revision rangesHarald Nordgren via GitGitGadget, Sep 8, 2026
  183. 6/8 history: protect branches when squashing a rangeHarald Nordgren via GitGitGadget, Sep 8, 2026
  184. 8/8 history: support editing squashed commit messagesHarald Nordgren via GitGitGadget, Sep 8, 2026
  185. 7/8 history: create squashed commits without editingHarald Nordgren via GitGitGadget, Sep 8, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.