skip to content

questions

6

In Git, how do you delete a remote branch, and what does git push origin :feature mean?

level: juniorimportance: must knowfreq 65%

answer

  1. One readable flag, one older punctuation form
  2. Push refspecs are source colon destination
  3. An empty source means update with nothing
  4. Your local branch is unaffected
  5. Teammates still see it until they prune

basics

~20 s

Use 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 s

Two 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
console
$ 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.0

go deeper

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context

open as a page

In Git, why is a push rejected as non-fast-forward, and what should you do about it?

level: middleimportance: must knowfreq 85%

basics

~20 s

The remote refuses because the branch's current commit is not an ancestor of the commit you are pushing, so accepting it would drop commits. Fetch, integrate the remote work with a merge or rebase, then push again.

open as a page

In Git, why don't tags travel with a normal git push, and what does --follow-tags do?

level: middleimportance: should knowfreq 40%

basics

~20 s

A push updates only the refs named by its refspec, which for a branch push means refs/heads/… — refs/tags/… is never implied. git push --follow-tags additionally sends annotated tags that are reachable from the pushed commits and missing on the remote.

open as a page

In Git, what does push.default=simple do differently from current and upstream?

level: middleimportance: should knowfreq 42%

basics

~20 s

push.default decides what a bare git push sends. simple, the default, pushes the current branch to a same-named remote branch and refuses when the upstream has a different name; upstream pushes to the upstream whatever it is called; current ignores names entirely.

open as a page

In Git, what does git push origin HEAD:refs/heads/release do?

level: middleimportance: should knowfreq 40%

basics

~10 s

It pushes the commit your HEAD points at to the branch release on origin, creating it if absent, regardless of what your local branch is called. HEAD is the source, refs/heads/release the destination ref.

open as a page

In Git, why does pushing to a non-bare repository's checked-out branch fail by default?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Because receive.denyCurrentBranch defaults to refuse: moving the branch that a working tree has checked out would leave that tree and index disagreeing with HEAD, so the receiving repository rejects the update. Shared remotes should be bare.

open as a page