From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
"D. Ben Knoble" <ben.knoble@gmail.com>,
Phillip Wood <phillip.wood123@gmail.com>,
Jasper Mayone <me@jaspermayone.com>,
Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v7 0/4] branch: let --delete-merged find squash merged branches
Date: Sat, 14 Nov 2026 13:24:26 +0000 [thread overview]
Message-ID: <pull.2425.v7.git.git.1794662670.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2425.git.git.1790667030497.gitgitgadget@gmail.com>
Branches merged on GitHub with "Squash and merge" or "Rebase and merge" are
never deleted by git branch --delete-merged, because their tips are not
reachable from the upstream. This treats such a branch as merged when some
upstream commit contains all of its changes, and names that commit in the
output:
Deleted branch topic (was 1a2b3c4, landed as 9f8e7d6).
Changes in v7:
* Added a test that --delete-integrated keeps a branch whose changes did
not land.
* Read the index once before the walk in find_landed_commits(), instead of
for every file merge at every candidate commit.
* The commit message now says the merge uses this repository's attributes
from the working tree and the index.
Changes in v6:
* Read the index before the file merge, so .gitattributes that are only in
the index are honored too.
* Renamed the diff_options variables from opt to diffopt.
Changes in v5:
* git branch -h lists --delete-integrated in the usage lines
* Giving both --delete-merged and --delete-integrated dies with a short
"cannot be used together" message instead of the full usage text
Changes in v4:
* --delete-merged works as it does on master again: it only deletes
branches whose tip is reachable from their upstream
* New --delete-integrated <pattern> also deletes branches that were rebase
or squash merged, including those squashed after upstream changed the
same files. It replaces --squashed
* New prep commit lets delete_branches() take a map of where each branch
landed, so "landed as" is printed there
* Added Junio's test that --delete-merged keeps a branch when an unrelated
upstream commit adds an identical file
* --dry-run works with either option, and the two options can't be combined
Changes in v3:
* Split into multiple commits, one for rebase merges and one for squash
merges.
* By default a branch counts as merged when an upstream commit has its
exact version of every file it changed. This never reads file contents,
so it stays cheap in a partial clone. It finds rebase merges, and squash
merges where upstream left those files alone.
* Replace --no-squashed with an opt-in --squashed, which also finds
branches merged after upstream changed the same files by merging those
files.
* Add a test for a rebase merge, and check that a squash onto changed files
is kept without --squashed and deleted with it.
Changes in v2:
* Diff each upstream commit once and share the result between all branches,
faster than v1.
* Fix a branch merged with a merge commit being reported as "landed as" the
merge when another branch was searched first, and add a test for it. Show
the topic commit itself in "landed as" for work that arrived through a
merge.
* Use git merge --squash as the example in the commit message and mention
GitHub's squash and rebase buttons.
* Add --no-squashed to skip the squash search.
Harald Nordgren (4):
branch: move the deleteMerged opt-out check into a helper
branch: let delete_branches() name where a branch landed
branch: add --delete-integrated to delete rebase and squash merged
branches
branch: let --delete-integrated find branches merged onto changed
files
Documentation/config/branch.adoc | 8 +-
Documentation/git-branch.adoc | 22 +-
builtin/branch.c | 361 +++++++++++++++++++++++++++++--
t/t3200-branch.sh | 271 +++++++++++++++++++++++
4 files changed, 632 insertions(+), 30 deletions(-)
base-commit: 7fb6055e678f800320d8ab8cd8f201d9493d1ba0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2425%2FHaraldNordgren%2Fbranch-delete-squashed-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2425/HaraldNordgren/branch-delete-squashed-v7
Pull-Request: https://github.com/git/git/pull/2425
Range-diff vs v6:
1: e728b4ed0d = 1: 25d2441ce7 branch: move the deleteMerged opt-out check into a helper
2: 9858c8c81e = 2: 18c3edb6e3 branch: let delete_branches() name where a branch landed
3: 92bd9afda4 = 3: 7bc634c794 branch: add --delete-integrated to delete rebase and squash merged branches
4: 006a57d7d1 ! 4: 4b5485584e branch: let --delete-integrated find branches merged onto changed files
@@ Commit message
unchanged. This has to read the files, so in a partial clone it may
download blobs.
+ The merge uses the attributes of this repository, from the working tree
+ and the index, the same as a merge run here would. A merge driver that
+ only the upstream repository has is not seen. In a bare repository only
+ info/attributes and the global file apply.
+
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
## Documentation/git-branch.adoc ##
@@ builtin/branch.c: static void collect_branch_changes(struct landing_search *sear
+ mmfile_t result = { 0 };
+ int ret;
+
-+ repo_read_index(the_repository);
+ read_mmblob(&base, the_repository->objects, &change->base_oid);
+ read_mmblob(&upstream, the_repository->objects, &change->upstream_oid);
+ read_mmblob(&branch, the_repository->objects, &change->branch_oid);
@@ builtin/branch.c: static void collect_branch_changes(struct landing_search *sear
return 1;
}
+@@ builtin/branch.c: static void find_landed_commits(struct landing_search *searches,
+ diffopt.no_free = 1;
+ diff_setup_done(&diffopt);
+ init_changed_paths(&cache);
++ repo_read_index(the_repository);
+
+ for (size_t i = 0; i < searches_nr; i++) {
+ struct commit *landed;
## t/t3200-branch.sh ##
@@ t/t3200-branch.sh: test_expect_success '--delete-integrated deletes a squash merged branch that was
@@ t/t3200-branch.sh: test_expect_success '--delete-integrated deletes a squash mer
+ EOF
+ )
+'
++
++test_expect_success '--delete-integrated keeps branches whose changes did not land' '
++ (
++ cd upstream &&
++ git checkout next &&
++ test_write_lines 1 2 3 4 5 6 7 8 9 10 >doc &&
++ git add doc &&
++ git commit -m "add doc" &&
++ git checkout main
++ ) &&
++ setup_repo_for_delete_merged &&
++ (
++ cd repo &&
++ git checkout -b unmerged --track origin/next &&
++ test_write_lines 1 2 3 4 5 6 7 eight 9 10 >doc &&
++ git commit -a -m "edit line 8" &&
++ git push origin unmerged &&
++ git checkout -b half-landed --track origin/next &&
++ test_write_lines one 2 3 4 5 6 7 8 9 ten >doc &&
++ git commit -a -m "edit lines 1 and 10" &&
++ git push origin half-landed &&
++ git checkout -b conflicting --track origin/next &&
++ test_write_lines 1 2 three 4 5 6 7 8 9 10 >doc &&
++ git commit -a -m "edit line 3" &&
++ git push origin conflicting &&
++ git checkout --detach
++ ) &&
++ (
++ cd upstream &&
++ git checkout next &&
++ test_write_lines 1 2 3 4 five 6 7 8 9 10 >doc &&
++ git commit -a -m "edit line 5" &&
++ test_write_lines one 2 3 4 five 6 7 8 9 10 >doc &&
++ git commit -a -m "take line 1 from half-landed" &&
++ test_write_lines one 2 THREE 4 five 6 7 8 9 10 >doc &&
++ git commit -a -m "edit line 3 differently" &&
++ git checkout main
++ ) &&
++ (
++ cd repo &&
++ git fetch origin &&
++
++ git branch --delete-integrated origin/next >actual 2>&1 &&
++ test_must_be_empty actual &&
++
++ check_branches <<-\EOF
++ conflicting
++ half-landed
++ main
++ unmerged
++ EOF
++ )
++'
+
test_expect_success '--delete-integrated reports a merge plainly next to an unmerged branch' '
setup_repo_for_delete_merged &&
--
gitgitgadget
next prev parent reply other threads:[~2026-11-14 13:24 UTC|newest]
Thread overview: 84+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-29 7:30 [PATCH] branch: let --delete-merged find squash merged branches Harald Nordgren via GitGitGadget
2026-09-29 7:46 ` Kristoffer Haugsbakk
2026-09-29 7:52 ` Harald Nordgren
2026-09-29 8:08 ` Kristoffer Haugsbakk
2026-09-29 11:18 ` D. Ben Knoble
2026-09-29 13:31 ` Kristoffer Haugsbakk
2026-09-29 11:26 ` D. Ben Knoble
2026-09-29 13:44 ` Harald Nordgren
2026-09-29 13:52 ` Harald Nordgren
2026-09-29 15:28 ` D. Ben Knoble
2026-09-29 18:34 ` Harald Nordgren
2026-10-04 9:54 ` Phillip Wood
2026-10-04 22:28 ` Harald Nordgren
2026-10-08 15:25 ` Phillip Wood
2026-10-08 18:42 ` Harald Nordgren
2026-10-09 8:16 ` Kristoffer Haugsbakk
2026-10-09 11:11 ` Harald Nordgren
2026-10-09 11:18 ` Harald Nordgren
2026-10-09 13:40 ` Phillip Wood
2026-10-09 13:37 ` Phillip Wood
2026-10-09 15:27 ` Harald Nordgren
2026-10-08 17:51 ` D. Ben Knoble
2026-10-09 13:34 ` Phillip Wood
2026-10-11 9:41 ` [PATCH v2 0/2] " Harald Nordgren via GitGitGadget
2026-10-11 9:41 ` [PATCH v2 1/2] " Harald Nordgren via GitGitGadget
2026-10-11 9:41 ` [PATCH v2 2/2] branch: add --no-squashed to skip the squash search Harald Nordgren via GitGitGadget
2026-10-12 0:30 ` [PATCH v2 0/2] branch: let --delete-merged find squash merged branches Junio C Hamano
2026-10-12 9:50 ` Phillip Wood
2026-10-12 14:31 ` Harald Nordgren
2026-10-12 16:50 ` Junio C Hamano
2026-10-12 17:44 ` Harald Nordgren
2026-10-13 2:23 ` Junio C Hamano
2026-10-17 22:23 ` Harald Nordgren
2026-10-19 4:40 ` Jasper Mayone
2026-10-18 15:09 ` Harald Nordgren
2026-10-12 12:41 ` Harald Nordgren
2026-10-12 15:49 ` D. Ben Knoble
2026-10-21 12:28 ` [PATCH v3 0/3] " Harald Nordgren via GitGitGadget
2026-10-21 12:28 ` [PATCH v3 1/3] branch: move the deleteMerged opt-out check into a helper Harald Nordgren via GitGitGadget
2026-10-21 12:28 ` [PATCH v3 2/3] branch: let --delete-merged find rebase merged branches Harald Nordgren via GitGitGadget
2026-10-21 12:28 ` [PATCH v3 3/3] branch: add --squashed to find squash " Harald Nordgren via GitGitGadget
2026-10-21 17:35 ` [PATCH v3 0/3] branch: let --delete-merged " Jasper Mayone
2026-10-21 18:51 ` Harald Nordgren
2026-10-22 12:55 ` D. Ben Knoble
2026-10-22 14:41 ` Harald Nordgren
2026-10-22 5:49 ` Junio C Hamano
2026-10-22 7:18 ` Harald Nordgren
2026-10-22 16:33 ` Junio C Hamano
2026-10-22 16:57 ` Junio C Hamano
2026-10-22 17:31 ` Harald Nordgren
2026-10-22 17:44 ` Jasper Mayone
2026-10-22 17:56 ` Junio C Hamano
2026-10-23 9:07 ` Harald Nordgren
2026-10-22 17:44 ` Harald Nordgren
2026-10-22 12:59 ` D. Ben Knoble
2026-10-26 8:33 ` [PATCH v4 0/4] " Harald Nordgren via GitGitGadget
2026-10-26 8:33 ` [PATCH v4 1/4] branch: move the deleteMerged opt-out check into a helper Harald Nordgren via GitGitGadget
2026-10-26 8:33 ` [PATCH v4 2/4] branch: let delete_branches() name where a branch landed Harald Nordgren via GitGitGadget
2026-10-26 8:33 ` [PATCH v4 3/4] branch: add --delete-integrated to delete rebase and squash merged branches Harald Nordgren via GitGitGadget
2026-10-26 8:33 ` [PATCH v4 4/4] branch: let --delete-integrated find branches merged onto changed files Harald Nordgren via GitGitGadget
2026-10-29 4:30 ` [PATCH v4 0/4] branch: let --delete-merged find squash merged branches Jasper Mayone
2026-10-29 8:30 ` Harald Nordgren
2026-10-30 10:52 ` [PATCH v5 " Harald Nordgren via GitGitGadget
2026-10-30 10:52 ` [PATCH v5 1/4] branch: move the deleteMerged opt-out check into a helper Harald Nordgren via GitGitGadget
2026-10-30 10:53 ` [PATCH v5 2/4] branch: let delete_branches() name where a branch landed Harald Nordgren via GitGitGadget
2026-10-30 10:53 ` [PATCH v5 3/4] branch: add --delete-integrated to delete rebase and squash merged branches Harald Nordgren via GitGitGadget
2026-10-30 19:14 ` Junio C Hamano
2026-10-31 8:09 ` Harald Nordgren
2026-10-30 10:53 ` [PATCH v5 4/4] branch: let --delete-integrated find branches merged onto changed files Harald Nordgren via GitGitGadget
2026-10-30 19:25 ` Junio C Hamano
2026-10-31 8:10 ` Harald Nordgren
2026-11-01 8:22 ` [PATCH v6 0/4] branch: let --delete-merged find squash merged branches Harald Nordgren via GitGitGadget
2026-11-01 8:22 ` [PATCH v6 1/4] branch: move the deleteMerged opt-out check into a helper Harald Nordgren via GitGitGadget
2026-11-01 8:22 ` [PATCH v6 2/4] branch: let delete_branches() name where a branch landed Harald Nordgren via GitGitGadget
2026-11-01 8:22 ` [PATCH v6 3/4] branch: add --delete-integrated to delete rebase and squash merged branches Harald Nordgren via GitGitGadget
2026-11-01 8:22 ` [PATCH v6 4/4] branch: let --delete-integrated find branches merged onto changed files Harald Nordgren via GitGitGadget
2026-11-11 8:30 ` Harald Nordgren
2026-11-11 15:27 ` Jasper Mayone
2026-11-14 11:08 ` Harald Nordgren
2026-11-14 13:24 ` Harald Nordgren via GitGitGadget [this message]
2026-11-14 13:24 ` [PATCH v7 1/4] branch: move the deleteMerged opt-out check into a helper Harald Nordgren via GitGitGadget
2026-11-14 13:24 ` [PATCH v7 2/4] branch: let delete_branches() name where a branch landed Harald Nordgren via GitGitGadget
2026-11-14 13:24 ` [PATCH v7 3/4] branch: add --delete-integrated to delete rebase and squash merged branches Harald Nordgren via GitGitGadget
2026-11-14 13:24 ` [PATCH v7 4/4] branch: let --delete-integrated find branches merged onto changed files Harald Nordgren via GitGitGadget
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=pull.2425.v7.git.git.1794662670.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=ben.knoble@gmail.com \
--cc=git@vger.kernel.org \
--cc=haraldnordgren@gmail.com \
--cc=kristofferhaugsbakk@fastmail.com \
--cc=me@jaspermayone.com \
--cc=phillip.wood123@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.