Subject: [PATCH v10 3/4] branch: add branch.<name>.pruneMerged opt-out

From: "Harald Nordgren via GitGitGadget" <[email-masked]>
Date: Thu, 21 May 2026 22:40:03 +0000

From: Harald Nordgren <[email-masked]>

Setting branch.<name>.pruneMerged=false exempts that branch
from --prune-merged. Useful for topic branches you intend to
develop further after an initial round has been merged
upstream.

Explicit deletion via 'git branch -d' is unaffected.

Signed-off-by: Harald Nordgren <[email-masked]>
---
 Documentation/config/branch.adoc |  5 +++++
 Documentation/git-branch.adoc    |  1 +
 builtin/branch.c                 | 13 +++++++++++++
 t/t3200-branch.sh                | 27 +++++++++++++++++++++++++++
 4 files changed, 46 insertions(+)

diff --git a/Documentation/config/branch.adoc b/Documentation/config/branch.adoc
index a4db9fa5c8..6402b78a73 100644
--- a/Documentation/config/branch.adoc
+++ b/Documentation/config/branch.adoc
@@ -102,3 +102,8 @@ 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`. Defaults to true. Explicit
+	deletion via `git branch -d` is unaffected.
diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc
index a7c0e29e94..247e4daeb8 100644
--- a/Documentation/git-branch.adoc
+++ b/Documentation/git-branch.adoc
@@ -224,6 +224,7 @@ The following branches are always preserved:
   any configured remote (the target of
   `refs/remotes/<remote>/HEAD`) -- typically `main` or
   `master`;
+* any branch with `branch.<name>.pruneMerged` set to `false`;
 * any branch whose upstream no longer resolves locally.
 --
 
diff --git a/builtin/branch.c b/builtin/branch.c
index 29d38e9060..f995f257f0 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -822,6 +822,7 @@ static int prune_merged_branches(int argc, const char **argv, int quiet)
 	for_each_string_list_item(item, &candidates) {
 		const char *short_name = item->string;
 		const char *upstream = item->util;
+		int prune_allowed = 1;
 
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "refs/heads/%s", short_name);
@@ -835,6 +836,18 @@ static int prune_merged_branches(int argc, const char **argv, int quiet)
 		if (!refs_ref_exists(refs, upstream))
 			continue;
 
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "branch.%s.prunemerged", short_name);
+		if (!repo_config_get_bool(the_repository, buf.buf,
+					  &prune_allowed) &&
+		    !prune_allowed) {
+			if (!quiet)
+				fprintf(stderr, _("Skipping '%s' "
+						  "(branch.%s.pruneMerged is false)\n"),
+					short_name, short_name);
+			continue;
+		}
+
 		strvec_push(&deletable, short_name);
 	}
 	strbuf_release(&buf);
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index c8589cd3a6..b35189ce84 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1937,4 +1937,31 @@ 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 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 &&
+	git -C pm-optout config 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 &&
+	git -C pm-optout-d config 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