All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.