skip to content

questions

6

Why doesn't a new Git tag like v1.4.0 reach the remote after git push, and how do you send it?

level: juniorimportance: must knowfreq 62%

answer

  1. push and tag are separate publications
  2. branches live in one namespace, tags another
  3. the default refspec covers refs/heads only
  4. a flag that follows annotated tags outward
  5. fetch is more generous than push here

basics

~20 s

A plain git push sends branch refs only; tags live in refs/tags and are excluded by the default refspec. Push one explicitly with git push origin v1.4.0, or push a branch plus its reachable annotated tags with git push --follow-tags.

solid answer

~40 s

Creating a tag is a purely local act. The default push refspec covers `refs/heads/*` — branches — so nothing under `refs/tags/*` travels with it, which is why a release tag looks "missing" to everyone else until you publish it. Three ways to publish: `git push origin v1.4.0` names the single tag, `git push origin --tags` pushes every local tag including lightweight scratch ones, and `git push --follow-tags` pushes the branch plus annotated tags reachable from the commits being pushed — the right default for a release, and settable once as `push.followTags = true`. On the receiving side, a normal `git fetch` auto-follows tags that point into the history it just downloaded, so collaborators pick up the release tag without extra flags. Verify publication with `git ls-remote --tags origin`.

code

bash · 5 lines
bash
git tag -a v1.4.0 -m "Release 1.4.0"
git push origin release/1.4          # branch goes, tag does not
git push origin v1.4.0               # publish just this tag
git push --follow-tags origin release/1.4   # branch + reachable annotated tags
git ls-remote --tags origin          # confirm from the remote's point of view

go deeper

for a junior

Remember the two-step: creating a tag is local, publishing it is a separate push. Be able to name git push origin <tag> and to check the result with git ls-remote --tags origin.

for a middle

