What does git fetch --prune do, and why do deleted branches linger without it?
answer
- Fetch updates forward, never deletes
- The stale names live under refs/remotes
- One flag, and a config key to make it automatic
- Local branches are never touched by it
- fetch.prune / remote.<name>.prune
basics
~20 sgit fetch --prune deletes remote-tracking refs under refs/remotes/origin whose branch no longer exists on the remote. Without it a plain fetch only creates and updates those refs, never removes them, so branches deleted upstream keep showing up locally forever.
solid answer
~40 sA normal `git fetch` reconciles your remote-tracking refs *forward* only: it adds refs for new remote branches and moves existing ones, but it has no reason to delete anything, so `origin/old-feature` survives long after the branch was deleted on the server. `git fetch --prune` (or `-p`) adds the deletion step, removing every remote-tracking ref under the fetched refspec that no longer has a counterpart on the remote. `git remote prune origin` does the pruning without fetching. Because forgetting the flag is the normal failure mode, most people set `fetch.prune=true` globally, or `remote.origin.prune` for one remote, so every fetch prunes. Note that pruning removes only the remote-tracking refs — your own local branches are never touched — and tags need the separate `--prune-tags`.
code
console · 4 lines$ git fetch --prune origin
- [deleted] (none) -> origin/feature-login
- [deleted] (none) -> origin/hotfix-42
a1b2c3d..e4f5a6b main -> origin/maingo deeper
Recall that stale origin/ names persist because a plain fetch never deletes, and that git fetch --prune removes the ones whose remote branch is gone.
Explain the mechanics: the refspec bounds what is pruned, only refs under refs/remotes are affected, and fetch.prune or remote.<name>.prune makes it automatic.
Show the operational angle — why a drifting mirror misleads people into branching from stale refs, why setting fetch.prune globally is safe, and why local-branch cleanup stays a separate, deliberate step.
Own the repository-hygiene position: what the team standardises in shared config, whether tag pruning is enabled given how tags are used for releases, and the cost of a cluttered ref namespace at scale.
## The asymmetry that causes the problem Remote-tracking refs under `refs/remotes/origin/` are your local mirror of the remote's branches, refreshed by fetch according to the refspec `+refs/heads/*:refs/remotes/origin/*`. A fetch asks the remote for its refs and updates the ones it hears about. What it does **not** do by default is act on absence: a branch that has vanished from the remote simply is not mentioned in the response, and a plain fetch treats that as "nothing to do" rather than "delete it". The mirror is therefore additive, and it drifts. On a team that merges and deletes a topic branch per change, this accumulates fast. Six months in, tab-completion offers dozens of `origin/` names that no longer exist anywhere, listings are cluttered, and — worse — you can create a local branch from a remote-tracking ref that is months stale and believe you are working from something current. ## What --prune actually deletes `git fetch --prune` (short form `-p`) compares the refs the remote reports against the remote-tracking refs your refspec maps them to, and deletes the ones with no counterpart. Two boundaries matter: - It only prunes within the refspec being fetched. Pruning `origin` does not touch `refs/remotes/upstream/`. - It only prunes **remote-tracking** refs. Your own local branches under `refs/heads/` are never deleted, even if the branch they track is gone. That is deliberate — those may hold unpushed work. Tags are a separate case. Tags fetched into `refs/tags/` are not pruned by `--prune` alone; `--prune-tags` (`-P`) exists for that, and it is aggressive enough that the documentation warns to use it carefully, since it will delete local tags absent from the remote. ## Making it automatic Remembering the flag is the weak point, so Git provides configuration: `fetch.prune=true` makes every fetch prune, and `remote.<name>.prune` does the same for a single remote. The tag equivalents are `fetch.pruneTags` and `remote.<name>.pruneTags`. Setting `fetch.prune=true` globally is a common, low-risk default — it can only remove refs that the remote itself no longer has, and those refs carry no information you cannot re-fetch. If you want to prune without transferring anything, `git remote prune origin` does exactly that. `git fetch --prune --dry-run` shows what would be removed without doing it. ## The consequence for local branches After pruning, a local branch whose upstream has been deleted still exists; Git simply reports its upstream as gone. Cleaning those up is a separate, deliberate step, because deleting a local branch can destroy commits that were never pushed. Keep the two operations distinct in your head and in your answer: pruning is about the mirror of the remote, not about your work. ## Why interviewers ask it It is a small question that separates people who understand remote-tracking refs as a *cache with its own update rules* from people who think of `origin/main` as a live view of the server. The follow-up is usually "so does pruning delete anything of mine?" — and the correct, confident answer is no: only refs under the remote's namespace, all of which can be recreated by fetching again. ## A note on pull `git pull` performs a fetch, so it honours `fetch.prune` and accepts `--prune` too — the pruning belongs to the fetch half. That is another example of the general rule that everything fetch does, pull also does, plus an integration step.
- Does pruning delete local branches whose upstream is gone?No. Pruning removes only remote-tracking refs under `refs/remotes/<remote>/`. A local branch under `refs/heads/` survives; Git just reports its upstream as gone. That separation is deliberate, because a local branch may hold commits that were never pushed, and deleting it could lose them.
- How do you prune stale tags, and why is that riskier?`git fetch --prune --prune-tags`, or the `fetch.pruneTags` / `remote.<name>.pruneTags` config. It is riskier because tags share one flat namespace with no per-remote separation, so pruning deletes local tags that simply do not exist on that remote — including ones you created locally and never pushed.
saying these in an interview costs you the question
- Thinks a plain fetch removes deleted remote branches
- Believes pruning deletes your local branches
- Confuses pruning refs with git gc pruning objects
- Assumes --prune also removes stale tags
- Says origin/x disappearing means the commits are gone