[PATCH v10 3.7/3.7] fixup! history: add squash subcommand to fold a range
Phillip Wood <[email protected]> Mon, 3 Aug 2026 10:49:27 +0100
| Newsgroups | org.kernel.vger.git |
|---|---|
| Message-ID | <2ccc83c776ed77a80cc5e0584367540d231311de.1785750108.git.phillip.wood@dunelm.org.uk> |
From: Phillip Wood <[email protected]> Fix squashing into a merge If the first commit of the range is a merge then the squashed commit should also be a merge. Unfortunately, we assume that the first commit in the range only has a single parent, but do not error out if that's not the case. This means that fixing up a merge drops the second and later parents. Correct this by using the list of parents from the first commit in the range when creating the squashed commit. Signed-off-by: Phillip Wood <[email protected]> --- builtin/history.c | 20 +++++++------------- t/t3455-history-squash.sh | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/builtin/history.c b/builtin/history.c index 894da69f31..e21ef31e7e 100644 --- a/builtin/history.c +++ b/builtin/history.c @@ -1276,14 +1276,13 @@ static int squash_check_subject(struct repository *repo, static int resolve_squash_range(struct repository *repo, bool update_branches, int argc, const char **argv, - struct commit **base_out, struct commit **oldest_out, struct commit **tip_out, char **message_out) { struct rev_info revs; struct subject_data subject_data = SUBJECT_DATA_INIT; - struct commit *commit, *base = NULL, *oldest = NULL, *tip = NULL; + struct commit *commit, *oldest = NULL, *tip = NULL; size_t i; int ret, tip_count = 0; struct ref_filter filter = REF_FILTER_INIT; @@ -1426,9 +1425,6 @@ static int resolve_squash_range(struct repository *repo, "untouched.")); goto out; } - base = oldest->parents->item; - - *base_out = base; *oldest_out = oldest; *tip_out = tip; *message_out = strbuf_detach(&subject_data.squash_message, NULL); @@ -1466,10 +1462,9 @@ static int cmd_history_squash(int argc, OPT_END(), }; struct strbuf reflog_msg = STRBUF_INIT; - struct commit *base, *oldest, *tip, *rewritten; + struct commit *oldest, *tip, *rewritten; const struct object_id *base_tree_oid, *tip_tree_oid; char *message_template = NULL; - struct commit_list *parents = NULL; struct rev_info revs = { 0 }; int ret; @@ -1488,7 +1483,7 @@ static int cmd_history_squash(int argc, strbuf_join_argv(&reflog_msg, argc - 1, argv + 1, ' '); ret = resolve_squash_range(repo, action == REF_ACTION_BRANCHES, - argc, argv, &base, &oldest, &tip, + argc, argv, &oldest, &tip, &message_template); if (ret < 0) goto out; @@ -1497,13 +1492,13 @@ static int cmd_history_squash(int argc, if (ret < 0) goto out; - base_tree_oid = &repo_get_commit_tree(repo, base)->object.oid; + base_tree_oid = &repo_get_commit_tree(repo, + oldest->parents->item)->object.oid; tip_tree_oid = &repo_get_commit_tree(repo, tip)->object.oid; - commit_list_append(base, &parents); ret = commit_tree_ext(repo, "squash", oldest, message_template, - parents, - base_tree_oid, tip_tree_oid, &rewritten, flags); + oldest->parents, base_tree_oid, tip_tree_oid, + &rewritten, flags); if (ret < 0) { ret = error(_("failed writing squashed commit")); goto out; @@ -1521,7 +1516,6 @@ static int cmd_history_squash(int argc, out: strbuf_release(&reflog_msg); - commit_list_free(parents); release_revisions(&revs); free(message_template); return ret; diff --git a/t/t3455-history-squash.sh b/t/t3455-history-squash.sh index 26c4b4e29e..1713d6dcc3 100755 --- a/t/t3455-history-squash.sh +++ b/t/t3455-history-squash.sh @@ -347,6 +347,32 @@ test_expect_success 'squash commit uses last "amend!" message' ' # amend! message that targets commit that is not in range is rejected test_must_fail git history squash HEAD~3.. 2>err && test_grep "^error: cannot squash .* target is not being squashed" err +' + +test_expect_success 'squashing fixups into a merge' ' + test_when_finished \ + "git switch -f $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME; \ + git branch -D feature" && + git checkout -f start && + test_commit F1 && + git checkout -b feature start && + test_commit F2 && + git merge F1 && + echo fixed >F1.t && + cat >msg <<-EOF && + amend! $(git rev-parse HEAD) + + merge F1 and F2 + + reworded + EOF + + git commit -a -F msg && + git history squash HEAD^^! HEAD && + test_cmp_rev HEAD^1 F2 && + test_cmp_rev HEAD^2 F1 && + test_cmp_rev HEAD@{1}^{tree} HEAD^{tree} && + sed 1,2d msg | test_commit_message HEAD ' test_expect_success '--update-refs=head only moves HEAD' ' -- 2.54.0.200.gfd8d68259e3