Git development
 help / color / mirror / Atom feed
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

  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