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 25, 2026 at 11:32 UTC 4ef02bc803796458fecaae7164e69af8518fc5a3
4 files changed +60 -2
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
+3 -2
@@ -216,11 +216,12 @@ A branch is not deleted when:
216 +
217 --
218 * its configured upstream ref no longer exists,
219 -* it is checked out in any worktree, or
219 +* it is checked out in any worktree,
220 * pushing it by name to the remote configured by
221 `branch.<name>.remote` would update its upstream, so it cannot be
222 distinguished from a branch that just looks "fully merged" right
223 - after a pull.
223 + after a pull, or
224 +* `branch.<name>.deleteMerged` is set to `false`.
225 --
226 +
227 A branch whose work has not yet been merged into its upstream is
builtin/branch.c
+14
@@ -786,6 +786,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
786 struct ref_array candidates = { 0 };
787 struct strset deletable_branch_names = STRSET_INIT;
788 struct strvec branches_to_delete = STRVEC_INIT;
789 + struct strbuf key = STRBUF_INIT;
790 struct hashmap_iter iter;
791 struct strmap_entry *entry;
792 int ret = 0;
@@ -804,6 +805,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
805 const char *branch_name;
806 struct branch *branch;
807 const char *upstream_refname;
808 + int opt_out;
809
810 if (!skip_prefix(branch_refname, "refs/heads/", &branch_name))
811 BUG("filter returned non-branch ref '%s'", branch_refname);
@@ -821,6 +823,17 @@ static int delete_merged_branches(const struct strvec *upstreams,
823 FILTER_REFS_BRANCHES, DELETE_BRANCH_SKIP_UNMERGED))
824 continue;
825
826 + strbuf_reset(&key);
827 + strbuf_addf(&key, "branch.%s.deletemerged", branch_name);
828 + if (!repo_config_get_bool(the_repository, key.buf, &opt_out) &&
829 + !opt_out) {
830 + if (!(flags & DELETE_BRANCH_QUIET))
831 + fprintf(stderr,
832 + _("Skipping '%s' (branch.%s.deleteMerged is false)\n"),
833 + branch_name, branch_name);
834 + continue;
835 + }
836 +
837 strset_add(&deletable_branch_names, branch_name);
838 }
839
@@ -836,6 +849,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
849 DELETE_BRANCH_NO_HEAD_FALLBACK |
850 flags);
851
852 + strbuf_release(&key);
853 strvec_clear(&branches_to_delete);
854 strset_clear(&deletable_branch_names);
855 ref_array_clear(&candidates);
t/t3200-branch.sh
+36
@@ -2038,4 +2038,40 @@ test_expect_success '--delete-merged requires a value' '
2038 test_must_fail git -C forked branch --delete-merged 2>err &&
2039 test_grep "requires a value" err
2040 '
2041 +
2042 +test_expect_success '--delete-merged honours branch.<name>.deleteMerged=false' '
2043 + setup_repo_for_delete_merged &&
2044 + create_merged_branch deleted &&
2045 + create_merged_branch kept &&
2046 + (
2047 + cd repo &&
2048 + git config branch.kept.deleteMerged false &&
2049 + git checkout --detach &&
2050 +
2051 + git branch --delete-merged origin/next 2>err &&
2052 +
2053 + test_grep "Skipping .kept." err &&
2054 + check_branches <<-\EOF
2055 + kept
2056 + main
2057 + EOF
2058 + )
2059 +'
2060 +
2061 +test_expect_success "branch -d still deletes a deleteMerged=false branch" '
2062 + setup_repo_for_delete_merged &&
2063 + create_merged_branch kept &&
2064 + (
2065 + cd repo &&
2066 + git config branch.kept.deleteMerged false &&
2067 + git checkout --detach &&
2068 +
2069 + git branch -d kept &&
2070 +
2071 + check_branches <<-\EOF
2072 + main
2073 + EOF
2074 + )
2075 +'
2076 +
2077 test_done