In Git, how do you delete a remote branch, and what does git push origin :feature mean?
answer
- One readable flag, one older punctuation form
- Push refspecs are source colon destination
- An empty source means update with nothing
- Your local branch is unaffected
- Teammates still see it until they prune
basics
~20 sUse git push origin --delete feature. The older equivalent, git push origin :feature, is a refspec with an empty source: pushing nothing onto the destination ref deletes it. Both remove only the remote branch, never your local one.
solid answer
~40 sTwo forms do the same thing. `git push origin --delete feature` is the readable one. `git push origin :feature` is the original: a push refspec is `<src>:<dst>`, and leaving the source empty asks the remote to update `feature` with *nothing*, which means delete it. Both also remove your own `refs/remotes/origin/feature`, since your push updated that ref. What they do **not** touch is your local branch `feature` or its commits, and other clones keep their stale `origin/feature` until they run `git fetch --prune`. The receiving side can refuse the deletion — `receive.denyDeletes` rejects deletions outright, and a `pre-receive` or `update` hook can reject any specific ref — in which case the branch stays and you get a rejected line.
code
console · 8 lines$ git push origin --delete feature
To github-style-host:example/repo.git
- [deleted] feature
$ git branch --list feature
feature
$ git push origin :refs/tags/v1.0go deeper
Recall git push origin --delete feature, and that it removes only the branch on the remote. Deleting your own copy is a separate git branch -d.
Explain the colon form through the refspec model — source colon destination, empty source means delete — and note that other clones keep the stale remote-tracking ref until they prune.
Sequence the cleanup safely: verify integration first, delete remote then local, and know that receive.denyDeletes or a pre-receive hook can refuse the deletion by policy.
Decide the team default: enabling fetch.prune everywhere keeps stale branch lists from accumulating, and deciding whether the receiving side permits deletions at all is a policy call rather than an individual one.
## The two spellings ``` git push origin --delete feature git push origin :feature ``` The first is explicit and hard to typo dangerously. The second is the refspec form, and understanding it teaches something about how push works in general. ## Why the colon form deletes Every push is driven by refspecs shaped `<src>:<dst>`: take the local revision `src` and update the remote ref `dst` to it. `git push origin main:main` moves the remote's `main` to your `main`. Now drop the source: `git push origin :feature` says "update the remote ref `feature` to *nothing*", and updating a ref to nothing is deletion. Nothing special-cases the syntax; it falls out of the refspec model. The same works for tags: `git push origin :refs/tags/v1.0` deletes a remote tag, and the destination is worth fully qualifying there so Git does not have to guess between a branch and a tag of the same name. ## What actually gets removed Only the ref on the remote. Specifically: - **Deleted:** `refs/heads/feature` on the remote, and — because your own push updated it — `refs/remotes/origin/feature` in your clone. - **Untouched:** your local branch `refs/heads/feature` and every commit on it; the commit objects on the remote, which merely become unreferenced there and are removed later by that repository's garbage collection, if ever; and every *other* clone's `refs/remotes/origin/feature`, which stays until its owner prunes. That last point is the source of the perennial "I deleted it, why can my colleague still see it?" confusion. Remote-tracking refs are per-clone caches and are only removed by pruning: `git fetch --prune`, `fetch.prune = true`, or `git remote prune origin`. ## When the remote refuses A deletion is a ref update like any other and can be rejected by the receiving repository: - `receive.denyDeletes = true` makes the receiving side refuse every branch deletion. - `receive.denyNonFastForwards` covers rewinds rather than deletions, but appears in the same family of receiver-side guards. - A `pre-receive` or `update` hook can reject the deletion of specific refs by name and print its own message. A refused deletion looks like any other rejected push: the branch remains and Git prints a rejected line with a reason. ## Recovering a deletion Because the commits usually still exist somewhere, deletions are often recoverable. If you still have the branch locally, just push it again. If not, and someone else still has a remote-tracking ref or a local copy, they can push it back under the same name. On the receiving repository itself the reflog for the ref may retain the previous value for a time, though whether you can reach that depends on your access to the machine, which is a separate matter from Git's mechanics. ## Order of operations that avoids surprises A safe habit for cleaning up a finished branch: 1. Confirm the work is integrated: `git log --oneline main..feature` should be empty, or use `git branch --merged main`. 2. Delete the remote branch: `git push origin --delete feature`. 3. Delete the local branch with `git branch -d feature`, which refuses if unmerged commits remain. 4. Tell teammates to `git fetch --prune`, or set `fetch.prune = true` so it happens for them automatically. ## What interviewers listen for The short answer is one command, so the substance is in the follow-through: that the colon form is the refspec model rather than a magic incantation, that the local branch survives, that other clones stay stale until they prune, and that the remote may legitimately refuse. Candidates who answer only with `--delete` and stop there usually have not thought about the refspec at all.
- After deleting the remote branch, why does a colleague's git branch -r still list it?Their `refs/remotes/origin/feature` is a local cache that a plain fetch never deletes. It disappears only when they prune — `git fetch --prune`, `fetch.prune = true`, or `git remote prune origin`. Your own clone dropped the ref automatically because your push is what performed the deletion.
- How do you delete a tag on the remote rather than a branch?`git push origin --delete v1.0`, or the refspec form `git push origin :refs/tags/v1.0`. Fully qualifying the destination as `refs/tags/...` matters when a branch and a tag share a name, since an unqualified `v1.0` would be ambiguous. Deleting a tag on the remote does not delete anyone's local copy of it.
- Can the remote refuse a branch deletion?Yes. `receive.denyDeletes = true` on the receiving repository refuses all branch deletions, and a `pre-receive` or `update` hook can reject specific refs with its own message. The push then reports a rejected line and the branch stays exactly where it was.
saying these in an interview costs you the question
- Thinks the command also deletes the local branch
- Believes deletion is impossible once others fetched it
- Cannot explain the empty source in the colon form
- Assumes a plain fetch removes the branch for teammates
- Thinks the commits are immediately erased from the server