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>,
	Phillip Wood <phillip.wood123@gmail.com>,
	Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v10 0/4] branch: prune-merged
Date: Thu, 21 May 2026 22:40:00 +0000	[thread overview]
Message-ID: <pull.2285.v10.git.git.1779403204.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2285.v9.git.git.1778700883.gitgitgadget@gmail.com>

 * --forked / --prune-merged now take a branch glob instead of a remote name
   — origin, origin/*, origin/release-* all work. This replaces the
   remote-only form and subsumes the old --all-remotes flag, which has been
   dropped.
 * New --dry-run for --prune-merged.

Harald Nordgren (4):
  branch: add --forked <branch>
  branch: add --prune-merged <branch>
  branch: add branch.<name>.pruneMerged opt-out
  branch: add --dry-run for --prune-merged

 Documentation/config/branch.adoc |   5 +
 Documentation/git-branch.adoc    |  33 ++++
 builtin/branch.c                 | 253 +++++++++++++++++++++++++--
 t/t3200-branch.sh                | 282 +++++++++++++++++++++++++++++++
 4 files changed, 560 insertions(+), 13 deletions(-)


base-commit: aec3f587505a472db67e9462d0702e7d463a449d
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2285%2FHaraldNordgren%2Ffetch-prune-local-branches-v10
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2285/HaraldNordgren/fetch-prune-local-branches-v10
Pull-Request: https://github.com/git/git/pull/2285

Range-diff vs v9:

 1:  9324b26091 ! 1:  f2df159830 branch: add --forked <remote>
     @@ Metadata
      Author: Harald Nordgren <haraldnordgren@gmail.com>
      
       ## Commit message ##
     -    branch: add --forked <remote>
     +    branch: add --forked <branch>
      
     -    List local branches whose configured upstream falls within any of
     -    the given <remote> arguments. <remote> may be either a configured
     -    remote name (matching all of its remote-tracking branches) or a
     -    single remote-tracking branch. Multiple <remote> arguments are
     -    unioned.
     +            git branch --forked <branch>...
      
     -    This is the building block for --prune-merged, which deletes the
     -    listed branches.
     +    lists local branches whose configured upstream matches any
     +    of the given <branch> arguments.
     +
     +    Each <branch> is resolved to the same kind of ref that
     +    branch.<name>.remote and branch.<name>.merge together point at:
     +    a remote-tracking branch (e.g. origin/master), or, for branches
     +    tracking a local upstream, a local branch (e.g. master).
     +    Shell-style globs are also accepted (e.g. 'origin/*'). Multiple
     +    arguments are unioned.
     +
     +    This is the building block for --prune-merged.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
     @@ Documentation/git-branch.adoc: git branch (-m|-M) [<old-branch>] <new-branch>
       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 --forked <branch>...
       
       DESCRIPTION
       -----------
     @@ Documentation/git-branch.adoc: This option is only applicable in non-verbose mod
       	nothing is printed.
       
      +`--forked`::
     -+	List local branches that fork from any of the given _<remote>_
     -+	arguments, that is, those whose configured upstream
     -+	(`branch.<name>.merge`) is one of those remotes' remote-tracking
     -+	branches.
     -++
     -+Each _<remote>_ may be either the name of a configured remote
     -+(e.g. `origin`, meaning any branch tracking a
     -+`refs/remotes/origin/*` ref) or a specific remote-tracking branch
     -+(e.g. `origin/master`). Multiple _<remote>_ arguments are unioned.
     ++	List local branches whose configured upstream matches any
     ++	of the given _<branch>_ arguments. Each argument is either
     ++	a ref (e.g. `origin/master`, `master`) or a shell-style
     ++	glob (e.g. `'origin/*'`). Multiple arguments are unioned.
      +
       `-v`::
       `-vv`::
       `--verbose`::
      
       ## builtin/branch.c ##
     +@@
     + #include "help.h"
     + #include "advice.h"
     + #include "commit-reach.h"
     ++#include "wildmatch.h"
     + 
     + static const char * const builtin_branch_usage[] = {
     + 	N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
      @@ builtin/branch.c: static const char * const builtin_branch_usage[] = {
       	N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
       	N_("git branch [<options>] [-r | -a] [--points-at]"),
       	N_("git branch [<options>] [-r | -a] [--format]"),
     -+	N_("git branch [<options>] --forked <remote>..."),
     ++	N_("git branch [<options>] --forked <branch>..."),
       	NULL
       };
       
     +@@ builtin/branch.c: static int branch_merged(int kind, const char *name,
     + 
     + static int check_branch_commit(const char *branchname, const char *refname,
     + 			       const struct object_id *oid, struct commit *head_rev,
     +-			       int kinds, int force)
     ++			       int kinds, int force, int warn_only,
     ++			       int *n_not_merged)
     + {
     + 	struct commit *rev = lookup_commit_reference(the_repository, oid);
     + 	if (!force && !rev) {
     +@@ builtin/branch.c: static int check_branch_commit(const char *branchname, const char *refname,
     + 		return -1;
     + 	}
     + 	if (!force && !branch_merged(kinds, branchname, rev, head_rev)) {
     +-		error(_("the branch '%s' is not fully merged"), branchname);
     +-		advise_if_enabled(ADVICE_FORCE_DELETE_BRANCH,
     +-				  _("If you are sure you want to delete it, "
     +-				  "run 'git branch -D %s'"), branchname);
     ++		if (warn_only) {
     ++			warning(_("the branch '%s' is not fully merged"),
     ++				branchname);
     ++		} else {
     ++			error(_("the branch '%s' is not fully merged"),
     ++			      branchname);
     ++			advise_if_enabled(ADVICE_FORCE_DELETE_BRANCH,
     ++					  _("If you are sure you want to delete it, "
     ++					  "run 'git branch -D %s'"), branchname);
     ++		}
     ++		if (n_not_merged)
     ++			(*n_not_merged)++;
     + 		return -1;
     + 	}
     + 	return 0;
     +@@ builtin/branch.c: static void delete_branch_config(const char *branchname)
     + }
     + 
     + static int delete_branches(int argc, const char **argv, int force, int kinds,
     +-			   int quiet)
     ++			   int quiet, int warn_only, int *n_not_merged)
     + {
     + 	struct commit *head_rev = NULL;
     + 	struct object_id oid;
     +@@ builtin/branch.c: static int delete_branches(int argc, const char **argv, int force, int kinds,
     + 
     + 		if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
     + 		    check_branch_commit(bname.buf, name, &oid, head_rev, kinds,
     +-					force)) {
     +-			ret = 1;
     ++					force, warn_only, n_not_merged)) {
     ++			if (!warn_only)
     ++				ret = 1;
     + 			goto next;
     + 		}
     + 
      @@ builtin/branch.c: static void copy_or_rename_branch(const char *oldname, const char *newname, int
       	free_worktrees(worktrees);
       }
       
      +static void parse_forked_args(int argc, const char **argv,
     -+			      struct string_list *remote_names,
     -+			      struct string_list *tracking_refs)
     ++			      struct string_list *upstream_patterns)
      +{
      +	int i;
      +
      +	for (i = 0; i < argc; i++) {
      +		const char *arg = argv[i];
     -+		struct remote *remote;
      +		struct object_id oid;
      +		char *full_ref = NULL;
     ++		const char *short_ref;
      +
     -+		remote = remote_get(arg);
     -+		if (remote && remote_is_configured(remote, 0)) {
     -+			string_list_insert(remote_names, remote->name);
     ++		if (has_glob_specials(arg)) {
     ++			string_list_insert(upstream_patterns, arg);
      +			continue;
      +		}
      +
      +		if (repo_dwim_ref(the_repository, arg, strlen(arg), &oid,
      +				  &full_ref, 0) == 1 &&
     -+		    starts_with(full_ref, "refs/remotes/")) {
     -+			string_list_insert(tracking_refs, full_ref);
     ++		    (skip_prefix(full_ref, "refs/heads/", &short_ref) ||
     ++		     skip_prefix(full_ref, "refs/remotes/", &short_ref))) {
     ++			string_list_insert(upstream_patterns, short_ref);
      +			free(full_ref);
      +			continue;
      +		}
      +		free(full_ref);
      +
     -+		die(_("'%s' is neither a configured remote nor a "
     -+		      "remote-tracking branch"), arg);
     ++		die(_("'%s' is not a valid branch or pattern"), arg);
      +	}
      +}
      +
     -+static int branch_is_forked(const char *short_name,
     -+			    const struct string_list *remote_names,
     -+			    const struct string_list *tracking_refs)
     -+{
     -+	struct branch *branch = branch_get(short_name);
     -+	const char *upstream;
     -+
     -+	if (!branch || !branch->remote_name)
     -+		return 0;
     -+
     -+	if (string_list_has_string(remote_names, branch->remote_name))
     -+		return 1;
     -+
     -+	upstream = branch_get_upstream(branch, NULL);
     -+	if (upstream && string_list_has_string(tracking_refs, upstream))
     -+		return 1;
     -+
     -+	return 0;
     -+}
     -+
      +struct forked_cb {
     -+	const struct string_list *remote_names;
     -+	const struct string_list *tracking_refs;
     ++	const struct string_list *upstream_patterns;
      +	struct string_list *out;
      +};
      +
      +static int collect_forked_branch(const struct reference *ref, void *cb_data)
      +{
      +	struct forked_cb *cb = cb_data;
     ++	struct branch *branch;
     ++	const char *upstream, *short_upstream;
     ++	const struct string_list_item *item;
      +
      +	if (ref->flags & REF_ISSYMREF)
      +		return 0;
     -+	if (branch_is_forked(ref->name, cb->remote_names, cb->tracking_refs))
     -+		string_list_append(cb->out, ref->name);
     ++	branch = branch_get(ref->name);
     ++	if (!branch)
     ++		return 0;
     ++	upstream = branch_get_upstream(branch, NULL);
     ++	if (!upstream)
     ++		return 0;
     ++	short_upstream = upstream;
     ++	(void)(skip_prefix(short_upstream, "refs/heads/", &short_upstream) ||
     ++	       skip_prefix(short_upstream, "refs/remotes/", &short_upstream));
     ++
     ++	for_each_string_list_item(item, cb->upstream_patterns)
     ++		if (!wildmatch(item->string, short_upstream, WM_PATHNAME)) {
     ++			string_list_append(cb->out, ref->name)->util =
     ++				xstrdup(upstream);
     ++			return 0;
     ++		}
      +	return 0;
      +}
      +
     -+static int list_forked_branches(int argc, const char **argv)
     ++static void collect_forked_set(int argc, const char **argv,
     ++			       struct string_list *out)
      +{
     -+	struct string_list remote_names = STRING_LIST_INIT_NODUP;
     -+	struct string_list tracking_refs = STRING_LIST_INIT_DUP;
     -+	struct string_list out = STRING_LIST_INIT_DUP;
     -+	struct string_list_item *item;
     ++	struct string_list upstream_patterns = STRING_LIST_INIT_DUP;
      +	struct forked_cb cb = {
     -+		.remote_names = &remote_names,
     -+		.tracking_refs = &tracking_refs,
     -+		.out = &out,
     ++		.upstream_patterns = &upstream_patterns,
     ++		.out = out,
      +	};
      +
     -+	if (!argc)
     -+		die(_("--forked requires at least one <remote>"));
     -+
     -+	parse_forked_args(argc, argv, &remote_names, &tracking_refs);
     ++	parse_forked_args(argc, argv, &upstream_patterns);
      +
      +	refs_for_each_branch_ref(get_main_ref_store(the_repository),
      +				 collect_forked_branch, &cb);
      +
     -+	string_list_sort(&out);
     ++	string_list_clear(&upstream_patterns, 0);
     ++}
     ++
     ++static int list_forked_branches(int argc, const char **argv)
     ++{
     ++	struct string_list out = STRING_LIST_INIT_DUP;
     ++	struct string_list_item *item;
     ++
     ++	if (!argc)
     ++		die(_("--forked requires at least one <branch>"));
     ++
     ++	collect_forked_set(argc, argv, &out);
      +	for_each_string_list_item(item, &out)
      +		puts(item->string);
      +
     -+	string_list_clear(&remote_names, 0);
     -+	string_list_clear(&tracking_refs, 0);
     -+	string_list_clear(&out, 0);
     ++	string_list_clear(&out, 1);
      +	return 0;
      +}
      +
     @@ builtin/branch.c: int cmd_branch(int argc,
       		OPT_BOOL(0, "edit-description", &edit_description,
       			 N_("edit the description for the branch")),
      +		OPT_BOOL(0, "forked", &forked,
     -+			N_("list local branches forked from the given <remote>s")),
     ++			N_("list local branches whose upstream matches the given <branch>...")),
       		OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
       		OPT_MERGED(&filter, N_("print only branches that are merged")),
       		OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
     @@ builtin/branch.c: int cmd_branch(int argc,
       		usage_with_options(builtin_branch_usage, options);
       
      @@ builtin/branch.c: int cmd_branch(int argc,
     + 	if (delete) {
     + 		if (!argc)
       			die(_("branch name required"));
     - 		ret = delete_branches(argc, argv, delete > 1, filter.kind, quiet);
     - 		goto out;
     +-		ret = delete_branches(argc, argv, delete > 1, filter.kind, quiet);
     ++		ret = delete_branches(argc, argv, delete > 1, filter.kind,
     ++				      quiet, 0, NULL);
     ++		goto out;
      +	} else if (forked) {
      +		ret = list_forked_branches(argc, argv);
     -+		goto out;
     + 		goto out;
       	} else if (show_current) {
       		print_current_branch_name();
     - 		ret = 0;
      
       ## t/t3200-branch.sh ##
      @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
     @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
      +	git -C forked branch --track local-one origin/one &&
      +	git -C forked branch --track local-two origin/two &&
      +	git -C forked branch --track local-foreign other/foreign &&
     -+	git -C forked branch detached
     ++	git -C forked branch detached &&
     ++	git -C forked branch --track topic-on-main main
     ++'
     ++
     ++test_expect_success '--forked <remote-tracking-branch> lists matching branches' '
     ++	git -C forked branch --forked origin/one >actual &&
     ++	echo local-one >expect &&
     ++	test_cmp expect actual
     ++'
     ++
     ++test_expect_success '--forked <local-branch> lists branches tracking that local branch' '
     ++	git -C forked branch --forked main >actual &&
     ++	echo topic-on-main >expect &&
     ++	test_cmp expect actual
      +'
      +
     -+test_expect_success '--forked <remote-name> lists branches tracking that remote' '
     -+	git -C forked branch --forked origin >actual &&
     ++test_expect_success '--forked <glob> matches every upstream under the pattern' '
     ++	git -C forked branch --forked "origin/*" >actual &&
      +	cat >expect <<-\EOF &&
      +	local-one
      +	local-two
     @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success '--forked <remote-tracking-branch> lists only matching branches' '
     -+	git -C forked branch --forked origin/one >actual &&
     -+	echo local-one >expect &&
     ++test_expect_success '--forked unions multiple <branch> arguments' '
     ++	git -C forked branch --forked origin/one other/foreign >actual &&
     ++	cat >expect <<-\EOF &&
     ++	local-foreign
     ++	local-one
     ++	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success '--forked unions multiple <remote> arguments' '
     -+	git -C forked branch --forked origin/one other >actual &&
     ++test_expect_success '--forked combines literal and glob arguments' '
     ++	git -C forked branch --forked main "other/*" >actual &&
     ++	cat >expect <<-\EOF &&
     ++	local-foreign
     ++	topic-on-main
     ++	EOF
     ++	test_cmp expect actual
     ++'
     ++
     ++test_expect_success '--forked "*/*" covers every remote-tracking upstream' '
     ++	git -C forked branch --forked "*/*" >actual &&
      +	cat >expect <<-\EOF &&
      +	local-foreign
      +	local-one
     ++	local-two
     ++	main
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success '--forked rejects unknown remote/ref' '
     ++test_expect_success '--forked rejects unknown branch/pattern' '
      +	test_must_fail git -C forked branch --forked nope 2>err &&
     -+	test_grep "neither a configured remote nor a remote-tracking branch" err
     ++	test_grep "not a valid branch or pattern" err
      +'
      +
     -+test_expect_success '--forked requires at least one <remote>' '
     ++test_expect_success '--forked requires at least one <branch>' '
      +	test_must_fail git -C forked branch --forked 2>err &&
     -+	test_grep "at least one <remote>" err
     ++	test_grep "at least one <branch>" err
      +'
      +
       test_done
 2:  2a13e5d4bc < -:  ---------- branch: let delete_branches warn instead of error on bulk refusal
 3:  f87e96e99d ! 2:  718e28c7e0 branch: add --prune-merged <remote>
     @@ Metadata
      Author: Harald Nordgren <haraldnordgren@gmail.com>
      
       ## Commit message ##
     -    branch: add --prune-merged <remote>
     +    branch: add --prune-merged <branch>
      
     -    Delete the local branches that --forked <remote> would list, but
     -    only those whose tip is reachable from their configured upstream
     -    remote-tracking branch (branch.<name>.merge): the work has already
     -    landed on the upstream it tracks, so the local copy is no longer
     -    needed.
     +            git branch --prune-merged <branch>...
      
     -    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, skip the reachability check and delete
     -    every branch in the candidate set. The currently checked-out
     -    branch in any worktree is always preserved, as is the local branch
     -    that mirrors <remote>'s default branch.
     +    deletes the local branches that --forked <branch> would list,
     +    but only those whose tip is reachable from their configured
     +    upstream: the work has already landed on the upstream the
     +    branch tracks, so the local copy is no longer needed.
      
     -    Reachability is read from whatever the remote-tracking refs say
     -    locally, so the natural workflow is
     +    The following branches are always preserved:
     +
     +    * the currently checked-out branch in any worktree;
     +    * any local branch whose name matches the default branch of
     +      any configured remote (the target of
     +      refs/remotes/<remote>/HEAD) -- typically 'main' or
     +      'master';
     +    * any branch whose upstream no longer resolves locally.
     +
     +    Reachability is read from whatever branch.<name>.merge
     +    resolves to locally, which is usually a remote-tracking ref
     +    but may also be a local branch. When the upstream is a
     +    remote-tracking ref, the natural workflow is
      
                  git fetch <remote>
     -            git branch --prune-merged <remote>
     +            git branch --prune-merged <upstream-pattern>
      
     -    with no implicit cleanup driven by fetch itself.
     +    so the upstream reflects the current state before pruning.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
     @@ Documentation/git-branch.adoc
      @@ 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 --prune-merged <remote>...
     + git branch --forked <branch>...
     ++git branch --prune-merged <branch>...
       
       DESCRIPTION
       -----------
     -@@ Documentation/git-branch.adoc: Each _<remote>_ may be either the name of a configured remote
     - `refs/remotes/origin/*` ref) or a specific remote-tracking branch
     - (e.g. `origin/master`). Multiple _<remote>_ arguments are unioned.
     +@@ Documentation/git-branch.adoc: This option is only applicable in non-verbose mode.
     + 	a ref (e.g. `origin/master`, `master`) or a shell-style
     + 	glob (e.g. `'origin/*'`). Multiple arguments are unioned.
       
      +`--prune-merged`::
      +	Delete the local branches that `--forked` would list for
     -+	the same _<remote>_ arguments, but only those whose tip is
     -+	reachable from their configured upstream remote-tracking
     -+	branch (`branch.<name>.merge`). In other words: the work on
     -+	the branch has already landed on the upstream it tracks, so
     -+	the local copy is no longer needed.
     ++	the same _<branch>_ arguments, but only those whose tip is
     ++	reachable from their configured upstream.
     +++
     ++For arguments that refer to remote-tracking branches, run
     ++`git fetch` first so reachability is checked against the
     ++current upstream state; refs are read locally.
      ++
     -+Run `git fetch` first so the upstream remote-tracking branches
     -+reflect the current state of _<remote>_; reachability is checked
     -+against whatever the remote-tracking refs say locally.
     ++The following branches are always preserved:
      ++
     -+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
     -+default branch.
     ++--
     ++* the currently checked-out branch in any worktree;
     ++* any local branch whose name matches the default branch of
     ++  any configured remote (the target of
     ++  `refs/remotes/<remote>/HEAD`) -- typically `main` or
     ++  `master`;
     ++* any branch whose upstream no longer resolves locally.
     ++--
      +
       `-v`::
       `-vv`::
     @@ builtin/branch.c
       #include "column.h"
       #include "utf8.h"
       #include "ref-filter.h"
     +@@ builtin/branch.c: static const char * const builtin_branch_usage[] = {
     + 	N_("git branch [<options>] [-r | -a] [--points-at]"),
     + 	N_("git branch [<options>] [-r | -a] [--format]"),
     + 	N_("git branch [<options>] --forked <branch>..."),
     ++	N_("git branch [<options>] --prune-merged <branch>..."),
     + 	NULL
     + };
     + 
      @@ builtin/branch.c: static int branch_merged(int kind, const char *name,
       	 * any of the following code, but during the transition period,
       	 * a gentle reminder is in order.
     @@ builtin/branch.c: static int branch_merged(int kind, const char *name,
       		if (expect < 0)
       			exit(128);
       		if (expect == merged)
     -@@ builtin/branch.c: static void delete_branch_config(const char *branchname)
     - 	strbuf_release(&buf);
     - }
     - 
     --static int delete_branches(int argc, const char **argv, int force, int kinds,
     -+static int delete_branches(int argc, const char **argv,
     -+			   int no_head_fallback,
     -+			   int force, int kinds,
     - 			   int quiet, int warn_only, int *n_not_merged)
     - {
     - 	struct commit *head_rev = NULL;
     -@@ builtin/branch.c: static int delete_branches(int argc, const char **argv, int force, int kinds,
     - 	}
     - 	branch_name_pos = strcspn(fmt, "%");
     - 
     --	if (!force)
     -+	if (!force && !no_head_fallback)
     - 		head_rev = lookup_commit_reference(the_repository, &head_oid);
     - 
     - 	for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
     -@@ builtin/branch.c: static int delete_branches(int argc, const char **argv, int force, int kinds,
     - 		}
     - 
     - 		if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
     --		    check_branch_commit(bname.buf, name, &oid, head_rev, kinds,
     --					force, warn_only, n_not_merged)) {
     -+		    check_branch_commit(bname.buf, name, &oid, head_rev,
     -+					kinds, force, warn_only, n_not_merged)) {
     - 			if (!warn_only)
     - 				ret = 1;
     - 			goto next;
      @@ builtin/branch.c: static int collect_forked_branch(const struct reference *ref, void *cb_data)
       	return 0;
       }
       
     --static int list_forked_branches(int argc, const char **argv)
     -+static void collect_default_branch_refs(const struct string_list *remote_names,
     -+					struct string_list *out)
     ++static int collect_default_branch_name(struct remote *remote, void *cb_data)
      +{
     ++	struct string_list *protected = cb_data;
      +	struct ref_store *refs = get_main_ref_store(the_repository);
     -+	struct string_list_item *item;
     -+
     -+	for_each_string_list_item(item, remote_names) {
     -+		struct strbuf head = STRBUF_INIT;
     -+		const char *target;
     -+
     -+		strbuf_addf(&head, "refs/remotes/%s/HEAD", item->string);
     -+		target = refs_resolve_ref_unsafe(refs, head.buf,
     -+						 RESOLVE_REF_NO_RECURSE,
     -+						 NULL, NULL);
     -+		if (target && starts_with(target, "refs/remotes/"))
     -+			string_list_insert(out, target);
     -+		strbuf_release(&head);
     ++	struct strbuf head = STRBUF_INIT;
     ++	const char *target;
     ++
     ++	strbuf_addf(&head, "refs/remotes/%s/HEAD", remote->name);
     ++	target = refs_resolve_ref_unsafe(refs, head.buf,
     ++					 RESOLVE_REF_NO_RECURSE, NULL, NULL);
     ++	if (target) {
     ++		const char *leaf = strrchr(target, '/');
     ++		if (leaf)
     ++			string_list_insert(protected, leaf + 1);
      +	}
     ++	strbuf_release(&head);
     ++	return 0;
      +}
      +
     -+static void collect_forked_set(int argc, const char **argv,
     -+			       struct string_list *protected_default_refs,
     -+			       struct string_list *out)
     + static void collect_forked_set(int argc, const char **argv,
     + 			       struct string_list *out)
       {
     - 	struct string_list remote_names = STRING_LIST_INIT_NODUP;
     - 	struct string_list tracking_refs = STRING_LIST_INIT_DUP;
     --	struct string_list out = STRING_LIST_INIT_DUP;
     --	struct string_list_item *item;
     - 	struct forked_cb cb = {
     - 		.remote_names = &remote_names,
     - 		.tracking_refs = &tracking_refs,
     --		.out = &out,
     -+		.out = out,
     - 	};
     - 
     --	if (!argc)
     --		die(_("--forked requires at least one <remote>"));
     --
     - 	parse_forked_args(argc, argv, &remote_names, &tracking_refs);
     - 
     - 	refs_for_each_branch_ref(get_main_ref_store(the_repository),
     - 				 collect_forked_branch, &cb);
     - 
     --	string_list_sort(&out);
     --	for_each_string_list_item(item, &out)
     --		puts(item->string);
     -+	string_list_sort(out);
     -+
     -+	if (protected_default_refs)
     -+		collect_default_branch_refs(&remote_names, protected_default_refs);
     - 
     - 	string_list_clear(&remote_names, 0);
     - 	string_list_clear(&tracking_refs, 0);
     -+}
     -+
     -+static int list_forked_branches(int argc, const char **argv)
     -+{
     -+	struct string_list out = STRING_LIST_INIT_DUP;
     -+	struct string_list_item *item;
     -+
     -+	if (!argc)
     -+		die(_("--forked requires at least one <remote>"));
     -+
     -+	collect_forked_set(argc, argv, NULL, &out);
     -+	for_each_string_list_item(item, &out)
     -+		puts(item->string);
     -+
     - 	string_list_clear(&out, 0);
     +@@ 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 quiet)
      +{
     ++	struct ref_store *refs = get_main_ref_store(the_repository);
      +	struct string_list candidates = STRING_LIST_INIT_DUP;
     -+	struct string_list protected_default_refs = STRING_LIST_INIT_DUP;
     ++	struct string_list protected_default_names = STRING_LIST_INIT_DUP;
      +	struct strvec deletable = STRVEC_INIT;
     ++	struct strbuf buf = STRBUF_INIT;
      +	struct string_list_item *item;
      +	int n_not_merged = 0;
      +	int ret = 0;
      +
      +	if (!argc)
     -+		die(_("--prune-merged requires at least one <remote>"));
     ++		die(_("--prune-merged requires at least one <branch>"));
      +
     -+	collect_forked_set(argc, argv, &protected_default_refs, &candidates);
     ++	collect_forked_set(argc, argv, &candidates);
     ++	for_each_remote(collect_default_branch_name, &protected_default_names);
      +
      +	for_each_string_list_item(item, &candidates) {
      +		const char *short_name = item->string;
     -+		struct strbuf full = STRBUF_INIT;
     -+		struct branch *branch;
     -+		const char *upstream;
     ++		const char *upstream = item->util;
      +
     -+		strbuf_addf(&full, "refs/heads/%s", short_name);
     -+		if (branch_checked_out(full.buf)) {
     -+			strbuf_release(&full);
     ++		strbuf_reset(&buf);
     ++		strbuf_addf(&buf, "refs/heads/%s", short_name);
     ++		if (branch_checked_out(buf.buf))
      +			continue;
     -+		}
     -+		strbuf_release(&full);
     -+
     -+		branch = branch_get(short_name);
     -+		upstream = branch ? branch_get_upstream(branch, NULL) : NULL;
     -+		if (!upstream ||
     -+		    !refs_ref_exists(get_main_ref_store(the_repository),
     -+				     upstream))
     ++
     ++		if (string_list_has_string(&protected_default_names,
     ++					   short_name))
     ++			continue;
     ++
     ++		if (!refs_ref_exists(refs, upstream))
      +			continue;
     -+		if (string_list_has_string(&protected_default_refs, upstream)) {
     -+			const char *leaf = strrchr(upstream, '/');
     -+			if (leaf && !strcmp(leaf + 1, short_name))
     -+				continue;
     -+		}
      +
      +		strvec_push(&deletable, short_name);
      +	}
     ++	strbuf_release(&buf);
      +
      +	if (deletable.nr)
      +		ret = delete_branches(deletable.nr, deletable.v,
     -+				      1, 0,
     -+				      FILTER_REFS_BRANCHES, quiet,
     ++				      0, FILTER_REFS_BRANCHES, quiet,
      +				      1, &n_not_merged);
      +
      +	if (n_not_merged && !quiet)
     @@ builtin/branch.c: static int collect_forked_branch(const struct reference *ref,
      +			n_not_merged);
      +
      +	strvec_clear(&deletable);
     -+	string_list_clear(&candidates, 0);
     -+	string_list_clear(&protected_default_refs, 0);
     ++	string_list_clear(&candidates, 1);
     ++	string_list_clear(&protected_default_names, 0);
      +	return ret;
      +}
      +
     @@ builtin/branch.c: int cmd_branch(int argc,
      @@ builtin/branch.c: int cmd_branch(int argc,
       			 N_("edit the description for the branch")),
       		OPT_BOOL(0, "forked", &forked,
     - 			N_("list local branches forked from the given <remote>s")),
     + 			N_("list local branches whose upstream matches the given <branch>...")),
      +		OPT_BOOL(0, "prune-merged", &prune_merged,
     -+			N_("delete local branches forked from the given <remote>s that are merged into their upstream")),
     ++			N_("delete local branches whose upstream matches the given <branch>... and that are merged into it")),
       		OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
       		OPT_MERGED(&filter, N_("print only branches that are merged")),
       		OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
     @@ builtin/branch.c: int cmd_branch(int argc,
       		usage_with_options(builtin_branch_usage, options);
       
      @@ builtin/branch.c: int cmd_branch(int argc,
     - 	if (delete) {
     - 		if (!argc)
     - 			die(_("branch name required"));
     --		ret = delete_branches(argc, argv, delete > 1, filter.kind,
     -+		ret = delete_branches(argc, argv, 0, delete > 1, filter.kind,
     - 				      quiet, 0, NULL);
     - 		goto out;
       	} else if (forked) {
       		ret = list_forked_branches(argc, argv);
       		goto out;
     @@ builtin/branch.c: int cmd_branch(int argc,
       		ret = 0;
      
       ## t/t3200-branch.sh ##
     -@@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>' '
     - 	test_grep "at least one <remote>" err
     +@@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <branch>' '
     + 	test_grep "at least one <branch>" err
       '
       
      +test_expect_success '--prune-merged: setup' '
     @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>'
      +	git -C pm-merged branch two two-commit &&
      +	git -C pm-merged branch --set-upstream-to=origin/next two &&
      +
     -+	git -C pm-merged branch --prune-merged origin &&
     ++	git -C pm-merged branch --prune-merged "origin/*" &&
      +
      +	test_must_fail git -C pm-merged rev-parse --verify refs/heads/one &&
      +	test_must_fail git -C pm-merged rev-parse --verify refs/heads/two
      +'
      +
     ++test_expect_success '--prune-merged with a literal upstream argument' '
     ++	test_when_finished "rm -rf pm-literal" &&
     ++	git clone pm-upstream pm-literal &&
     ++	git -C pm-literal branch one one-commit &&
     ++	git -C pm-literal branch --set-upstream-to=origin/next one &&
     ++	git -C pm-literal branch keepme one-commit &&
     ++	git -C pm-literal branch --set-upstream-to=origin/main keepme &&
     ++
     ++	git -C pm-literal branch --prune-merged origin/next &&
     ++
     ++	test_must_fail git -C pm-literal rev-parse --verify refs/heads/one &&
     ++	git -C pm-literal rev-parse --verify refs/heads/keepme
     ++'
     ++
     ++test_expect_success '--prune-merged unions multiple <branch> arguments' '
     ++	test_when_finished "rm -rf pm-union" &&
     ++	git clone pm-upstream pm-union &&
     ++	git -C pm-union branch one one-commit &&
     ++	git -C pm-union branch --set-upstream-to=origin/next one &&
     ++	git -C pm-union branch two base &&
     ++	git -C pm-union branch --set-upstream-to=origin/main two &&
     ++
     ++	git -C pm-union branch --prune-merged origin/next origin/main &&
     ++
     ++	test_must_fail git -C pm-union rev-parse --verify refs/heads/one &&
     ++	test_must_fail git -C pm-union rev-parse --verify refs/heads/two
     ++'
     ++
     ++test_expect_success '--prune-merged with a local-branch argument' '
     ++	test_create_repo pm-local &&
     ++	test_when_finished "rm -rf pm-local" &&
     ++	test_commit -C pm-local base &&
     ++	git -C pm-local branch topic base &&
     ++	git -C pm-local config branch.topic.remote . &&
     ++	git -C pm-local config branch.topic.merge refs/heads/main &&
     ++	git -C pm-local checkout --detach &&
     ++
     ++	git -C pm-local branch --prune-merged main &&
     ++
     ++	test_must_fail git -C pm-local rev-parse --verify refs/heads/topic &&
     ++	git -C pm-local rev-parse --verify refs/heads/main
     ++'
     ++
      +test_expect_success '--prune-merged spares branches with un-integrated commits' '
      +	test_when_finished "rm -rf pm-unmerged" &&
      +	git clone pm-upstream pm-unmerged &&
     @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>'
      +	test_commit -C pm-unmerged local-only &&
      +	git -C pm-unmerged checkout - &&
      +
     -+	git -C pm-unmerged branch --prune-merged origin 2>err &&
     ++	git -C pm-unmerged branch --prune-merged "origin/*" 2>err &&
      +	test_grep "not fully merged" err &&
      +	test_grep "Skipped 1 branch" err &&
      +	test_grep "git branch -D" err &&
     @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>'
      +	git -C pm-upstream-gone branch --set-upstream-to=origin/next one &&
      +
      +	git -C pm-upstream-gone update-ref -d refs/remotes/origin/next &&
     -+	git -C pm-upstream-gone branch --prune-merged origin &&
     ++	git -C pm-upstream-gone branch --prune-merged "origin/*" &&
      +
      +	git -C pm-upstream-gone rev-parse --verify refs/heads/one
      +'
     @@ 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 --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 --prune-merged origin &&
     ++	git -C pm-default branch --prune-merged "origin/*" &&
      +	git -C pm-default rev-parse --verify refs/heads/main
      +'
      +
     -+test_expect_success '--prune-merged protects only the default branch by name, not by upstream' '
     ++test_expect_success '--prune-merged protects the default branch by name only' '
      +	test_when_finished "rm -rf pm-default-alias" &&
      +	git clone pm-upstream pm-default-alias &&
      +	git -C pm-default-alias branch --track trunk origin/main &&
      +	git -C pm-default-alias checkout --detach &&
     -+	git -C pm-default-alias branch --prune-merged origin &&
     ++	git -C pm-default-alias branch --prune-merged "origin/*" &&
      +	git -C pm-default-alias rev-parse --verify refs/heads/main &&
      +	test_must_fail git -C pm-default-alias rev-parse --verify refs/heads/trunk
      +'
     ++
     ++test_expect_success '--prune-merged with literal arg also protects default-name' '
     ++	test_when_finished "rm -rf pm-literal-default" &&
     ++	git clone pm-upstream pm-literal-default &&
     ++	git -C pm-literal-default checkout --detach &&
     ++	git -C pm-literal-default branch --prune-merged origin/main &&
     ++	git -C pm-literal-default rev-parse --verify refs/heads/main
     ++'
     ++
     ++test_expect_success '--prune-merged requires at least one <branch>' '
     ++	test_must_fail git -C pm-upstream branch --prune-merged 2>err &&
     ++	test_grep "at least one <branch>" err
     ++'
      +
       test_done
 4:  19b6d94fa7 ! 3:  6e38d7af3a branch: add branch.<name>.pruneMerged opt-out
     @@ Metadata
       ## Commit message ##
          branch: add branch.<name>.pruneMerged opt-out
      
     -    Setting branch.<name>.pruneMerged=false exempts that branch from
     -    --prune-merged, even with --force. Useful for keeping a topic
     -    branch around between rounds.
     +    Setting branch.<name>.pruneMerged=false exempts that branch
     +    from --prune-merged. Useful for topic branches you intend to
     +    develop further after an initial round has been merged
     +    upstream.
      
          Explicit deletion via 'git branch -d' is unaffected.
      
     @@ Documentation/config/branch.adoc: for details).
      +
      +`branch.<name>.pruneMerged`::
      +	If set to `false`, branch _<name>_ is exempt from
     -+	`git branch --prune-merged`.
     -+	Useful for topic branches you intend to develop further after
     -+	an initial round has been merged upstream. Defaults to true.
     -+	Explicit deletion via `git branch -d` is unaffected.
     ++	`git branch --prune-merged`. Defaults to true. Explicit
     ++	deletion via `git branch -d` is unaffected.
      
       ## Documentation/git-branch.adoc ##
     -@@ 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.
     +@@ Documentation/git-branch.adoc: The following branches are always preserved:
     +   any configured remote (the target of
     +   `refs/remotes/<remote>/HEAD`) -- typically `main` or
     +   `master`;
     ++* any branch with `branch.<name>.pruneMerged` set to `false`;
     + * any branch whose upstream no longer resolves locally.
     + --
       
     - `-v`::
      
       ## builtin/branch.c ##
      @@ 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;
     -+		struct strbuf key = STRBUF_INIT;
     - 		struct branch *branch;
     - 		const char *upstream;
     -+		int opt_out = 0;
     + 		const char *upstream = item->util;
     ++		int prune_allowed = 1;
       
     - 		strbuf_addf(&full, "refs/heads/%s", short_name);
     - 		if (branch_checked_out(full.buf)) {
     - 			strbuf_release(&full);
     -+			strbuf_release(&key);
     - 			continue;
     - 		}
     - 		strbuf_release(&full);
     + 		strbuf_reset(&buf);
     + 		strbuf_addf(&buf, "refs/heads/%s", short_name);
      @@ 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),
     --				     upstream))
     -+				     upstream)) {
     -+			strbuf_release(&key);
     + 		if (!refs_ref_exists(refs, upstream))
       			continue;
     -+		}
     - 		if (string_list_has_string(&protected_default_refs, upstream)) {
     - 			const char *leaf = strrchr(upstream, '/');
     --			if (leaf && !strcmp(leaf + 1, short_name))
     -+			if (leaf && !strcmp(leaf + 1, short_name)) {
     -+				strbuf_release(&key);
     - 				continue;
     -+			}
     -+		}
     -+
     -+		strbuf_addf(&key, "branch.%s.prunemerged", short_name);
     -+		if (!repo_config_get_bool(the_repository, key.buf, &opt_out) &&
     -+		    !opt_out) {
     + 
     ++		strbuf_reset(&buf);
     ++		strbuf_addf(&buf, "branch.%s.prunemerged", short_name);
     ++		if (!repo_config_get_bool(the_repository, buf.buf,
     ++					  &prune_allowed) &&
     ++		    !prune_allowed) {
      +			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);
       	}
     + 	strbuf_release(&buf);
      
       ## t/t3200-branch.sh ##
     -@@ t/t3200-branch.sh: test_expect_success '--prune-merged protects only the default branch by name, no
     - 	test_must_fail git -C pm-default-alias rev-parse --verify refs/heads/trunk
     +@@ t/t3200-branch.sh: test_expect_success '--prune-merged requires at least one <branch>' '
     + 	test_grep "at least one <branch>" err
       '
       
      +test_expect_success '--prune-merged honours branch.<name>.pruneMerged=false' '
     @@ t/t3200-branch.sh: test_expect_success '--prune-merged protects only the default
      +	git -C pm-optout branch --set-upstream-to=origin/next two &&
      +	git -C pm-optout config branch.one.pruneMerged false &&
      +
     -+	git -C pm-optout branch --prune-merged origin 2>err &&
     ++	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 &&
 5:  6ae95d3f98 ! 4:  c68d162e22 branch: add --all-remotes flag
     @@ Metadata
      Author: Harald Nordgren <haraldnordgren@gmail.com>
      
       ## Commit message ##
     -    branch: add --all-remotes flag
     +    branch: add --dry-run for --prune-merged
      
     -    Combined with --forked or --prune-merged, --all-remotes acts on
     -    every configured remote, in addition to any explicit <remote>
     -    arguments. Used alone, it errors out.
     +    With --dry-run, --prune-merged prints the branches it would
     +    delete and exits without touching any ref. Useful for
     +    sanity-checking a glob like 'origin/*' before letting it run.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
       ## Documentation/git-branch.adoc ##
     -@@ Documentation/git-branch.adoc: git branch (-m|-M) [<old-branch>] <new-branch>
     - git branch (-c|-C) [<old-branch>] <new-branch>
     +@@ 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 --prune-merged <remote>...
     -+git branch --forked (<remote>... | --all-remotes)
     -+git branch --prune-merged (<remote>... | --all-remotes)
     + git branch --forked <branch>...
     +-git branch --prune-merged <branch>...
     ++git branch --prune-merged [--dry-run] <branch>...
       
       DESCRIPTION
       -----------
     -@@ 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.
     +@@ Documentation/git-branch.adoc: The following branches are always preserved:
     + * any branch whose upstream no longer resolves locally.
     + --
       
     -+`--all-remotes`::
     -+	With `--forked` or `--prune-merged`, act on every
     -+	configured remote in addition to any explicit _<remote>_
     -+	arguments.
     ++`--dry-run`::
     ++	With `--prune-merged`, print the branches that would be
     ++	deleted instead of deleting them.
      +
       `-v`::
       `-vv`::
       `--verbose`::
      
       ## builtin/branch.c ##
     -@@ builtin/branch.c: static void copy_or_rename_branch(const char *oldname, const char *newname, int
     - 	free_worktrees(worktrees);
     +@@ builtin/branch.c: static const char * const builtin_branch_usage[] = {
     + 	N_("git branch [<options>] [-r | -a] [--points-at]"),
     + 	N_("git branch [<options>] [-r | -a] [--format]"),
     + 	N_("git branch [<options>] --forked <branch>..."),
     +-	N_("git branch [<options>] --prune-merged <branch>..."),
     ++	N_("git branch [<options>] --prune-merged [--dry-run] <branch>..."),
     + 	NULL
     + };
     + 
     +@@ builtin/branch.c: static void delete_branch_config(const char *branchname)
       }
       
     -+static int collect_remote_name(struct remote *remote, void *cb_data)
     -+{
     -+	struct string_list *remote_names = cb_data;
     -+	string_list_insert(remote_names, remote->name);
     -+	return 0;
     -+}
     -+
     - static void parse_forked_args(int argc, const char **argv,
     - 			      struct string_list *remote_names,
     - 			      struct string_list *tracking_refs)
     -@@ builtin/branch.c: static void collect_default_branch_refs(const struct string_list *remote_names,
     - 	}
     - }
     - 
     --static void collect_forked_set(int argc, const char **argv,
     -+static void collect_forked_set(int argc, const char **argv, int all_remotes,
     - 			       struct string_list *protected_default_refs,
     - 			       struct string_list *out)
     - {
     -@@ builtin/branch.c: static void collect_forked_set(int argc, const char **argv,
     - 	};
     - 
     - 	parse_forked_args(argc, argv, &remote_names, &tracking_refs);
     -+	if (all_remotes)
     -+		for_each_remote(collect_remote_name, &remote_names);
     - 
     - 	refs_for_each_branch_ref(get_main_ref_store(the_repository),
     - 				 collect_forked_branch, &cb);
     -@@ builtin/branch.c: static void collect_forked_set(int argc, const char **argv,
     - 	string_list_clear(&tracking_refs, 0);
     - }
     - 
     --static int list_forked_branches(int argc, const char **argv)
     -+static int list_forked_branches(int argc, const char **argv, int all_remotes)
     + static int delete_branches(int argc, const char **argv, int force, int kinds,
     +-			   int quiet, int warn_only, int *n_not_merged)
     ++			   int quiet, int warn_only, int dry_run,
     ++			   int *n_not_merged)
       {
     - 	struct string_list out = STRING_LIST_INIT_DUP;
     - 	struct string_list_item *item;
     - 
     --	if (!argc)
     --		die(_("--forked requires at least one <remote>"));
     -+	if (!argc && !all_remotes)
     -+		die(_("--forked requires at least one <remote> or --all-remotes"));
     - 
     --	collect_forked_set(argc, argv, NULL, &out);
     -+	collect_forked_set(argc, argv, all_remotes, NULL, &out);
     - 	for_each_string_list_item(item, &out)
     - 		puts(item->string);
     - 
     + 	struct commit *head_rev = NULL;
     + 	struct object_id oid;
     +@@ builtin/branch.c: static int delete_branches(int argc, const char **argv, int force, int kinds,
     + 			goto next;
     + 		}
     + 
     ++		if (dry_run) {
     ++			printf(_("Would delete branch '%s'\n"),
     ++			       name + branch_name_pos);
     ++			goto next;
     ++		}
     ++
     + 		item = string_list_append(&refs_to_delete, name);
     + 		item->util = xstrdup((flags & REF_ISBROKEN) ? "broken"
     + 				    : (flags & REF_ISSYMREF) ? target
      @@ 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 quiet)
      +static int prune_merged_branches(int argc, const char **argv,
     -+				 int all_remotes, int quiet)
     ++				 int dry_run, int quiet)
       {
     + 	struct ref_store *refs = get_main_ref_store(the_repository);
       	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 quiet)
     - 	int n_not_merged = 0;
     - 	int ret = 0;
     - 
     --	if (!argc)
     --		die(_("--prune-merged requires at least one <remote>"));
     -+	if (!argc && !all_remotes)
     -+		die(_("--prune-merged requires at least one <remote> or --all-remotes"));
     - 
     --	collect_forked_set(argc, argv, &protected_default_refs, &candidates);
     -+	collect_forked_set(argc, argv, all_remotes, &protected_default_refs,
     -+			   &candidates);
     - 
     - 	for_each_string_list_item(item, &candidates) {
     - 		const char *short_name = item->string;
     + 	if (deletable.nr)
     + 		ret = delete_branches(deletable.nr, deletable.v,
     + 				      0, FILTER_REFS_BRANCHES, quiet,
     +-				      1, &n_not_merged);
     ++				      1, dry_run, &n_not_merged);
     + 
     + 	if (n_not_merged && !quiet)
     + 		fprintf(stderr,
      @@ builtin/branch.c: int cmd_branch(int argc,
       	    unset_upstream = 0, show_current = 0, edit_description = 0;
       	int forked = 0;
       	int prune_merged = 0;
     -+	int all_remotes = 0;
     ++	int dry_run = 0;
       	const char *new_upstream = NULL;
       	int noncreate_actions = 0;
       	/* possible options */
      @@ builtin/branch.c: int cmd_branch(int argc,
     - 			N_("list local branches forked from the given <remote>s")),
     + 			N_("list local branches whose upstream matches the given <branch>...")),
       		OPT_BOOL(0, "prune-merged", &prune_merged,
     - 			N_("delete local branches forked from the given <remote>s that are merged into their upstream")),
     -+		OPT_BOOL_F(0, "all-remotes", &all_remotes,
     -+			N_("with --forked or --prune-merged, act on every configured remote"),
     -+			PARSE_OPT_NONEG),
     + 			N_("delete local branches whose upstream matches the given <branch>... and that are merged into it")),
     ++		OPT_BOOL(0, "dry-run", &dry_run,
     ++			N_("with --prune-merged, only print what would be deleted")),
       		OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
       		OPT_MERGED(&filter, N_("print only branches that are merged")),
       		OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
     @@ builtin/branch.c: int cmd_branch(int argc,
       	argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
       			     0);
       
     -+	if (all_remotes && !forked && !prune_merged)
     -+		die(_("--all-remotes requires --forked or --prune-merged"));
     -+
     ++	if (dry_run && !prune_merged)
     ++		die(_("--dry-run requires --prune-merged"));
      +
       	if (!delete && !rename && !copy && !edit_description && !new_upstream &&
       	    !show_current && !unset_upstream && !forked && !prune_merged &&
       	    argc == 0)
      @@ builtin/branch.c: int cmd_branch(int argc,
     - 				      quiet, 0, NULL);
     + 		if (!argc)
     + 			die(_("branch name required"));
     + 		ret = delete_branches(argc, argv, delete > 1, filter.kind,
     +-				      quiet, 0, NULL);
     ++				      quiet, 0, 0, NULL);
       		goto out;
       	} else if (forked) {
     --		ret = list_forked_branches(argc, argv);
     -+		ret = list_forked_branches(argc, argv, all_remotes);
     + 		ret = list_forked_branches(argc, argv);
       		goto out;
       	} else if (prune_merged) {
      -		ret = prune_merged_branches(argc, argv, quiet);
     -+		ret = prune_merged_branches(argc, argv, all_remotes, quiet);
     ++		ret = prune_merged_branches(argc, argv, dry_run, quiet);
       		goto out;
       	} else if (show_current) {
       		print_current_branch_name();
      
       ## t/t3200-branch.sh ##
     -@@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>' '
     - 	test_grep "at least one <remote>" err
     - '
     - 
     -+test_expect_success '--forked --all-remotes covers every configured remote' '
     -+	git -C forked branch --forked --all-remotes >actual &&
     -+	cat >expect <<-\EOF &&
     -+	local-foreign
     -+	local-one
     -+	local-two
     -+	main
     -+	EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success '--forked --all-remotes still validates explicit <remote>' '
     -+	test_must_fail git -C forked branch --forked nope --all-remotes 2>err &&
     -+	test_grep "neither a configured remote nor a remote-tracking branch" err
     -+'
     -+
     -+test_expect_success '--all-remotes alone is rejected' '
     -+	test_must_fail git -C forked branch --all-remotes 2>err &&
     -+	test_grep "requires --forked or --prune-merged" err
     -+'
     -+
     - test_expect_success '--prune-merged: setup' '
     - 	test_create_repo pm-upstream &&
     - 	test_commit -C pm-upstream base &&
      @@ t/t3200-branch.sh: test_expect_success 'branch -d still deletes a pruneMerged=false branch' '
       	test_must_fail git -C pm-optout-d rev-parse --verify refs/heads/one
       '
       
     -+test_expect_success '--prune-merged --all-remotes covers every configured remote' '
     -+	test_when_finished "rm -rf pm-allremotes pm-other" &&
     -+	git clone pm-upstream pm-allremotes &&
     -+	test_create_repo pm-other &&
     -+	test_commit -C pm-other other-base &&
     -+	git -C pm-other checkout -b stable &&
     -+	test_commit -C pm-other foreign-commit &&
     -+	git -C pm-other branch foreign HEAD &&
     -+	git -C pm-other checkout main &&
     ++test_expect_success '--prune-merged --dry-run prints but does not delete' '
     ++	test_when_finished "rm -rf pm-dryrun" &&
     ++	git clone pm-upstream pm-dryrun &&
     ++	git -C pm-dryrun branch one one-commit &&
     ++	git -C pm-dryrun branch --set-upstream-to=origin/next one &&
      +
     -+	git -C pm-allremotes remote add other ../pm-other &&
     -+	git -C pm-allremotes fetch other &&
     -+	git -C pm-allremotes branch one one-commit &&
     -+	git -C pm-allremotes branch --set-upstream-to=origin/next one &&
     -+	git -C pm-allremotes branch foreign other/foreign &&
     -+	git -C pm-allremotes branch --set-upstream-to=other/stable foreign &&
     ++	git -C pm-dryrun branch --prune-merged --dry-run "origin/*" >out &&
     ++	test_grep "Would delete branch .one." out &&
     ++	git -C pm-dryrun rev-parse --verify refs/heads/one
     ++'
     ++
     ++test_expect_success '--prune-merged --dry-run skips un-integrated branches' '
     ++	test_when_finished "rm -rf pm-dryrun-unmerged" &&
     ++	git clone pm-upstream pm-dryrun-unmerged &&
     ++	git -C pm-dryrun-unmerged checkout -b wip origin/next &&
     ++	git -C pm-dryrun-unmerged branch --set-upstream-to=origin/next wip &&
     ++	test_commit -C pm-dryrun-unmerged local-only &&
     ++	git -C pm-dryrun-unmerged checkout - &&
     ++	git -C pm-dryrun-unmerged branch merged one-commit &&
     ++	git -C pm-dryrun-unmerged branch --set-upstream-to=origin/next merged &&
      +
     -+	git -C pm-allremotes branch --prune-merged --all-remotes &&
     ++	git -C pm-dryrun-unmerged branch --prune-merged --dry-run "origin/*" \
     ++		>out 2>err &&
     ++	test_grep "Would delete branch .merged." out &&
     ++	test_grep ! "Would delete branch .wip." out &&
     ++	test_grep "not fully merged" err &&
     ++	git -C pm-dryrun-unmerged rev-parse --verify refs/heads/wip &&
     ++	git -C pm-dryrun-unmerged rev-parse --verify refs/heads/merged
     ++'
      +
     -+	test_must_fail git -C pm-allremotes rev-parse --verify refs/heads/one &&
     -+	test_must_fail git -C pm-allremotes rev-parse --verify refs/heads/foreign
     ++test_expect_success '--dry-run requires --prune-merged' '
     ++	test_must_fail git -C pm-upstream branch --dry-run 2>err &&
     ++	test_grep "requires --prune-merged" err
      +'
      +
       test_done

-- 
gitgitgadget

  parent reply	other threads:[~2026-05-21 22:40 UTC|newest]

Thread overview: 124+ 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               ` [PATCH v9 0/5] branch: prune-merged Harald Nordgren via GitGitGadget
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-18 15:27                   ` Phillip Wood
2026-05-21  9:46                     ` Phillip Wood
2026-05-21 19:16                       ` Harald Nordgren
2026-05-22  9:47                         ` Phillip Wood
2026-05-22 10:51                           ` Harald Nordgren
2026-05-21 12:37                     ` Harald Nordgren
2026-05-21 13:29                     ` Junio C Hamano
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
2026-05-18 15:27                   ` Phillip Wood
2026-05-18  8:14                 ` [PATCH v9 0/5] branch: prune-merged Harald Nordgren
2026-05-21 22:40                 ` Harald Nordgren via GitGitGadget [this message]
2026-05-21 22:40                   ` [PATCH v10 1/4] branch: add --forked <branch> Harald Nordgren via GitGitGadget
2026-05-22  1:52                     ` Junio C Hamano
2026-05-22  6:18                     ` Johannes Sixt
2026-05-22  6:36                       ` Junio C Hamano
2026-05-22 10:49                         ` Harald Nordgren
2026-05-22 11:25                           ` Johannes Sixt
2026-05-21 22:40                   ` [PATCH v10 2/4] branch: add --prune-merged <branch> Harald Nordgren via GitGitGadget
2026-05-22  1:17                     ` Junio C Hamano
2026-05-22  2:51                     ` Junio C Hamano
2026-05-22  2:53                       ` Junio C Hamano
2026-05-22  7:59                         ` Harald Nordgren
2026-05-22 11:58                           ` Junio C Hamano
2026-05-22  2:52                     ` Junio C Hamano
2026-05-21 22:40                   ` [PATCH v10 3/4] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-21 22:40                   ` [PATCH v10 4/4] branch: add --dry-run for --prune-merged Harald Nordgren via GitGitGadget
2026-05-22 11:31                   ` [PATCH v11 0/6] branch: prune-merged Harald Nordgren via GitGitGadget
2026-05-22 11:31                     ` [PATCH v11 1/6] branch: add --forked <branch> Harald Nordgren via GitGitGadget
2026-05-22 11:31                     ` [PATCH v11 2/6] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-05-22 11:31                     ` [PATCH v11 3/6] branch: prepare delete_branches for a bulk caller Harald Nordgren via GitGitGadget
2026-05-22 11:31                     ` [PATCH v11 4/6] branch: add --prune-merged <branch> Harald Nordgren via GitGitGadget
2026-05-22 11:31                     ` [PATCH v11 5/6] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-05-22 11:31                     ` [PATCH v11 6/6] branch: add --dry-run for --prune-merged Harald Nordgren via GitGitGadget
2026-06-02 13:05                     ` [PATCH v11 0/6] branch: prune-merged Phillip Wood
2026-06-02 13:41                       ` Harald Nordgren
2026-06-03  9:04                     ` [PATCH v12 " Harald Nordgren via GitGitGadget
2026-06-03  9:04                       ` [PATCH v12 1/6] branch: add --forked filter for --list mode Harald Nordgren via GitGitGadget
2026-06-05 13:48                         ` Phillip Wood
2026-06-05 17:50                           ` Harald Nordgren
2026-06-03  9:04                       ` [PATCH v12 2/6] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-06-05 13:49                         ` Phillip Wood
2026-06-03  9:04                       ` [PATCH v12 3/6] branch: prepare delete_branches for a bulk caller Harald Nordgren via GitGitGadget
2026-06-05 13:49                         ` Phillip Wood
2026-06-03  9:04                       ` [PATCH v12 4/6] branch: add --prune-merged <branch> Harald Nordgren via GitGitGadget
2026-06-05 13:50                         ` Phillip Wood
2026-06-05 15:04                           ` Phillip Wood
2026-06-03  9:04                       ` [PATCH v12 5/6] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-06-03  9:04                       ` [PATCH v12 6/6] branch: add --dry-run for --prune-merged Harald Nordgren via GitGitGadget
2026-06-05 18:35                       ` [PATCH v13 0/6] branch: prune-merged Harald Nordgren via GitGitGadget
2026-06-05 18:35                         ` [PATCH v13 1/6] branch: add --forked filter for --list mode Harald Nordgren via GitGitGadget
2026-06-05 18:35                         ` [PATCH v13 2/6] branch: let delete_branches warn instead of error on bulk refusal Harald Nordgren via GitGitGadget
2026-06-05 18:35                         ` [PATCH v13 3/6] branch: prepare delete_branches for a bulk caller Harald Nordgren via GitGitGadget
2026-06-05 18:35                         ` [PATCH v13 4/6] branch: add --prune-merged <branch> Harald Nordgren via GitGitGadget
2026-06-05 18:35                         ` [PATCH v13 5/6] branch: add branch.<name>.pruneMerged opt-out Harald Nordgren via GitGitGadget
2026-06-05 18:35                         ` [PATCH v13 6/6] branch: add --dry-run for --prune-merged 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.v10.git.git.1779403204.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=haraldnordgren@gmail.com \
    --cc=j6t@kdbg.org \
    --cc=kristofferhaugsbakk@fastmail.com \
    --cc=phillip.wood123@gmail.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.