By admin , 28 April 2026

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).