skip to content

In Git, how does an annotated tag differ from a lightweight tag?

level: middleimportance: should knowfreq 58%

answer

  1. one creates an object, the other does not
  2. cat-file -t answers it immediately
  3. who tagged, and when?
  4. git describe ignores one of them by default

basics

~20 s

A lightweight tag is only a ref under refs/tags pointing straight at a commit. An annotated tag creates a real tag object holding a tagger, a date, a message and an optional signature, and the ref points at that object.

solid answer

~40 s

`git tag v1.0` creates a **lightweight** tag: a single entry under `refs/tags/` containing a commit hash and nothing else — an alias, effectively a branch name that nobody moves. `git tag -a v1.0 -m "..."` creates an **annotated** tag: Git writes a fourth kind of object into the object database, containing the target object's hash, the target's type, the tag name, a tagger identity with timestamp, and a message; the ref then points at that tag object, which in turn points at the commit. So `git cat-file -t v1.0` prints `tag` for one and `commit` for the other. Annotated tags carry provenance and are the ones `git describe` uses by default, so releases should be annotated.

code

console · 8 lines
console
$ git tag -a v1.0 -m "release one"
$ git cat-file -p v1.0
object d2b5d5322e03b5733ebfa5debcd995cfc63f718f
type commit
tag v1.0
tagger Tester <[email protected]> 1787132500 +0400

release one

go deeper

for a junior

Recall the two commands and the one-line difference: plain git tag makes a bare pointer, git tag -a records a message, a tagger and a date.

for a middle

Explain that an annotated tag is a fourth object type with its own hash, and that the ref points at that object which in turn points at the commit — so the tag must be peeled to reach the commit.

for a senior

Justify the policy: releases get annotated (and signable) tags because provenance and git describe depend on it, and know that tags are inert pointers Git never advances on your behalf.

for a principal

Own tagging as part of the release contract — which artefacts are named, whether the naming record must be attributable and verifiable, and what breaks downstream when a tag is a bare alias with no author or date.

## Two things called "tag" A tag is a ref under the `refs/tags/` namespace, and by convention nothing ever moves it. What differs between the two flavours is *what the ref points at*. **Lightweight**: `git tag v1.0` writes the commit's hash directly into `refs/tags/v1.0`. No object is created. There is no record of who made the tag, when, or why. **Annotated**: `git tag -a v1.0 -m "release one"` creates a **tag object** — the fourth object type alongside blob, tree and commit — and points the ref at that object instead. Printing it shows a small header block and a message: - `object <hash>` — what is being tagged. - `type <objecttype>` — usually `commit`. - `tag <name>` — the name recorded inside the object itself. - `tagger <name> <email> <epoch> <timezone>`. - a blank line, then the tag message. Because the object carries a message and an identity, an annotated tag is a first-class, describable artefact: it has its own hash, and it can be signed so the signature covers the tag record. ## How to tell them apart `git cat-file -t v1.0` prints `tag` for an annotated tag and `commit` for a lightweight one — the quickest check. `git show` on an annotated tag prints the tagger and message before the commit; on a lightweight tag it goes straight to the commit. ## Peeling Since an annotated tag ref points at a tag object rather than a commit, anything that wants the commit must *peel* the tag — follow the `object` line down. Git does this automatically almost everywhere, which is why a tag name works fine as a revision argument. When refs are compacted into the packed-refs file, Git even stores the peeled commit hash on a following line beginning with `^`, so lookups do not need to open the tag object. ## Which to use Use annotated tags for anything anyone else will consume — releases above all. The reasons are concrete: they record who tagged and when, they can be signed, and `git describe` considers only annotated tags unless you pass `--tags`, so a lightweight tag will not name your build. Lightweight tags are appropriate as private bookmarks — a throwaway marker before a risky rebase, for example. ## Details worth knowing - A tag object can point at *any* object type, not just a commit: a tree, a blob, or even another tag object. - The tag name is stored twice — in the ref path and inside the object — so renaming the ref of an annotated tag leaves a stale name inside the object. - Tags are not branches: nothing about a tag makes Git advance it when you commit, and `HEAD` never points into `refs/tags/` in the normal way.

  • How would you check, on the command line, which kind of tag you are looking at?
    Ask for the object type: `git cat-file -t v1.0` prints `tag` for an annotated tag, because the ref points at a tag object, and `commit` for a lightweight one, because the ref points straight at the commit. `git show` also reveals it — an annotated tag prints tagger and message first.
  • Can an annotated tag point at something other than a commit?
    Yes. The tag object records both the target hash and its type, so it can tag a blob, a tree, another tag, or a commit. Tagging a commit is simply the overwhelmingly common case; tools that want the commit peel the tag by following its object line.

saying these in an interview costs you the question

  • Says both tag kinds create an object in the database
  • Calls a tag a branch that Git moves for you
  • Thinks a lightweight tag records who created it
  • Assumes git describe treats both kinds identically

context