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 22, 2026 at 07:10 UTC
4803f201e6d7213895b0450f85fa3dd1615492ef
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
size_t i;
@@ -805,6 +806,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
806
const char *branch_name;
807
struct branch *branch;
808
const char *upstream_refname;
809
+ int opt_out;
810
811
if (!skip_prefix(branch_refname, "refs/heads/", &branch_name))
812
BUG("filter returned non-branch ref '%s'", branch_refname);
@@ -822,6 +824,17 @@ static int delete_merged_branches(const struct strvec *upstreams,
824
FILTER_REFS_BRANCHES, DELETE_BRANCH_SKIP_UNMERGED))
825
continue;
826
827
+ strbuf_reset(&key);
828
+ strbuf_addf(&key, "branch.%s.deletemerged", branch_name);
829
+ if (!repo_config_get_bool(the_repository, key.buf, &opt_out) &&
830
+ !opt_out) {
831
+ if (!(flags & DELETE_BRANCH_QUIET))
832
+ fprintf(stderr,
833
+ _("Skipping '%s' (branch.%s.deleteMerged is false)\n"),
834
+ branch_name, branch_name);
835
+ continue;
836
+ }
837
+
838
strset_add(&deletable_branch_names, branch_name);
839
}
840
@@ -837,6 +850,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
850
DELETE_BRANCH_NO_HEAD_FALLBACK |
851
flags);
852
853
+ strbuf_release(&key);
854
strvec_clear(&branches_to_delete);
855
strset_clear(&deletable_branch_names);
856
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