skip to content

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

level: middleimportance: must knowfreq 60%

answer

  1. one adds a name, the other adds a record
  2. tagger identity, date, message
  3. cat-file reports a different object type
  4. describe ignores one kind by default
  5. only one kind rides along with follow-tags

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.

solid answer

~40 s

`git tag -a v1.4.0 -m "Release 1.4.0"` writes a **tag object** into the object database: it records who tagged, when, a free-form message, and the object it points at; `git tag -s` additionally signs it so `git tag -v` can verify it. `git tag v1.4.0` creates only a ref under `refs/tags/` whose contents are the commit id — no author, no date, no message. You can tell them apart with `git cat-file -t v1.4.0`, which prints `tag` for annotated and `commit` for lightweight. The practical consequences: annotated tags carry an auditable "who cut this release and why", `git describe` considers only annotated tags unless you pass `--tags`, and `git push --follow-tags` publishes only annotated ones. So releases get annotated tags; lightweight tags are fine as private, disposable bookmarks.

code

bash · 6 lines
bash
git tag v1.4.0-tmp                       # lightweight: just a ref
git tag -a v1.4.0 -m "Release 1.4.0" 9fceb02   # annotated: a tag object

git cat-file -t v1.4.0-tmp   # commit
git cat-file -t v1.4.0       # tag
git tag -n1 -l 'v1.4.*'      # message for annotated, commit subject otherwise

go deeper

for a junior

Know both commands and the headline difference: git tag -a records who tagged, when, and why; a bare git tag records only a name. Use annotated tags for releases.

for a middle

Explain the object-level difference — a tag object in the database versus a bare ref — and demonstrate it with git cat-file -t. Connect it to git describe and --follow-tags behaviour.

for a senior

Argue the provenance case: an annotated, optionally signed tag is the auditable record of what shipped, tagged at an explicit verified commit rather than at a moving HEAD, and never re-pointed once published.

for a principal

Own the convention across repositories — one tag naming scheme, annotated-and-signed for anything consumed downstream, and a build pipeline that derives its version from the tag rather than from a hand-edited file.

## Two things wearing the same name Git calls both of these a "tag", but they are different constructions. A **lightweight tag** is nothing more than a ref: a name under `refs/tags/` whose value is an object id, usually a commit's. Creating one (`git tag v1.4.0`) writes no new object at all. It is the tag equivalent of a branch that never moves. An **annotated tag** (`git tag -a v1.4.0 -m "Release 1.4.0"`) creates a fourth kind of Git object — alongside blobs, trees and commits — called a *tag object*. That object stores the id and type of the thing being tagged, the tag name, a *tagger* identity line (name, email, timestamp) and a message. The ref under `refs/tags/` then points at the tag object, which in turn points at the commit. `git tag -s` (or `tag.gpgSign`/`-a` with signing configured) appends a cryptographic signature over that object's contents, and `git tag -v` verifies it. The cheapest way to see which you have: `git cat-file -t v1.4.0` prints `tag` for an annotated tag and `commit` for a lightweight one. `git show v1.4.0` on an annotated tag prints the tagger block and message before the commit; on a lightweight tag it goes straight to the commit. ## Why the distinction matters for a release **Provenance.** A release tag is a claim: *this exact tree is what we shipped as 1.4.0*. An annotated tag records who made that claim and when, independently of the commit's author and commit dates — which are often someone else's, and often days earlier. When a signature is attached, the claim becomes verifiable rather than merely asserted. **Release notes.** The tag message is a natural home for the short "what's in this release" text, and it lives in the repository rather than in an external system. `git tag -n<n>` lists tags with the first *n* lines of their messages; a lightweight tag shows the commit subject instead, which is usually some unrelated bug fix that happened to be at the tip. **`git describe`.** By default `git describe` walks back for the nearest **annotated** tag; lightweight tags are ignored unless you pass `--tags`. Build systems that stamp a version by calling `git describe` therefore silently disagree with a repository that uses lightweight tags — either producing a stale version from an older annotated tag or failing outright when none exists. **Publication.** `git push --follow-tags` pushes annotated tags reachable from the pushed commits and skips lightweight ones entirely. If your release tags are lightweight, the ergonomic push flow does not carry them, and people fall back to `git push --tags`, which sprays every local bookmark onto the shared remote. **Sorting and tooling.** Both kinds sort with `git tag --sort=-v:refname` (version-aware ordering, so `v1.10.0` sorts above `v1.9.0`), and both work with `git tag --contains <commit>` to answer "which releases include this fix". Those capabilities are not a reason to choose one over the other — the differentiators are metadata, `describe`, signing and push behaviour. ## Where lightweight tags are genuinely fine As private, throwaway markers: `git tag before-rebase` so you can find a commit again, a temporary label while bisecting by hand, a local pin on a state you want to compare against. They cost nothing, carry no ceremony, and you delete them with `git tag -d`. The rule of thumb interviewers want to hear: **annotated for anything you publish, lightweight for anything you would be happy to lose.** ## Practical mechanics Tag the exact commit you verified, not "whatever HEAD is now" — `git tag -a v1.4.0 -m "..." <commit>` takes an explicit commit, which matters when the release branch has moved since the build. Editing a tag is not a thing: `git tag -f` deletes and recreates the ref, and since fetch will not overwrite an existing tag in other clones, a moved published tag diverges silently across machines. If a published tag is wrong, publish a corrected one under a new name. A useful mental check for the interview: a lightweight tag adds a *name*; an annotated tag adds a *record*. Releases need the record. ## What an interviewer probes next Usually one of three things: how you would tell the two apart in an unfamiliar repository (`git cat-file -t`, or `git for-each-ref refs/tags` showing objecttype), why `git describe` came back with an unexpected version (lightweight tags being skipped), or how signing turns the tag into something a downstream consumer can verify rather than trust.

  • How do you tell whether an existing tag in an unfamiliar repository is annotated or lightweight?
    `git cat-file -t v1.4.0` prints `tag` for annotated and `commit` for lightweight. `git for-each-ref refs/tags` shows the object type per ref for the whole namespace, and `git show v1.4.0` reveals a tagger block and tag message before the commit only for annotated tags.
  • Why does tagging an explicit commit id matter more than tagging HEAD during a release?
    The tag is supposed to name the exact tree that was built and verified. If the release branch advanced after the build — a doc fix, a merged backport — tagging HEAD pins a version nobody tested. Passing the commit explicitly, `git tag -a v1.4.0 -m "..." <commit>`, keeps the artifact and the tag in agreement.
  • When is a lightweight tag the right choice?
    For private, disposable markers: a bookmark before a risky rebase, a hand-driven bisect label, a local pin for comparison. They carry no metadata and cost nothing to create or delete. The moment a tag is meant to be published or consumed by tooling, it should be annotated.

saying these in an interview costs you the question

  • Says the only difference is that annotated tags have a message
  • Thinks lightweight tags cannot be pushed at all
  • Believes a tag stores a copy of the source tree
  • Assumes git describe treats both tag kinds identically
  • Uses git tag -f to correct an already-published release tag

context