From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
Johannes Sixt <j6t@kdbg.org>,
Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v9 0/5] branch: prune-merged
Date: Wed, 13 May 2026 19:34:38 +0000 [thread overview]
Message-ID: <pull.2285.v9.git.git.1778700883.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2285.v8.git.git.1778605658.gitgitgadget@gmail.com>
* --force no longer has special meaning with --prune-merged; reachability
is always enforced. Use git branch -D to delete an unmerged branch.
Matches how git branch's other read/safe actions treat --force.
* Synopsis drops [-f]; "not fully merged" hint points at git branch -D.
* Dropped the --prune-merged --force tests.
Harald Nordgren (5):
branch: add --forked <remote>
branch: let delete_branches warn instead of error on bulk refusal
branch: add --prune-merged <remote>
branch: add branch.<name>.pruneMerged opt-out
branch: add --all-remotes flag
Documentation/config/branch.adoc | 7 +
Documentation/git-branch.adoc | 37 ++++
builtin/branch.c | 292 +++++++++++++++++++++++++++++--
t/t3200-branch.sh | 208 ++++++++++++++++++++++
4 files changed, 528 insertions(+), 16 deletions(-)
base-commit: 59ff4886a579f4bc91e976fe18590b9ae02c7a08
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2285%2FHaraldNordgren%2Ffetch-prune-local-branches-v9
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2285/HaraldNordgren/fetch-prune-local-branches-v9
Pull-Request: https://github.com/git/git/pull/2285
Range-diff vs v8:
1: 22fa8515df = 1: 9324b26091 branch: add --forked <remote>
2: b443f0f367 = 2: 2a13e5d4bc branch: let delete_branches warn instead of error on bulk refusal
3: 3032e9c39a ! 3: f87e96e99d branch: add --prune-merged <remote>
@@ Documentation/git-branch.adoc: git branch (-c|-C) [<old-branch>] <new-branch>
git branch (-d|-D) [-r] <branch-name>...
git branch --edit-description [<branch-name>]
git branch --forked <remote>...
-+git branch [-f] --prune-merged <remote>...
++git branch --prune-merged <remote>...
DESCRIPTION
-----------
@@ Documentation/git-branch.adoc: Each _<remote>_ may be either the name of a confi
++
+A branch whose upstream no longer resolves locally is left alone
+(its disappearance is not, on its own, evidence that the work was
-+integrated). With `--force` (or `-f`), the reachability check is
-+skipped and every branch in the candidate set is deleted. The
-+currently checked-out branch in any worktree is always preserved,
-+as is the local branch that mirrors _<remote>_'s default branch.
++integrated). The currently checked-out branch in any worktree is
++always preserved, as is the local branch that mirrors _<remote>_'s
++default branch.
+
`-v`::
`-vv`::
@@ builtin/branch.c: static int collect_forked_branch(const struct reference *ref,
return 0;
}
-+static int prune_merged_branches(int argc, const char **argv, int force,
-+ int quiet)
++static int prune_merged_branches(int argc, const char **argv, int quiet)
+{
+ struct string_list candidates = STRING_LIST_INIT_DUP;
+ struct string_list protected_default_refs = STRING_LIST_INIT_DUP;
@@ builtin/branch.c: static int collect_forked_branch(const struct reference *ref,
+
+ if (deletable.nr)
+ ret = delete_branches(deletable.nr, deletable.v,
-+ 1, force,
++ 1, 0,
+ FILTER_REFS_BRANCHES, quiet,
+ 1, &n_not_merged);
+
+ if (n_not_merged && !quiet)
+ fprintf(stderr,
+ Q_("Skipped %d branch that is not fully merged; "
-+ "re-run with --force to delete it anyway.\n",
++ "delete it with 'git branch -D' if you are sure.\n",
+ "Skipped %d branches that are not fully merged; "
-+ "re-run with --force to delete them anyway.\n",
++ "delete them with 'git branch -D' if you are sure.\n",
+ n_not_merged),
+ n_not_merged);
+
@@ builtin/branch.c: int cmd_branch(int argc,
ret = list_forked_branches(argc, argv);
goto out;
+ } else if (prune_merged) {
-+ ret = prune_merged_branches(argc, argv, force, quiet);
++ ret = prune_merged_branches(argc, argv, quiet);
+ goto out;
} else if (show_current) {
print_current_branch_name();
@@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>'
+ git -C pm-unmerged branch --prune-merged origin 2>err &&
+ test_grep "not fully merged" err &&
+ test_grep "Skipped 1 branch" err &&
-+ test_grep "re-run with --force" err &&
++ test_grep "git branch -D" err &&
+ test_grep ! "If you are sure you want to delete it" err &&
+ git -C pm-unmerged rev-parse --verify refs/heads/wip
+'
+
-+test_expect_success '--prune-merged --force deletes branches regardless of reachability' '
-+ test_when_finished "rm -rf pm-force" &&
-+ git clone pm-upstream pm-force &&
-+ git -C pm-force checkout -b wip origin/wip &&
-+ git -C pm-force branch --set-upstream-to=origin/next wip &&
-+ test_commit -C pm-force local-only &&
-+ git -C pm-force checkout - &&
-+
-+ git -C pm-force branch --force --prune-merged origin &&
-+
-+ test_must_fail git -C pm-force rev-parse --verify refs/heads/wip
-+'
-+
+test_expect_success '--prune-merged skips branches whose upstream is gone' '
+ test_when_finished "rm -rf pm-upstream-gone" &&
+ git clone pm-upstream pm-upstream-gone &&
@@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>'
+ git -C pm-head checkout -b one one-commit &&
+ git -C pm-head branch --set-upstream-to=origin/next one &&
+
-+ git -C pm-head branch --force --prune-merged origin &&
++ git -C pm-head branch --prune-merged origin &&
+
+ git -C pm-head rev-parse --verify refs/heads/one
+'
@@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>'
+ test_when_finished "rm -rf pm-default" &&
+ git clone pm-upstream pm-default &&
+ git -C pm-default checkout --detach &&
-+ git -C pm-default branch --force --prune-merged origin &&
++ git -C pm-default branch --prune-merged origin &&
+ git -C pm-default rev-parse --verify refs/heads/main
+'
+
4: dd33309344 ! 4: 19b6d94fa7 branch: add branch.<name>.pruneMerged opt-out
@@ Documentation/config/branch.adoc: for details).
+ Explicit deletion via `git branch -d` is unaffected.
## Documentation/git-branch.adoc ##
-@@ Documentation/git-branch.adoc: A branch whose upstream no longer resolves locally is left alone
- integrated). With `--force` (or `-f`), the reachability check is
- skipped and every branch in the candidate set is deleted. The
- currently checked-out branch in any worktree is always preserved,
--as is the local branch that mirrors _<remote>_'s default branch.
-+as is any branch with `branch.<name>.pruneMerged` set to `false`,
-+and the local branch that mirrors _<remote>_'s default branch.
+@@ Documentation/git-branch.adoc: against whatever the remote-tracking refs say locally.
+ A branch whose upstream no longer resolves locally is left alone
+ (its disappearance is not, on its own, evidence that the work was
+ integrated). The currently checked-out branch in any worktree is
+-always preserved, as is the local branch that mirrors _<remote>_'s
++always preserved, as is any branch with `branch.<name>.pruneMerged`
++set to `false`, and the local branch that mirrors _<remote>_'s
+ default branch.
`-v`::
- `-vv`::
## builtin/branch.c ##
-@@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int force,
+@@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int quiet)
for_each_string_list_item(item, &candidates) {
const char *short_name = item->string;
struct strbuf full = STRBUF_INIT;
@@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv,
continue;
}
strbuf_release(&full);
-@@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int force,
+@@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int quiet)
upstream = branch ? branch_get_upstream(branch, NULL) : NULL;
if (!upstream ||
!refs_ref_exists(get_main_ref_store(the_repository),
@@ t/t3200-branch.sh: test_expect_success '--prune-merged protects only the default
+ test_grep "Skipping .one." err
+'
+
-+test_expect_success '--prune-merged --force still honours pruneMerged=false' '
-+ test_when_finished "rm -rf pm-optout-force" &&
-+ git clone pm-upstream pm-optout-force &&
-+ git -C pm-optout-force checkout -b wip origin/wip &&
-+ git -C pm-optout-force branch --set-upstream-to=origin/next wip &&
-+ test_commit -C pm-optout-force local-only &&
-+ git -C pm-optout-force checkout - &&
-+ git -C pm-optout-force config branch.wip.pruneMerged false &&
-+
-+ git -C pm-optout-force branch --force --prune-merged origin &&
-+
-+ git -C pm-optout-force rev-parse --verify refs/heads/wip
-+'
-+
+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 &&
5: 6e81ed3147 ! 5: 6ae95d3f98 branch: add --all-remotes flag
@@ Documentation/git-branch.adoc: git branch (-m|-M) [<old-branch>] <new-branch>
git branch (-d|-D) [-r] <branch-name>...
git branch --edit-description [<branch-name>]
-git branch --forked <remote>...
--git branch [-f] --prune-merged <remote>...
+-git branch --prune-merged <remote>...
+git branch --forked (<remote>... | --all-remotes)
-+git branch [-f] --prune-merged (<remote>... | --all-remotes)
++git branch --prune-merged (<remote>... | --all-remotes)
DESCRIPTION
-----------
-@@ Documentation/git-branch.adoc: currently checked-out branch in any worktree is always preserved,
- as is any branch with `branch.<name>.pruneMerged` set to `false`,
- and the local branch that mirrors _<remote>_'s default branch.
+@@ Documentation/git-branch.adoc: always preserved, as is any branch with `branch.<name>.pruneMerged`
+ set to `false`, and the local branch that mirrors _<remote>_'s
+ default branch.
+`--all-remotes`::
+ With `--forked` or `--prune-merged`, act on every
@@ builtin/branch.c: static int list_forked_branches(int argc, const char **argv)
return 0;
}
--static int prune_merged_branches(int argc, const char **argv, int force,
-- int quiet)
+-static int prune_merged_branches(int argc, const char **argv, int quiet)
+static int prune_merged_branches(int argc, const char **argv,
-+ int all_remotes, int force, int quiet)
++ int all_remotes, int quiet)
{
struct string_list candidates = STRING_LIST_INIT_DUP;
struct string_list protected_default_refs = STRING_LIST_INIT_DUP;
-@@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int force,
+@@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int quiet)
int n_not_merged = 0;
int ret = 0;
@@ builtin/branch.c: int cmd_branch(int argc,
+ if (all_remotes && !forked && !prune_merged)
+ die(_("--all-remotes requires --forked or --prune-merged"));
++
+
if (!delete && !rename && !copy && !edit_description && !new_upstream &&
!show_current && !unset_upstream && !forked && !prune_merged &&
@@ builtin/branch.c: int cmd_branch(int argc,
+ ret = list_forked_branches(argc, argv, all_remotes);
goto out;
} else if (prune_merged) {
-- ret = prune_merged_branches(argc, argv, force, quiet);
-+ ret = prune_merged_branches(argc, argv, all_remotes, force, quiet);
+- ret = prune_merged_branches(argc, argv, quiet);
++ ret = prune_merged_branches(argc, argv, all_remotes, quiet);
goto out;
} else if (show_current) {
print_current_branch_name();
--
gitgitgadget
next prev parent reply other threads:[~2026-05-13 19:34 UTC|newest]
Thread overview: 70+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-01 21:35 [PATCH] fetch: add fetch.pruneLocalBranches config Harald Nordgren via GitGitGadget
2026-05-03 22:39 ` Junio C Hamano
2026-05-04 18:28 ` [PATCH] checkout: add --autostash option for branch switching Harald Nordgren
2026-05-10 1:01 ` Junio C Hamano
2026-05-05 7:14 ` [PATCH] fetch: add fetch.pruneLocalBranches config Johannes Sixt
2026-05-04 18:27 ` [PATCH v2 0/6] fetch: add fetch.pruneBranches config Harald Nordgren via GitGitGadget
2026-05-04 18:27 ` [PATCH v2 1/6] branch: add --forked <remote> Harald Nordgren via GitGitGadget
2026-05-04 23:25 ` Kristoffer Haugsbakk
2026-05-04 18:27 ` [PATCH v2 2/6] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-04 18:27 ` [PATCH v2 3/6] branch: add --prune-merged <remote> Harald Nordgren via GitGitGadget
2026-05-04 18:27 ` [PATCH v2 4/6] fetch: add --prune-merged Harald Nordgren via GitGitGadget
2026-05-04 18:27 ` [PATCH v2 5/6] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-04 18:27 ` [PATCH v2 6/6] branch: add --all-remotes flag Harald Nordgren via GitGitGadget
2026-05-05 7:22 ` [PATCH v3 0/6] fetch: add fetch.pruneBranches config Harald Nordgren via GitGitGadget
2026-05-05 7:22 ` [PATCH v3 1/6] branch: add --forked <remote> Harald Nordgren via GitGitGadget
2026-05-05 7:22 ` [PATCH v3 2/6] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-05 7:22 ` [PATCH v3 3/6] branch: add --prune-merged <remote> Harald Nordgren via GitGitGadget
2026-05-05 7:22 ` [PATCH v3 4/6] fetch: add --prune-merged Harald Nordgren via GitGitGadget
2026-05-05 7:22 ` [PATCH v3 5/6] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-05 7:22 ` [PATCH v3 6/6] branch: add --all-remotes flag Harald Nordgren via GitGitGadget
2026-05-05 19:23 ` [PATCH v4 0/6] fetch: add fetch.pruneBranches config Harald Nordgren via GitGitGadget
2026-05-05 19:23 ` [PATCH v4 1/6] branch: add --forked <remote> Harald Nordgren via GitGitGadget
2026-05-05 19:23 ` [PATCH v4 2/6] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-05 19:23 ` [PATCH v4 3/6] branch: add --prune-merged <remote> Harald Nordgren via GitGitGadget
2026-05-05 19:23 ` [PATCH v4 4/6] fetch: add --prune-merged Harald Nordgren via GitGitGadget
2026-05-05 20:48 ` Johannes Sixt
2026-05-05 22:07 ` [PATCH] fetch: add fetch.pruneLocalBranches config Harald Nordgren
2026-05-11 2:59 ` Junio C Hamano
2026-05-11 6:56 ` Harald Nordgren
2026-05-05 19:23 ` [PATCH v4 5/6] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-05 19:23 ` [PATCH v4 6/6] branch: add --all-remotes flag Harald Nordgren via GitGitGadget
2026-05-07 20:14 ` [PATCH v4 0/6] fetch: add fetch.pruneBranches config Harald Nordgren
2026-05-11 6:58 ` [PATCH v5 0/5] branch: prune-merged Harald Nordgren via GitGitGadget
2026-05-11 6:58 ` [PATCH v5 1/5] branch: add --forked <remote> Harald Nordgren via GitGitGadget
2026-05-11 6:58 ` [PATCH v5 2/5] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-11 8:18 ` Junio C Hamano
2026-05-11 8:44 ` [PATCH] fetch: add fetch.pruneLocalBranches config Harald Nordgren
2026-05-11 6:58 ` [PATCH v5 3/5] branch: add --prune-merged <remote> Harald Nordgren via GitGitGadget
2026-05-11 6:58 ` [PATCH v5 4/5] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-11 6:58 ` [PATCH v5 5/5] branch: add --all-remotes flag Harald Nordgren via GitGitGadget
2026-05-11 9:44 ` [PATCH v6 0/5] branch: prune-merged Harald Nordgren via GitGitGadget
2026-05-11 9:44 ` [PATCH v6 1/5] branch: add --forked <remote> Harald Nordgren via GitGitGadget
2026-05-11 9:44 ` [PATCH v6 2/5] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-11 9:44 ` [PATCH v6 3/5] branch: add --prune-merged <remote> Harald Nordgren via GitGitGadget
2026-05-11 9:44 ` [PATCH v6 4/5] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-11 9:44 ` [PATCH v6 5/5] branch: add --all-remotes flag Harald Nordgren via GitGitGadget
2026-05-11 23:20 ` [PATCH v6 0/5] branch: prune-merged Junio C Hamano
2026-05-12 7:35 ` [PATCH] fetch: add fetch.pruneLocalBranches config Harald Nordgren
2026-05-12 8:23 ` [PATCH v7 0/5] branch: prune-merged Harald Nordgren via GitGitGadget
2026-05-12 8:23 ` [PATCH v7 1/5] branch: add --forked <remote> Harald Nordgren via GitGitGadget
2026-05-12 8:23 ` [PATCH v7 2/5] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-12 8:23 ` [PATCH v7 3/5] branch: add --prune-merged <remote> Harald Nordgren via GitGitGadget
2026-05-12 13:53 ` Junio C Hamano
2026-05-12 17:00 ` [PATCH] fetch: add fetch.pruneLocalBranches config Harald Nordgren
2026-05-12 8:23 ` [PATCH v7 4/5] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-12 8:23 ` [PATCH v7 5/5] branch: add --all-remotes flag Harald Nordgren via GitGitGadget
2026-05-12 17:07 ` [PATCH v8 0/5] branch: prune-merged Harald Nordgren via GitGitGadget
2026-05-12 17:07 ` [PATCH v8 1/5] branch: add --forked <remote> Harald Nordgren via GitGitGadget
2026-05-12 17:07 ` [PATCH v8 2/5] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-12 17:07 ` [PATCH v8 3/5] branch: add --prune-merged <remote> Harald Nordgren via GitGitGadget
2026-05-12 17:07 ` [PATCH v8 4/5] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-12 17:07 ` [PATCH v8 5/5] branch: add --all-remotes flag Harald Nordgren via GitGitGadget
2026-05-13 13:46 ` [PATCH v8 0/5] branch: prune-merged Junio C Hamano
2026-05-13 18:57 ` [PATCH] fetch: add fetch.pruneLocalBranches config Harald Nordgren
2026-05-13 19:34 ` Harald Nordgren via GitGitGadget [this message]
2026-05-13 19:34 ` [PATCH v9 1/5] branch: add --forked <remote> Harald Nordgren via GitGitGadget
2026-05-13 19:34 ` [PATCH v9 2/5] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-13 19:34 ` [PATCH v9 3/5] branch: add --prune-merged <remote> Harald Nordgren via GitGitGadget
2026-05-13 19:34 ` [PATCH v9 4/5] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-13 19:34 ` [PATCH v9 5/5] branch: add --all-remotes flag Harald Nordgren via GitGitGadget
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2285.v9.git.git.1778700883.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=haraldnordgren@gmail.com \
--cc=j6t@kdbg.org \
--cc=kristofferhaugsbakk@fastmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox