All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Harald Nordgren <haraldnordgren@gmail.com>,
	Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v7 5/5] history: re-edit a squash with every message
Date: Mon, 06 Jul 2026 08:50:49 +0000	[thread overview]
Message-ID: <615fe4dd3f3ad13ada8a605433f79b5012530018.1783327849.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2337.v7.git.git.1783327849.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

By default "git history squash" reuses the oldest commit's message.
When --reedit-message is given it only reopened that one message, so the
messages of the folded-in commits were lost.

Gather the messages of every commit in the range, oldest first, and build
the same editor template that "git rebase -i" shows for a squash, using
add_squash_combination_header(), add_squash_message_header() and
squash_subject_comment_len(). Only the message text differs, the changes
are always folded in. Following autosquash, a fixup!'s message is
commented out in full under a "will be skipped" header, while a squash! or
amend! keeps its body with only the marker subject commented.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 Documentation/git-history.adoc |  12 +++-
 builtin/history.c              |  73 ++++++++++++++++++++-
 t/t3455-history-squash.sh      | 115 +++++++++++++++++++++++++++++++++
 3 files changed, 196 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc
index 783ddf4a51..f51332e731 100644
--- a/Documentation/git-history.adoc
+++ b/Documentation/git-history.adoc
@@ -117,15 +117,21 @@ like the arguments to linkgit:git-rev-list[1], so several arguments may be
 given, for example `HEAD~3..HEAD ^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
+The oldest commit's message and authorship are preserved by default. With
+`--reedit-message`, an editor opens pre-filled with the messages of all the
+folded commits so you can combine them. A merge commit inside the range is
 folded like any other, but the range must have a single base, so a range
 that reaches more than one entry point (for example a side branch that
 forked before the range and was later merged into it) is rejected.
 +
 Because the oldest commit's message is reused, the range may not begin
 with a `fixup!`, `squash!`, or `amend!` commit, whose target is
-necessarily outside the range.
+necessarily outside the range. The changes from every commit in the range
+are always folded in. Only the message text differs. With
+`--reedit-message` the template mirrors `git rebase -i`: the message of a
+`fixup!` elsewhere in the range is commented out in full, while a
+`squash!` or `amend!` keeps its message body with only the marker subject
+commented, so you can fold the remark into the result.
 +
 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
diff --git a/builtin/history.c b/builtin/history.c
index 63911a493d..8999ba9533 100644
--- a/builtin/history.c
+++ b/builtin/history.c
@@ -1114,6 +1114,68 @@ static int find_interior_ref(const struct reference *ref, void *cb_data)
 	return 0;
 }
 
+static int build_squash_message(struct repository *repo,
+				struct commit *base,
+				struct commit *tip,
+				struct strbuf *out)
+{
+	struct commit_list *commits = NULL, **tail = &commits, *c;
+	struct rev_info revs;
+	struct commit *commit;
+	struct strvec args = STRVEC_INIT;
+	int n = 0, total, ret;
+
+	repo_init_revisions(repo, &revs, NULL);
+	strvec_push(&args, "ignored");
+	strvec_push(&args, "--reverse");
+	strvec_push(&args, "--topo-order");
+	strvec_pushf(&args, "%s..%s", oid_to_hex(&base->object.oid),
+		     oid_to_hex(&tip->object.oid));
+	setup_revisions_from_strvec(&args, &revs, NULL);
+
+	if (prepare_revision_walk(&revs) < 0) {
+		ret = error(_("error preparing revisions"));
+		goto out;
+	}
+
+	while ((commit = get_revision(&revs)))
+		tail = &commit_list_insert(commit, tail)->next;
+	total = commit_list_count(commits);
+
+	for (c = commits; c; c = c->next) {
+		const char *message, *body;
+		size_t commented_len;
+		int skip;
+
+		message = repo_logmsg_reencode(repo, c->item, NULL, NULL);
+		find_commit_subject(message, &body);
+
+		skip = starts_with(body, "fixup! ");
+		commented_len = skip ? strlen(body) :
+			squash_subject_comment_len(body, 1);
+
+		if (!n)
+			add_squash_combination_header(out, total);
+		strbuf_addch(out, '\n');
+		add_squash_message_header(out, ++n, skip);
+		strbuf_addstr(out, "\n\n");
+		strbuf_add_commented_lines(out, body, commented_len, comment_line_str);
+		strbuf_addstr(out, body + commented_len);
+		strbuf_complete_line(out);
+
+		repo_unuse_commit_buffer(repo, c->item, message);
+	}
+
+	ret = 0;
+
+out:
+	commit_list_free(commits);
+	reset_revision_walk();
+	release_revisions(&revs);
+	strvec_clear(&args);
+	return ret;
+}
+
 static int cmd_history_squash(int argc,
 			      const char **argv,
 			      const char *prefix,
@@ -1138,6 +1200,7 @@ static int cmd_history_squash(int argc,
 		OPT_END(),
 	};
 	struct strbuf reflog_msg = STRBUF_INIT;
+	struct strbuf message = STRBUF_INIT;
 	struct oidset interior = OIDSET_INIT;
 	struct commit *base, *oldest, *tip, *rewritten;
 	const struct object_id *base_tree_oid, *tip_tree_oid;
@@ -1181,6 +1244,12 @@ static int cmd_history_squash(int argc,
 		}
 	}
 
+	if (flags & COMMIT_TREE_EDIT_MESSAGE) {
+		ret = build_squash_message(repo, base, tip, &message);
+		if (ret < 0)
+			goto out;
+	}
+
 	ret = setup_revwalk(repo, action, tip, &revs);
 	if (ret < 0)
 		goto out;
@@ -1189,7 +1258,8 @@ static int cmd_history_squash(int argc,
 	tip_tree_oid = &repo_get_commit_tree(repo, tip)->object.oid;
 	commit_list_append(base, &parents);
 
-	ret = commit_tree_ext(repo, "squash", oldest, NULL, parents,
+	ret = commit_tree_ext(repo, "squash", oldest,
+			      message.len ? message.buf : NULL, parents,
 			      base_tree_oid, tip_tree_oid, &rewritten, flags);
 	if (ret < 0) {
 		ret = error(_("failed writing squashed commit"));
@@ -1210,6 +1280,7 @@ static int cmd_history_squash(int argc,
 
 out:
 	strbuf_release(&reflog_msg);
+	strbuf_release(&message);
 	oidset_clear(&interior);
 	commit_list_free(parents);
 	release_revisions(&revs);
diff --git a/t/t3455-history-squash.sh b/t/t3455-history-squash.sh
index 6598649971..1985c83fbb 100755
--- a/t/t3455-history-squash.sh
+++ b/t/t3455-history-squash.sh
@@ -186,6 +186,121 @@ test_expect_success 'preserves authorship of the oldest commit' '
 	test_cmp expect actual
 '
 
+test_expect_success '--reedit-message offers every folded-in message' '
+	git reset --hard start &&
+	echo b >file &&
+	git add file &&
+	git commit -m "re-one subject" -m "re-one body line" &&
+	test_commit --no-tag re-two file c &&
+	test_commit re-three file d &&
+
+	write_script editor <<-\EOF &&
+	cat "$1" >edited &&
+	echo combined >"$1"
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	git history squash --reedit-message start.. &&
+
+	cat >expect <<-EOF &&
+	# This is a combination of 3 commits.
+	# This is the 1st commit message:
+
+	re-one subject
+
+	re-one body line
+
+	# This is the commit message #2:
+
+	re-two
+
+	# This is the commit message #3:
+
+	re-three
+
+	# Please enter the commit message for the squash changes. Lines starting
+	# with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
+	# Changes to be committed:
+	#	modified:   file
+	#
+	EOF
+	test_cmp expect edited &&
+	echo combined >expect &&
+	git log --format="%s" -1 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--reedit-message handles fixup!, squash! and amend! like rebase' '
+	git reset --hard start &&
+	test_commit --no-tag mark-base file b &&
+	printf "fixup! mark-base\n\nfixup body\n" >msg &&
+	echo c >file &&
+	git add file &&
+	git commit -qF msg &&
+	printf "squash! mark-base\n\nsquash remark\n" >msg &&
+	echo d >file &&
+	git add file &&
+	git commit -qF msg &&
+	printf "amend! mark-base\n\namended message\n" >msg &&
+	echo e >file &&
+	git add file &&
+	git commit -qF msg &&
+
+	write_script editor <<-\EOF &&
+	cat "$1" >edited
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	git history squash --reedit-message start.. &&
+
+	cat >expect <<-EOF &&
+	# This is a combination of 4 commits.
+	# This is the 1st commit message:
+
+	mark-base
+
+	# The commit message #2 will be skipped:
+
+	# fixup! mark-base
+	#
+	# fixup body
+
+	# This is the commit message #3:
+
+	# squash! mark-base
+
+	squash remark
+
+	# This is the commit message #4:
+
+	# amend! mark-base
+
+	amended message
+
+	# Please enter the commit message for the squash changes. Lines starting
+	# with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
+	# Changes to be committed:
+	#	modified:   file
+	#
+	EOF
+	test_cmp expect edited &&
+	git log -1 --format="%B" >final &&
+	test_grep ! "fixup body" final &&
+	test_grep "squash remark" final &&
+	test_grep "amended message" final
+'
+
+test_expect_success '--reedit-message aborts on an empty message' '
+	git reset --hard three &&
+	head_before=$(git rev-parse HEAD) &&
+
+	write_script editor <<-\EOF &&
+	>"$1"
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	test_must_fail git history squash --reedit-message start.. &&
+
+	test_cmp_rev "$head_before" HEAD
+'
+
 test_expect_success '--update-refs=head only moves HEAD' '
 	git reset --hard three &&
 	git branch -f other HEAD &&
-- 
gitgitgadget

  parent reply	other threads:[~2026-07-06  8:51 UTC|newest]

Thread overview: 82+ 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             ` Harald Nordgren via GitGitGadget [this message]
2026-07-06 14:06             ` [PATCH v7 0/5] history: add squash subcommand to fold a range Phillip Wood

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=615fe4dd3f3ad13ada8a605433f79b5012530018.1783327849.git.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 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.