[PATCH v11 5/6] branch: add branch.<name>.pruneMerged opt-out
From: Harald Nordgren via GitGitGadget <hidden>
Date: 2026-05-22 11:31:51
Subsystem:
documentation, the rest · Maintainers:
Jonathan Corbet, Linus Torvalds
From: Harald Nordgren <redacted> Setting branch.<name>.pruneMerged=false exempts that branch from "git branch --prune-merged". Useful for a topic branch you want to develop further after an initial round has been merged upstream. Unless --quiet is given, the skip is reported per branch so the user knows why their topic was preserved. Explicit deletion via "git branch -d" continues to consult the normal merge check and is not affected by this setting. Signed-off-by: Harald Nordgren <redacted> --- Documentation/config/branch.adoc | 7 +++++++ Documentation/git-branch.adoc | 5 +++-- builtin/branch.c | 14 ++++++++++++++ t/t3200-branch.sh | 30 ++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/Documentation/config/branch.adoc b/Documentation/config/branch.adoc
index a4db9fa5c8..6c1b5bb9cd 100644
--- a/Documentation/config/branch.adoc
+++ b/Documentation/config/branch.adoc@@ -102,3 +102,10 @@ for details). `git branch --edit-description`. Branch description is automatically added to the `format-patch` cover letter or `request-pull` summary. + +`branch.<name>.pruneMerged`:: + If set to `false`, branch _<name>_ is exempt from + `git branch --prune-merged`. Useful for a topic branch you + intend to develop further after an initial round has been + merged upstream. Defaults to true. Explicit deletion via + `git branch -d` is unaffected.
diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc
index c521b5f4ca..1bd28c4e37 100644
--- a/Documentation/git-branch.adoc
+++ b/Documentation/git-branch.adoc@@ -226,9 +226,10 @@ the upstream refs refreshed. + A branch is left alone if any of the following holds: its upstream no longer resolves locally; it is checked out in any -worktree; or its push destination (`<branch>@{push}`) equals its +worktree; its push destination (`<branch>@{push}`) equals its upstream (`<branch>@{upstream}`), so it cannot be distinguished -from a freshly pulled trunk that just looks "fully merged". +from a freshly pulled trunk that just looks "fully merged"; or +`branch.<name>.pruneMerged` is set to `false`. + Branches refused by the "fully merged" safety check are listed as warnings and skipped; pass them to `git branch -D` explicitly if
diff --git a/builtin/branch.c b/builtin/branch.c
index 1569f29573..187d5d1563 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c@@ -875,7 +875,9 @@ static int prune_merged_branches(int argc, const char **argv, int quiet) struct branch *branch = branch_get(short_name); const char *upstream, *push; struct strbuf full = STRBUF_INIT; + struct strbuf key = STRBUF_INIT; int skip; + int opt_out; strbuf_addf(&full, "refs/heads/%s", short_name); skip = !!branch_checked_out(full.buf);
@@ -890,6 +892,18 @@ static int prune_merged_branches(int argc, const char **argv, int quiet) if (!push || !strcmp(push, upstream)) continue; + strbuf_addf(&key, "branch.%s.prunemerged", short_name); + if (!repo_config_get_bool(the_repository, key.buf, &opt_out) && + !opt_out) { + if (!quiet) + fprintf(stderr, + _("Skipping '%s' (branch.%s.pruneMerged is false)\n"), + short_name, short_name); + strbuf_release(&key); + continue; + } + strbuf_release(&key); + strvec_push(&deletable, short_name); }
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index ad87946081..da7e174e09 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh@@ -1990,4 +1990,34 @@ test_expect_success '--prune-merged requires at least one <branch>' ' test_grep "at least one <branch>" err ' +test_expect_success '--prune-merged honours branch.<name>.pruneMerged=false' ' + test_when_finished "rm -rf pm-optout" && + git clone pm-upstream pm-optout && + git -C pm-optout remote add fork ../pm-fork && + test_config -C pm-optout remote.pushDefault fork && + test_config -C pm-optout push.default current && + git -C pm-optout branch one one-commit && + git -C pm-optout branch --set-upstream-to=origin/next one && + git -C pm-optout branch two two-commit && + git -C pm-optout branch --set-upstream-to=origin/next two && + test_config -C pm-optout branch.one.pruneMerged false && + + git -C pm-optout branch --prune-merged "origin/*" 2>err && + + git -C pm-optout rev-parse --verify refs/heads/one && + test_must_fail git -C pm-optout rev-parse --verify refs/heads/two && + test_grep "Skipping .one." err +' + +test_expect_success 'branch -d still deletes a pruneMerged=false branch' ' + test_when_finished "rm -rf pm-optout-d" && + git clone pm-upstream pm-optout-d && + git -C pm-optout-d branch one one-commit && + git -C pm-optout-d branch --set-upstream-to=origin/next one && + test_config -C pm-optout-d branch.one.pruneMerged false && + + git -C pm-optout-d branch -d one && + test_must_fail git -C pm-optout-d rev-parse --verify refs/heads/one +' + test_done
--
gitgitgadget