Prune stale remote-tracking references

How-to Last tested 27 October 2026

To prune stale Git remote-tracking branches, fetch with the prune option: it removes local references whose branches no longer exist on the remote under the configured fetch mappings. With the standard remote-tracking mapping it leaves local branches alone; custom mappings change the scope.

Distinguish the three names

A branch on the remote, its local remote-tracking reference and a local branch are different references. In the example, the bare remote has a branch named stale, the working repository records origin/stale, and a separate local branch is named local-copy. Similar names do not make them the same object or give pruning permission to remove all of them.

The fetch reference explains that remote-tracking references are updated according to the configured refspec1. The pruning option removes local references that no longer have corresponding remote references under that mapping2. Read that condition carefully: the command is not an instruction to delete every local branch whose upstream disappeared, nor a promise to clean any particular hosting interface.

Follow the local test

The example creates an ordinary working repository and a bare repository on the same machine. It pushes a branch to that local remote, fetches it and creates a separate local branch at the same commit. To simulate a remote branch being removed elsewhere, the script deletes the branch reference in the fictional bare repository. No remote service or network connection is involved.

Before pruning, the script verifies that the old remote-tracking reference still exists. After fetching with prune, it verifies that the reference is absent and that local-copy still exists. The printed line is a summary of those assertions. It does not report a hosting provider’s branch settings, a team member’s copy or any action on your actual remote.

Inspect the mapping before generalizing

Pruning follows reference mappings, not a universal category called “old branches.” A standard remote configuration commonly maps remote branch heads into a corresponding remote-tracking namespace. A different fetch refspec can map a wider or different set of references. Tag mappings can also matter when they are explicitly included; review the configuration rather than assuming that an option acts only on the names visible in one branch listing.

For a real repository, distinguish a stale tracking reference from a local branch you may still need. The latter can hold commits that exist nowhere else. Before deleting any local branch as a separate decision, inspect its history and determine whether the work is preserved. This page’s test does not delete the local branch and does not establish whether another repository still has the same commits.

When a name disappears

A missing remote-tracking name means the fetched mapping no longer supplies that reference. It does not tell you why the remote branch disappeared, whether it was merged or whether a different branch contains its work. Those are questions for the remote history and your team’s process. Avoid turning a tidy branch listing into an unsupported claim that all associated work is complete.

If the result is not what you expected, stop before making further deletions. Check the selected remote, its refspec and the fetched references. This example demonstrates one standard mapping in a newly created repository; it does not test custom tag mappings, multiple remotes, access failures or a concurrent change to a remote repository.

Separate an observation from a cleanup decision

The remote in this demonstration is a local bare repository that the script controls. Deleting its test reference makes the missing-branch condition repeatable, but it does not model a team member deleting a branch through a hosted service. In real work, a branch can disappear for several reasons. The fetch result establishes the currently advertised references under the selected mapping; it does not supply the reason for the change.

The local-copy branch continues to point at the same commit after the remote-tracking name is pruned. That distinction gives you time to inspect the local history separately. If you later decide a local name is unnecessary, treat that as a second operation with its own safeguards. Check for unique commits, unfinished work and other worktrees before taking action; none of those additional checks is performed by the short pruning example.

An absent remote or an access error is also different from a successful comparison that shows one missing reference. Confirm which remote and configuration you are using before interpreting the result. The test’s successful local fetch does not establish how a network error, changed permissions or a custom fetch mapping would behave. Preserve the relevant branch names and commit identifiers when handing the investigation to someone else.

Run the complete local example

Before running: use an empty directory. This script creates example-work, including a fictional working repository and a local bare remote; it stops if that directory already exists. This test deletes a branch reference from its newly created local bare remote; never substitute an existing repository. Review the commands first and do not point it at a real project.

Download the complete MIT-licensed example and run it with Git 2.56.0. The printed script is the tested file. The two recorded runs used fresh directories and produced the same output; the remote in this example is a local directory.

#!/bin/sh
# SPDX-License-Identifier: MIT
set -eu
export TZ=UTC LC_ALL=C
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
export GIT_AUTHOR_NAME='Example Author' GIT_AUTHOR_EMAIL='author@example.invalid'
export GIT_COMMITTER_NAME='Example Author' GIT_COMMITTER_EMAIL='author@example.invalid'
export GIT_AUTHOR_DATE='2026-10-27T12:00:00+00:00'
export GIT_COMMITTER_DATE='2026-10-27T12:00:00+00:00'
test "$(git --version)" = 'git version 2.56.0'
mkdir example-work
cd example-work
git init --bare -q --template= --initial-branch=main remote.git
git init -q --template= --initial-branch=main project
cd project
git config commit.gpgsign false
git config core.autocrlf false
git remote add origin ../remote.git
printf 'base\n' > note.txt
git add note.txt
git commit -qm 'Base note'
git push -q -u origin main
git branch stale
git push -q origin stale
git fetch -q origin
git branch local-copy origin/stale >/dev/null
git --git-dir=../remote.git update-ref -d refs/heads/stale
git show-ref --verify --quiet refs/remotes/origin/stale
git fetch -q --prune origin
if git show-ref --verify --quiet refs/remotes/origin/stale; then
  printf 'Unexpected remote-tracking reference remains\n'
  exit 1
fi
git show-ref --verify --quiet refs/heads/local-copy
printf 'Pruned origin/stale; local-copy remains\n'

Both runs produced:

Pruned origin/stale; local-copy remains

The script leaves example-work in the directory where it ran, including the local repositories and their data. After inspecting the result, remove that example directory when it is no longer needed. Do not remove a directory with the same name in a real project unless you have verified its contents and decided it is safe to delete.

Not covered on this page

Sources

  1. git-fetch — Git 2.56.0. Git 2.56.0; checked 27 October 2026 UTC. ↩

  2. fetch-options — Git 2.56.0. Git 2.56.0; checked 27 October 2026 UTC. ↩

How we test: methodology. Found a mistake? Corrections.