Explain why the default push refspec covers only refs/heads/*, and contrast --tags with --follow-tags in terms of which tags each selects and why that matters on a shared remote.

for a senior

Show the operational judgment: standardise push.followTags, tag the exact verified commit, and treat published tags as immutable because fetch never overwrites an existing local tag.

for a principal

Own the policy question — who is allowed to create release tags, what naming scheme makes them machine-sortable, and how a build system proves an artifact came from the tagged commit rather than a rebuilt one.

## The one-line cause Tags are refs, and `git push` does not push all refs — it pushes the refs its refspec selects. With no arguments, `git push` uses `push.default` (`simple` in modern Git), which pushes the current branch to its upstream branch. That refspec lives entirely inside `refs/heads/*`. A tag you just created lives at `refs/tags/v1.4.0`. It is simply not in the set of things being sent, so the push succeeds, reports the branch update, and quietly leaves the tag behind on your machine. This surprises people because tagging *feels* like the last act of a release. Mechanically it is two separate publications: the commits, and the name you pinned to one of them. ## The three ways to publish a tag **Name it explicitly.** `git push origin v1.4.0` pushes exactly that ref. Git expands the short name to `refs/tags/v1.4.0` when it is unambiguous; if a branch and a tag share a name you can spell the full ref, `git push origin refs/tags/v1.4.0`, and avoid the ambiguity entirely. **Push all tags.** `git push origin --tags` sends everything under `refs/tags/*`. It is blunt: it publishes lightweight tags you created as bookmarks, tags left over from an experiment, and tags pointing at commits that are not reachable from any published branch. On a shared repository that is noise other people cannot easily clean up, because deleting a published tag requires everyone to prune it. **Push the branch and its annotated tags.** `git push --follow-tags` pushes the refs it would normally push, plus annotated tags that are reachable from the commits being pushed. That is almost exactly the release semantics you want: the tag for the release you are shipping goes out, unrelated local tags stay home, and lightweight scratch tags are ignored because `--follow-tags` only considers annotated ones. Make it permanent with `git config --global push.followTags true`. ## How everyone else gets the tag Fetching is more generous than pushing. A plain `git fetch` performs *tag auto-following*: after downloading the requested refs, Git also downloads any tags that point at objects now in the local history. So once the release tag is on the remote and the commit it names is on a fetched branch, collaborators get it with no special flags. `git fetch --tags` additionally fetches tags that are not reachable from the fetched branches; `--no-tags` turns auto-following off. One important asymmetry: fetch does **not** update a tag that already exists locally with a different value. Tags are treated as immutable names. If you move a published tag and re-push it with force, clones that already have the old value keep the old value silently unless someone forces the update. This is the practical reason for the rule "never move a published tag" — the mechanism does not propagate the move, so different machines end up disagreeing about what `v1.4.0` means, and any build reproduced from the tag is no longer reproducible. ## Deleting and correcting If you must retract a tag before anyone depends on it: `git tag -d v1.4.0` locally, then `git push origin --delete v1.4.0` (equivalently, the older colon refspec `git push origin :refs/tags/v1.4.0`). Deletion does not propagate on fetch either — collaborators keep the deleted tag until they run `git fetch --prune --prune-tags` or delete it by hand. Once a tag is genuinely published and consumed, the safe correction is a new tag on the corrected commit, not a redefinition of the old one. ## Where this sits in a release A typical release sequence: cut or update the release branch, build and verify, then tag the **exact commit** that produced the artifact you tested — `git tag -a v1.4.0 -m "Release 1.4.0"` — and publish branch and tag together with `git push --follow-tags origin release/1.4`. Confirm with `git ls-remote --tags origin`, which asks the remote directly instead of trusting local state; that one command catches the whole class of "we tagged it but never shipped the tag" mistakes. ## What an interviewer is checking That you know a tag is a ref rather than a property of a commit, that push and fetch have deliberately different default policies for the tag namespace, and that you reach for `--follow-tags` rather than the shotgun `--tags`. The follow-up is usually about immutability: why re-pointing a released tag is a bad idea even though Git will let you do it locally.

  • Why is `git push --follow-tags` usually preferred over `git push --tags` when shipping a release?
    `--tags` pushes every local tag, including lightweight bookmarks and tags on commits nobody else has, and published tags are awkward to retract. `--follow-tags` pushes only annotated tags reachable from the commits in this push, so exactly the release tag goes out with the release commits and local clutter stays local.
  • A colleague already fetched v1.4.0, then you force-move that tag to a different commit and push it. What do they see?
    Nothing, by default. Fetch will not overwrite a tag that already exists locally, so they keep the old target while you and fresh clones see the new one, and builds "from v1.4.0" stop being reproducible. That silent divergence is why a published tag should be corrected with a new tag, not moved.
  • How do you check what tags actually exist on the remote rather than locally?
    `git ls-remote --tags origin` queries the remote's refs directly without touching your local refs, so it distinguishes "I created the tag" from "the tag is published". Annotated tags additionally show a `^{}` peeled line giving the commit the tag object points at.

saying these in an interview costs you the question

  • Thinks git push sends every local ref including tags
  • Uses git push --tags routinely on a shared repository
  • Believes tags are attached to commits, not separate refs
  • Moves and force-pushes a released tag to fix it
  • Assumes collaborators must run git fetch --tags to see any tag

context

open as a page

For marking a release in Git, what does an annotated tag give you that a lightweight tag does not?

level: middleimportance: must knowfreq 60%

basics

~20 s

An annotated tag creates a real tag object storing a tagger name, email, date, message and optional signature, and can be verified. A lightweight tag is just a ref file pointing at a commit, carrying no metadata of its own.

open as a page

How do you get a hotfix committed on a release branch back into main, and what breaks if you don't?

level: middleimportance: must knowfreq 70%

basics

~20 s

Propagate it deliberately: merge the release branch into main, or replay the commit with git cherry-pick -x. Skip it and the next release cut from main ships without the fix, so the bug returns as a regression nobody expects.

open as a page

What does git describe output like v1.4.0-14-g2414721 mean, and what does it need to work?

level: middleimportance: should knowfreq 40%

basics

~20 s

It names the nearest reachable tag (v1.4.0), the number of commits since it (14), and the abbreviated commit id prefixed by g for git (2414721). By default it searches annotated tags only, so a repository without one cannot be described.

open as a page

With release/1.4, release/1.5 and main all supported, how should a security fix reach all three?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Commit the fix on the oldest affected line, release/1.4, then merge forward: 1.4 into 1.5, 1.5 into main. Each merge makes the fix reachable, so git branch --contains proves coverage instead of you having to trust three independent cherry-picks.

open as a page

How many long-lived maintenance branches should a project keep, and how do you bound the backport cost?

level: principalimportance: should knowfreq 32%

basics

~20 s

As few as the support promise requires — each extra live line multiplies every fix by another merge or cherry-pick, retest and tag. Bound the cost with a published end-of-life date, strictly ordered merge-up, and no refactoring on maintenance lines.

open as a page