]> git.ipfire.org Git - thirdparty/git.git/commitdiff
branch: add branch.<name>.pruneMerged opt-out
authorHarald Nordgren <haraldnordgren@gmail.com>
Wed, 3 Jun 2026 09:04:38 +0000 (09:04 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 3 Jun 2026 10:10:05 +0000 (19:10 +0900)
Setting branch.<name>.pruneMerged=false exempts that branch from
"git branch --prune-merged". Useful for a topic branch you want
to develop further after an initial round has been merged
upstream.

Unless --quiet is given, the skip is reported per branch so the
user knows why their topic was preserved.

Explicit deletion via "git branch -d" continues to consult the
normal merge check and is not affected by 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..6c1b5bb9cd966e599bb30339100ac8f7ebbf64b1 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>.pruneMerged`::
+       If set to `false`, branch _<name>_ is exempt from
+       `git branch --prune-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 f7942fcd7da17ba41c0be3759bf84fee02d2f136..69878549fc4ec862015fe86638f2250c0531c022 100644 (file)
@@ -221,9 +221,10 @@ the upstream refs refreshed.
 +
 A branch is left alone if any of the following holds:
 its upstream no longer resolves locally; it is checked out in any
-worktree; or its push destination (`<branch>@{push}`) equals its
+worktree; its push destination (`<branch>@{push}`) equals its
 upstream (`<branch>@{upstream}`), so it cannot be distinguished
-from a freshly pulled trunk that just looks "fully merged".
+from a freshly pulled trunk that just looks "fully merged"; or
+`branch.<name>.pruneMerged` is set to `false`.
 +
 Branches refused by the "fully merged" safety check are listed as
 warnings and skipped; pass them to `git branch -D` explicitly if
index 736480b00287ce4930d392cd22fdddb3c15bc454..e03805a8a706790191a606effe994df1f94d980a 100644 (file)
@@ -878,7 +878,9 @@ static int prune_merged_branches(const struct string_list *upstreams,
                struct branch *branch = branch_get(short_name);
                const char *upstream, *push;
                struct strbuf full = STRBUF_INIT;
+               struct strbuf key = STRBUF_INIT;
                int skip;
+               int opt_out;
 
                strbuf_addf(&full, "refs/heads/%s", short_name);
                skip = !!branch_checked_out(full.buf);
@@ -893,6 +895,18 @@ static int prune_merged_branches(const struct string_list *upstreams,
                if (!push || !strcmp(push, upstream))
                        continue;
 
+               strbuf_addf(&key, "branch.%s.prunemerged", short_name);
+               if (!repo_config_get_bool(the_repository, key.buf, &opt_out) &&
+                   !opt_out) {
+                       if (!quiet)
+                               fprintf(stderr,
+                                       _("Skipping '%s' (branch.%s.pruneMerged is false)\n"),
+                                       short_name, short_name);
+                       strbuf_release(&key);
+                       continue;
+               }
+               strbuf_release(&key);
+
                strvec_push(&deletable, short_name);
        }
 
index beb86987adf9bdd5172f3acf8089715c33c001fb..9e331795901d102d0cc64fc70d9e52150f757923 100755 (executable)
@@ -1997,4 +1997,34 @@ test_expect_success '--prune-merged rejects positional arguments' '
        test_grep "does not take positional arguments" 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 remote add fork ../pm-fork &&
+       test_config -C pm-optout remote.pushDefault fork &&
+       test_config -C pm-optout push.default current &&
+       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 &&
+       test_config -C pm-optout 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 &&
+       test_config -C pm-optout-d 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