In Git, why does git branch -vv mark a branch's upstream as gone, and how do you clean up?
answer
- The commits are fine; it is bookkeeping
- Config still names a ref
- Fetching adds refs but does not remove them
- Pruning removed the remote-tracking ref
- Delete the branch or unset-upstream
basics
~20 sGone 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 sA 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$ 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
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.
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.
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).
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