branch: add branch.<name>.deleteMerged opt-out

Setting branch.<name>.deleteMerged=false exempts that branch from "git branch --delete-merged", which is useful for a topic you want to keep developing after an early round of it has been merged upstream. Unless --quiet is given, each skip is reported so the user knows why their topic was kept. Explicit deletion with "git branch -d" still uses the normal merge check and ignores this setting. Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Harald Nordgren committed Jul 30, 2026 at 13:58 UTC 8ac53c69950fd4d0e57c1e86d022a4668966b18d
4 files changed +59 -1
Documentation/config/branch.adoc
+7
@@ -102,3 +102,10 @@ for details).
102 `git branch --edit-description`. Branch description is
103 automatically added to the `format-patch` cover letter or
104 `request-pull` summary.
105 +
106 +`branch.<name>.deleteMerged`::
107 + If set to `false`, branch _<name>_ is exempt from
108 + `git branch --delete-merged`. Useful for a topic branch you
109 + intend to develop further after an initial round has been
110 + merged upstream. Defaults to true. Explicit deletion via
111 + `git branch -d` is unaffected.
Documentation/git-branch.adoc
+2 -1
@@ -224,7 +224,8 @@ A branch is not deleted when:
224 distinguished from a branch that just looks fully merged right
225 after a pull; this is determined by the remote's configured push and
226 fetch refspecs,
227 -* it is the local upstream of a branch that is not being deleted.
227 +* it is the local upstream of a branch that is not being deleted, or
228 +* `branch.<name>.deleteMerged` is set to `false`.
229 --
230 +
231 When such a local upstream branch has its own upstream deleted by the
builtin/branch.c
+14
@@ -818,6 +818,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
818 struct strset deletable_branch_names = STRSET_INIT;
819 struct strset protected_branch_names = STRSET_INIT;
820 struct strvec branches_to_delete = STRVEC_INIT;
821 + struct strbuf key = STRBUF_INIT;
822 struct hashmap_iter iter;
823 struct strmap_entry *entry;
824 int ret = 0;
@@ -836,6 +837,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
837 const char *branch_name;
838 struct branch *branch;
839 const char *upstream_refname;
840 + int opt_out;
841
842 if (!skip_prefix(branch_refname, "refs/heads/", &branch_name))
843 BUG("filter returned non-branch ref '%s'", branch_refname);
@@ -853,6 +855,17 @@ static int delete_merged_branches(const struct strvec *upstreams,
855 FILTER_REFS_BRANCHES, DELETE_BRANCH_SKIP_UNMERGED))
856 continue;
857
858 + strbuf_reset(&key);
859 + strbuf_addf(&key, "branch.%s.deletemerged", branch_name);
860 + if (!repo_config_get_bool(the_repository, key.buf, &opt_out) &&
861 + !opt_out) {
862 + if (!(flags & DELETE_BRANCH_QUIET))
863 + fprintf(stderr,
864 + _("Skipping '%s' (branch.%s.deleteMerged is false)\n"),
865 + branch_name, branch_name);
866 + continue;
867 + }
868 +
869 strset_add(&deletable_branch_names, branch_name);
870 }
871
@@ -873,6 +886,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
886 clear_deleted_upstreams(&protected_branch_names,
887 &deletable_branch_names);
888
889 + strbuf_release(&key);
890 strvec_clear(&branches_to_delete);
891 strset_clear(&protected_branch_names);
892 strset_clear(&deletable_branch_names);
t/t3200-branch.sh
+36
@@ -2084,4 +2084,40 @@ test_expect_success '--delete-merged requires a value' '
2084 test_must_fail git -C forked branch --delete-merged 2>err &&
2085 test_grep "requires a value" err
2086 '
2087 +
2088 +test_expect_success '--delete-merged honours branch.<name>.deleteMerged=false' '
2089 + setup_repo_for_delete_merged &&
2090 + create_merged_branch deleted &&
2091 + create_merged_branch kept &&
2092 + (
2093 + cd repo &&
2094 + git config branch.kept.deleteMerged false &&
2095 + git checkout --detach &&
2096 +
2097 + git branch --delete-merged origin/next 2>err &&
2098 +
2099 + test_grep "Skipping .kept." err &&
2100 + check_branches <<-\EOF
2101 + kept
2102 + main
2103 + EOF
2104 + )
2105 +'
2106 +
2107 +test_expect_success "branch -d still deletes a deleteMerged=false branch" '
2108 + setup_repo_for_delete_merged &&
2109 + create_merged_branch kept &&
2110 + (
2111 + cd repo &&
2112 + git config branch.kept.deleteMerged false &&
2113 + git checkout --detach &&
2114 +
2115 + git branch -d kept &&
2116 +
2117 + check_branches <<-\EOF
2118 + main
2119 + EOF
2120 + )
2121 +'
2122 +
2123 test_done