Introduction
Git supports two kinds of tags: lightweight, which are just refs pointing at commits, and annotated, which are full Git objects with metadata. Use annotated tags for releases.
Creating
git tag v1.0.0 # lightweight
git tag -a v1.0.0 -m "1.0 GA" # annotated
git tag -s v1.0.0 -m "Signed" # GPG-signed annotated
git tag -a v1.0.0 a1b2c3d -m "..." # tag a specific commit
What annotated tags store
git cat-file -t v1.0.0
# tag
git cat-file -p v1.0.0
# object <commit-sha>
# type commit
# tag v1.0.0
# tagger Ada <[email protected]> 1714300000 +0000
#
# 1.0 GA
An annotated tag is its own object. It has its own SHA distinct from the commit it points at, a tagger and date, a message, and an optional signature.
What lightweight tags do not store
A lightweight tag is just refs/tags/v1.0.0 containing a commit SHA. There is no message, no tagger, no signature. git describe, git tag -v, and many CI release tools assume annotated tags and behave oddly with lightweight ones.
Inspecting
git show v1.0.0
git tag -l --format='%(refname:short) %(objecttype) %(taggername) %(subject)'
Lightweight tags show commit as object type; annotated tags show tag.
Signing and verifying
git tag -s v1.0.0 -m "Signed"
git tag -v v1.0.0
# gpg: Good signature from "Ada Lovelace <[email protected]>"
SSH signing is also supported in modern Git:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git tag -s v1.1.0 -m "SSH-signed"
Choosing
- Use annotated for releases, anything you publish, anything you sign.
- Use lightweight for ad hoc local bookmarks (e.g. "remember this commit before I rebase").
Pushing tags
git push origin v1.0.0
git push origin --tags
git push --follow-tags # only annotated tags reachable from pushed commits
--follow-tags deliberately ignores lightweight tags, which is usually what you want.
Tag dereferencing
Many commands automatically dereference an annotated tag to the commit it points at; some do not. Use ^{} to force dereferencing in scripts:
git rev-parse v1.0.0 # SHA of the tag object (annotated)
git rev-parse v1.0.0^{} # SHA of the commit it points at
git rev-parse v1.0.0^{commit} # same, with type assertion
git for-each-ref --format='%(refname:short) %(*objectname)' refs/tags
The %(*objectname) placeholder gives the dereferenced commit for tags and is empty for non-tag refs. This pattern is essential for release scripts that need a commit SHA from a tag name.
Common mistakes
Tagging releases with plain git tag v1.0 and missing tagger/date/message in audit logs. Always pass -a or -s. Moving a published tag to a new commit ("force-tag"); downstream users have already cached the old SHA and may not pick up the change. Issue v1.0.1 instead. Using lightweight tags as named bookmarks shared across a team; if you must share, prefer branches or annotated tags. Finally, expecting git describe to work without annotated tags; by default it only counts annotated ones (--tags opts in to lightweight, with caveats).