]> git.ipfire.org Git - thirdparty/git.git/commitdiff
branch: add branch.<name>.deleteMerged opt-out
authorHarald Nordgren <haraldnordgren@gmail.com>
Sat, 25 Jul 2026 11:32:15 +0000 (11:32 +0000)
committerJunio C Hamano <gitster@pobox.com>
Sat, 25 Jul 2026 17:15:04 +0000 (10:15 -0700)
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>
Documentation/config/branch.adoc
Documentation/git-branch.adoc
builtin/branch.c
t/t3200-branch.sh

index a4db9fa5c87eab074a6754e690041073e1972d58..d8483acb4f9b9807b55ebdc7ea5b8dda3f1d19b2 100644 (file)
@@ -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>.deleteMerged`::
+       If set to `false`, branch _<name>_ is exempt from
+       `git branch --delete-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.
index 2a96cd72530a1e074c747db022b48ac31e400d09..2b206e8689ed3f5747b1c7bab925eaf761208301 100644 (file)
@@ -216,11 +216,12 @@ A branch is not deleted when:
 +
 --
 * its configured upstream ref no longer exists,
-* it is checked out in any worktree, or
+* it is checked out in any worktree,
 * pushing it by name to the remote configured by
   `branch.<name>.remote` would update its upstream, so it cannot be
   distinguished from a branch that just looks "fully merged" right
-  after a pull.
+  after a pull, or
+* `branch.<name>.deleteMerged` is set to `false`.
 --
 +
 A branch whose work has not yet been merged into its upstream is
index a74709229788cad62b7413de8215848fa7893f9c..4644f061ea9b6e1a5da109bc3f1a652ad86503b5 100644 (file)
@@ -786,6 +786,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
        struct ref_array candidates = { 0 };
        struct strset deletable_branch_names = STRSET_INIT;
        struct strvec branches_to_delete = STRVEC_INIT;
+       struct strbuf key = STRBUF_INIT;
        struct hashmap_iter iter;
        struct strmap_entry *entry;
        int ret = 0;
@@ -804,6 +805,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
                const char *branch_name;
                struct branch *branch;
                const char *upstream_refname;
+               int opt_out;
 
                if (!skip_prefix(branch_refname, "refs/heads/", &branch_name))
                        BUG("filter returned non-branch ref '%s'", branch_refname);
@@ -821,6 +823,17 @@ static int delete_merged_branches(const struct strvec *upstreams,
                                        FILTER_REFS_BRANCHES, DELETE_BRANCH_SKIP_UNMERGED))
                        continue;
 
+               strbuf_reset(&key);
+               strbuf_addf(&key, "branch.%s.deletemerged", branch_name);
+               if (!repo_config_get_bool(the_repository, key.buf, &opt_out) &&
+                   !opt_out) {
+                       if (!(flags & DELETE_BRANCH_QUIET))
+                               fprintf(stderr,
+                                       _("Skipping '%s' (branch.%s.deleteMerged is false)\n"),
+                                       branch_name, branch_name);
+                       continue;
+               }
+
                strset_add(&deletable_branch_names, branch_name);
        }
 
@@ -836,6 +849,7 @@ static int delete_merged_branches(const struct strvec *upstreams,
                                      DELETE_BRANCH_NO_HEAD_FALLBACK |
                                      flags);
 
+       strbuf_release(&key);
        strvec_clear(&branches_to_delete);
        strset_clear(&deletable_branch_names);
        ref_array_clear(&candidates);
index 29d33bf3e927716b7dc8079538ab46f701ca06c1..e808e0dd09822b8843a8829212268eaffb6bc596 100755 (executable)
@@ -2038,4 +2038,40 @@ test_expect_success '--delete-merged requires a value' '
        test_must_fail git -C forked branch --delete-merged 2>err &&
        test_grep "requires a value" err
 '
+
+test_expect_success '--delete-merged honours branch.<name>.deleteMerged=false' '
+       setup_repo_for_delete_merged &&
+       create_merged_branch deleted &&
+       create_merged_branch kept &&
+       (
+               cd repo &&
+               git config branch.kept.deleteMerged false &&
+               git checkout --detach &&
+
+               git branch --delete-merged origin/next 2>err &&
+
+               test_grep "Skipping .kept." err &&
+               check_branches <<-\EOF
+               kept
+               main
+               EOF
+       )
+'
+
+test_expect_success "branch -d still deletes a deleteMerged=false branch" '
+       setup_repo_for_delete_merged &&
+       create_merged_branch kept &&
+       (
+               cd repo &&
+               git config branch.kept.deleteMerged false &&
+               git checkout --detach &&
+
+               git branch -d kept &&
+
+               check_branches <<-\EOF
+               main
+               EOF
+       )
+'
+
 test_done