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 v4 4/4] history: re-edit a squash with every message
Date: Sun, 21 Jun 2026 05:53:15 +0000	[thread overview]
Message-ID: <85c7817d7eb2f2598940ae9dfa065aaedd9fe5a9.1782021195.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2337.v4.git.git.1782021195.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 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.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 Documentation/git-history.adoc |  5 +--
 builtin/history.c              | 61 +++++++++++++++++++++++++++++++++-
 t/t3455-history-squash.sh      | 37 +++++++++++++++++++++
 3 files changed, 100 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc
index 6716749cde..df389015aa 100644
--- a/Documentation/git-history.adoc
+++ b/Documentation/git-history.adoc
@@ -111,8 +111,9 @@ 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 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.
diff --git a/builtin/history.c b/builtin/history.c
index 4f1baea56c..ff3bc9f945 100644
--- a/builtin/history.c
+++ b/builtin/history.c
@@ -1068,6 +1068,56 @@ 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 rev_info revs;
+	struct commit *commit;
+	struct strvec args = STRVEC_INIT;
+	int n = 0, 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))) {
+		const char *message, *body;
+		struct strbuf one = STRBUF_INIT;
+
+		message = repo_logmsg_reencode(repo, commit, NULL, NULL);
+		find_commit_subject(message, &body);
+		strbuf_addstr(&one, body);
+		strbuf_trim_trailing_newline(&one);
+
+		if (n++)
+			strbuf_addch(out, '\n');
+		strbuf_addbuf(out, &one);
+		strbuf_addch(out, '\n');
+
+		strbuf_release(&one);
+		repo_unuse_commit_buffer(repo, commit, message);
+	}
+
+	ret = 0;
+
+out:
+	reset_revision_walk();
+	release_revisions(&revs);
+	strvec_clear(&args);
+	return ret;
+}
+
 static int cmd_history_squash(int argc,
 			      const char **argv,
 			      const char *prefix,
@@ -1092,6 +1142,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;
@@ -1131,6 +1182,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;
@@ -1139,7 +1196,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"));
@@ -1160,6 +1218,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 821c801153..1fb3b9b63e 100755
--- a/t/t3455-history-squash.sh
+++ b/t/t3455-history-squash.sh
@@ -135,6 +135,43 @@ 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 &&
+	cp "$1" buffer &&
+	echo combined >"$1"
+	EOF
+	test_set_editor "$(pwd)/editor" &&
+	git history squash --reedit-message start.. &&
+
+	test_grep "re-one subject" buffer &&
+	test_grep "re-one body line" buffer &&
+	test_grep re-two buffer &&
+	test_grep re-three buffer &&
+	git log --format="%s" -1 >actual &&
+	echo combined >expect &&
+	test_cmp expect actual
+'
+
+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 '--dry-run predicts the rewrite without performing it' '
 	git reset --hard three &&
 	head_before=$(git rev-parse HEAD) &&
-- 
gitgitgadget

      parent reply	other threads:[~2026-06-21  5:53 UTC|newest]

Thread overview: 32+ 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       ` Harald Nordgren via GitGitGadget [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=85c7817d7eb2f2598940ae9dfa065aaedd9fe5a9.1782021195.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.