skip to content

In Git, why does git branch -vv mark a branch's upstream as gone, and how do you clean up?

level: seniorimportance: should knowfreq 45%

answer

  1. The commits are fine; it is bookkeeping
  2. Config still names a ref
  3. Fetching adds refs but does not remove them
  4. Pruning removed the remote-tracking ref
  5. Delete the branch or unset-upstream

basics

~20 s

Gone means the branch still has upstream configuration pointing at a remote-tracking ref that no longer exists locally — the remote branch was deleted and your clone pruned it. Clean up by deleting the local branch or running git branch --unset-upstream.

solid answer

~40 s

A branch's upstream is config (`branch.<name>.remote` plus `branch.<name>.merge`) resolving to a remote-tracking ref such as `refs/remotes/origin/feature`. When someone deletes `feature` on the server, your clone keeps that ref until it is pruned — by `git fetch --prune`, `fetch.prune = true`, `remote.origin.prune = true`, or `git remote prune origin`. Once pruned, the configuration still names a ref that no longer exists, and `git branch -vv` annotates the branch `[origin/feature: gone]`. It is a bookkeeping marker, not data loss: your local commits are untouched. The usual cleanup is to delete the merged local branch with `git branch -d`, or `git branch --unset-upstream` if you want to keep working on it. The mirror image matters too: until a teammate prunes, *their* clone still lists the deleted branch under `git branch -r`.

code

console · 9 lines
console
$ git fetch --prune
 - [deleted]         (none)     -> origin/feature

$ git branch -vv
* main     a1b2c3d [origin/main] Add parser
  feature  9f8e7d6 [origin/feature: gone] Handle empty input

$ git branch -d feature
Deleted branch feature (was 9f8e7d6).

go deeper

for a junior

Recall that gone means the remote branch is no longer there and that the usual response is to delete your local branch with git branch -d. Your commits are not lost.

for a middle

Explain the chain: upstream config points at a remote-tracking ref, fetch does not delete refs, pruning does, and only afterwards is the branch annotated gone.

for a senior

Diagnose the team-facing version — every clone caches remote branches independently, so deleted branches linger for colleagues until they prune — and script the cleanup with for-each-ref and %(upstream:track).

for a principal

Decide the default: turning on fetch.prune repository-wide keeps everyone's view honest, at the cost of remote-tracking refs vanishing under long-running local work. Pair it with a branch-hygiene convention rather than ad hoc cleanup.

## Three separate things, easily conflated After a branch is deleted on the server there are three distinct pieces of state, and `gone` is about the relationship between two of them: 1. **The branch on the remote** — `refs/heads/feature` in the server's repository. Deleted. 2. **Your remote-tracking ref** — `refs/remotes/origin/feature` in *your* clone. A cache, deleted only by pruning. 3. **Your local branch and its upstream config** — `refs/heads/feature` plus `branch.feature.remote` / `branch.feature.merge`. Untouched by anything the server does. `gone` appears when (3) still points at (2) but (2) has been removed. ## Why pruning is a separate step A plain `git fetch` adds and updates remote-tracking refs; it does not delete them, because deletion is destructive and Git prefers not to do it implicitly. Pruning is opt-in: - `git fetch --prune` (short `-p`) prunes for that invocation. - `fetch.prune = true` makes every fetch prune; `remote.<name>.prune = true` scopes it to one remote. - `git remote prune origin` prunes without fetching anything new. Until one of those runs, `git branch -r` in your clone still lists `origin/feature` and `git log origin/feature` still works — the classic "but I can still see the branch, so it wasn't deleted" confusion. Nothing is wrong; you are reading a cache. ## What gone does and does not mean It means only "the ref your upstream config names is absent". It does **not** mean: - your commits were deleted — they are still reachable from your local branch, and from the reflog even after that; - the remote is unreachable — network state has nothing to do with it; - the branch was merged — Git makes no such claim. Use `git branch --merged main` to ask that question separately. In practice `gone` correlates strongly with "this branch was integrated on the server and the branch deleted afterwards", which is why it is the standard input to local branch cleanup. ## Cleaning up - **Delete the local branch**: `git branch -d feature` refuses if the branch holds commits not reachable from HEAD or its upstream, which is the safety you want; `git branch -D` forces it. `-d` is deliberately conservative — a `gone` upstream removes one of the signals it can use, so verify with `git log main..feature` if you are unsure. - **Keep the branch, drop the link**: `git branch --unset-upstream` removes the configuration so `git status` stops referring to a missing ref. Re-point it later with `git branch -u origin/other`. - **Bulk listing**: `git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads` prints `[gone]` for exactly these branches, which is a cleaner scripting input than parsing `git branch -vv` output. ## The recreate case If someone deletes and later recreates the same branch name from a different commit, your remote-tracking ref goes from stale to `gone` to newly created, and the new `origin/feature` may share no history with your local branch. The result is a diverged status with large counts on both sides and no useful merge base. The tell is a branch that appeared to be `gone` and then reappeared; verify with `git log --oneline origin/feature` before merging anything. ## Interview framing This question separates people who model remote-tracking refs as a **local cache** from people who imagine `git branch -r` as a live view of the server. Say explicitly that fetch does not delete, name at least one prune mechanism, state that `gone` is bookkeeping rather than lost work, and mention that other clones stay stale independently — that last point is what makes deleted branches keep "existing" for teammates.

  • A colleague deleted the branch on the server, yet git branch -r in another clone still lists it. Why?
    Because `refs/remotes/origin/*` is a per-clone cache. A plain fetch adds and updates those refs but never deletes them, so each clone keeps the stale entry until it prunes — `git fetch --prune`, `fetch.prune = true`, or `git remote prune origin`. Nothing about the server changed; the clone is showing what it last saw.
  • How do you list every local branch whose upstream is gone?
    `git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads` prints `[gone]` for exactly those branches, in parseable form. `git branch -vv` shows the same annotation for humans. Prune first, otherwise nothing is marked gone yet. Then delete with `git branch -d`, keeping the refusal on unmerged branches as your safety net.
  • Does a gone upstream mean the branch's commits are lost?
    No. `gone` describes only the missing remote-tracking ref; your local branch still points at its commits, and even after deleting the branch the reflog and `git fsck` can recover them until garbage collection expires them. If you want to know whether the work was integrated, ask directly with `git branch --merged main` or `git log main..feature`.

saying these in an interview costs you the question

  • Thinks gone means the local commits were deleted
  • Believes plain git fetch removes deleted remote branches
  • Says the remote is offline or unreachable
  • Assumes gone proves the branch was merged
  • Thinks the stale ref means the deletion failed on the server

context