Subject: [PATCH v3 0/6] fetch: add fetch.pruneBranches config

From: "Harald Nordgren via GitGitGadget" <[email-masked]>
Date: Tue, 05 May 2026 07:22:21 +0000

* s/remote-tracking refs/remote-tracking branches/g

Harald Nordgren (6):
  branch: add --forked <remote>
  branch: let delete_branches warn instead of error on bulk refusal
  branch: add --prune-merged <remote>
  fetch: add --prune-merged
  branch: add branch.<name>.pruneMerged opt-out
  branch: add --all-remotes flag

 Documentation/config/branch.adoc |   7 +
 Documentation/fetch-options.adoc |   8 +
 Documentation/git-branch.adoc    |  32 ++++
 builtin/branch.c                 | 247 +++++++++++++++++++++++++++++--
 builtin/fetch.c                  |  20 +++
 t/t3200-branch.sh                | 215 +++++++++++++++++++++++++++
 t/t5510-fetch.sh                 |  31 ++++
 7 files changed, 549 insertions(+), 11 deletions(-)

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

Range-diff vs v2:

 1:  e9f8d06a2b ! 1:  77e67d4b8b branch: add --forked <remote>
     @@ Commit message
      
          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 refs) or a single
     -    remote-tracking ref. Multiple <remote> arguments are unioned.
     +    remote name (matching all of its remote-tracking branches) or a
     +    single remote-tracking branch. Multiple <remote> arguments are
     +    unioned.
      
          This is the building block for --prune-merged, which deletes the
          listed branches.
     @@ Documentation/git-branch.adoc: This option is only applicable in non-verbose mod
      +	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
     -+	refs.
     ++	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 ref
     ++`refs/remotes/origin/*` ref) or a specific remote-tracking branch
      +(e.g. `origin/master`). Multiple _<remote>_ arguments are unioned.
      +
       `-v`::
     @@ builtin/branch.c: static void copy_or_rename_branch(const char *oldname, const c
      +		free(full_ref);
      +
      +		die(_("'%s' is neither a configured remote nor a "
     -+		      "remote-tracking ref"), arg);
     ++		      "remote-tracking branch"), arg);
      +	}
      +}
      +
     @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success '--forked <remote-tracking-ref> lists only matching branches' '
     ++test_expect_success '--forked <remote-tracking-branch> lists only matching branches' '
      +	git -C forked branch --forked origin/one >actual &&
      +	echo local-one >expect &&
      +	test_cmp expect actual
     @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
      +
      +test_expect_success '--forked rejects unknown remote/ref' '
      +	test_must_fail git -C forked branch --forked nope 2>err &&
     -+	test_grep "neither a configured remote nor a remote-tracking ref" err
     ++	test_grep "neither a configured remote nor a remote-tracking branch" err
      +'
      +
      +test_expect_success '--forked requires at least one <remote>' '
 2:  cd4a7e47af = 2:  807c9f981f branch: let delete_branches warn instead of error on bulk refusal
 3:  c0a5f69eb6 ! 3:  49dc853403 branch: add --prune-merged <remote>
     @@ Commit message
      
          Delete the local branches that --forked <remote> would list,
          refusing any whose tip is not reachable from its upstream
     -    remote-tracking ref. With --force, delete unconditionally. The
     -    currently checked-out branch in any worktree is always preserved.
     +    remote-tracking branch. With --force, delete unconditionally.
     +    The currently checked-out branch in any worktree is always
     +    preserved.
      
          Signed-off-by: Harald Nordgren <[email-masked]>
      
     @@ Documentation/git-branch.adoc: git branch (-c|-C) [<old-branch>] <new-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 ref
     + `refs/remotes/origin/*` ref) or a specific remote-tracking branch
       (e.g. `origin/master`). Multiple _<remote>_ arguments are unioned.
       
      +`--prune-merged`::
      +	Delete the local branches that `--forked` would list for
      +	the same _<remote>_ arguments, but only when the branch's
     -+	push destination remote-tracking ref (the ref `git push`
     ++	push destination remote-tracking branch (the branch `git push`
      +	would update; see `branch_get_push` semantics) no longer
      +	resolves locally. In other words: the branch was pushed
      +	under some name on _<remote>_, and that name has since
      +	been pruned upstream.
      ++
      +By default, the local tip must also be reachable from the
     -+upstream remote-tracking ref (see `--no-merged`); branches with
     ++upstream remote-tracking branch (see `--no-merged`); branches with
      +unpushed commits are refused. With `--force` (or `-f`), delete
      +them regardless. The currently checked-out branch in any worktree
      +is always preserved.
 4:  e979fd238b ! 4:  938bf7c794 fetch: add --prune-merged
     @@ Documentation/fetch-options.adoc: See the PRUNING section below for more details
      +	After a successful fetch, run `git branch --prune-merged
      +	<remote>` for the fetched remote, deleting local branches
      +	that fork from this remote and whose tip is reachable from
     -+	their upstream remote-tracking ref. See linkgit:git-branch[1]
     ++	their upstream remote-tracking branch. See linkgit:git-branch[1]
      +	for the exact selection rules. The currently checked-out
      +	branch is always preserved.
      +
 5:  0bc5ebbe68 ! 5:  b2e7c97298 branch: add branch.<name>.pruneMerged opt-out
     @@ Documentation/git-branch.adoc
      @@ Documentation/git-branch.adoc: Each _<remote>_ may be either the name of a configured remote
       	Delete the local branches that `--forked` would list for
       	the same _<remote>_ arguments, but only when the branch's
     - 	push destination remote-tracking ref (the ref `git push`
     + 	push destination remote-tracking branch (the branch `git push`
      -	would update; see `branch_get_push` semantics) no longer
      -	resolves locally. In other words: the branch was pushed
      -	under some name on _<remote>_, and that name has since
     @@ Documentation/git-branch.adoc: Each _<remote>_ may be either the name of a confi
      +	that name has since been pruned upstream.
       +
      -By default, the local tip must also be reachable from the
     --upstream remote-tracking ref (see `--no-merged`); branches with
     +-upstream remote-tracking branch (see `--no-merged`); branches with
      -unpushed commits are refused. With `--force` (or `-f`), delete
      -them regardless. The currently checked-out branch in any worktree
      -is always preserved.
      +The local tip must also be reachable from the upstream
     -+remote-tracking ref; branches with unpushed commits are refused.
     ++remote-tracking branch; branches with unpushed commits are refused.
      +With `--force` (or `-f`), delete them regardless. The currently
      +checked-out branch in any worktree is always preserved, as is
      +any branch with `branch.<name>.pruneMerged` set to `false`.
 6:  66dac97626 ! 6:  6462642cd0 branch: add --all-remotes flag
     @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <remote>'
      +
      +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 ref" err
     ++	test_grep "neither a configured remote nor a remote-tracking branch" err
      +'
      +
      +test_expect_success '--all-remotes alone is rejected' '

-- 
gitgitgadget