[PATCH v5 0/4] branch: let --delete-merged find squash merged branches
- From
Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>
- Date
- Oct 30, 2026, 10:52 UTC
- Message-ID
- <pull.2425.v5.git.git.1793357582.gitgitgadget@gmail.com>
- 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 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
filesDocumentation/config/branch.adoc | 8 +- Documentation/git-branch.adoc | 22 +- builtin/branch.c | 360 +++++++++++++++++++++++++++++-- t/t3200-branch.sh | 218 +++++++++++++++++++ 4 files changed, 578 insertions(+), 30 deletions(-)
base-commit: 8d1b215c01512b9658d4525c54ff9b9245f9a7e8 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2425%2FHaraldNordgren%2Fbranch-delete-squashed-v5 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2425/HaraldNordgren/branch-delete-squashed-v5 Pull-Request: https://github.com/git/git/pull/2425
Range-diff vs v4:
1: 1d01085b75 = 1: 090aec5725 branch: move the deleteMerged opt-out check into a helper
2: 592562d7e7 = 2: 24180bf4a2 branch: let delete_branches() name where a branch landed
3: deb1e649cf ! 3: bf30f57ceb branch: add --delete-integrated to delete rebase and squash merged branches
@@ builtin/branch.c
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged] [--no-merged] [(--forked <branch>)...]"),
+@@ builtin/branch.c: static const char * const builtin_branch_usage[] = {
+ N_("git branch [<options>] [-r | -a] [--format]"),
+ N_("git branch [<options>] (--delete-merged <pattern>)... "
+ "[<branch-pattern>...]"),
++ N_("git branch [<options>] (--delete-integrated <pattern>)... "
++ "[<branch-pattern>...]"),
+ NULL
+ };
+
@@ builtin/branch.c: enum delete_branch_flags {
DELETE_BRANCH_SKIP_UNMERGED = (1 << 2),
DELETE_BRANCH_NO_HEAD_FALLBACK = (1 << 3),
@@ builtin/branch.c: int cmd_branch(int argc,
if (filter.with_commit || filter.no_commit ||
@@ builtin/branch.c: int cmd_branch(int argc,
+ filter.points_at.nr || filter.forked.nr)
+ list = 1;
++ die_for_incompatible_opt2(!!delete_merged.nr, "--delete-merged",
++ !!delete_integrated.nr, "--delete-integrated");
++
noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
!!show_current + !!list + !!edit_description +
- !!unset_upstream + !!delete_merged.nr;
@@ t/t3200-branch.sh: test_expect_success '--dry-run without --delete-merged is rej
+test_expect_success '--delete-integrated cannot be combined with --delete-merged' '
+ test_must_fail git -C forked branch --delete-merged origin/next \
-+ --delete-integrated origin/next
++ --delete-integrated origin/next 2>err &&
++ test_grep "cannot be used together" err
+'
+
test_done
4: 19a9630c13 = 4: 9443055242 branch: let --delete-integrated find branches merged onto changed files-- gitgitgadget