Tags turn cryptic commit hashes into a readable release history. Without them, `git log` is a wall of 40-character identifiers. With them, you get `v1.0.0`, `v1.1.0`, `v2.0.0` sitting right next to the commits they label.
This guide covers what a tag is, the two types Git offers, and the naming convention that keeps release histories clean.
What a Git Tag Is
A tag is a readable label attached to a specific commit—a friendly alias for a SHA-1 hash. Git stores tags as refs, and creating one takes seconds:
“`bash
$ git tag v1.0.0
$ git tag –list
V1.0.0
“`
The single line tags whatever commit HEAD currently points to. Listing tags with `–list` (or `-l`) prints every tag in the repo.
Lightweight vs Annotated Tags
| Feature | Lightweight | Annotated |
|---|---|---|
| Storage | File in `.git/refs/tags` | Full Git object in database |
| Contents | Commit hash only | Hash, message, tagger name/email, date |
| Message | None | Yes—mirrors commit messages |
| GPG signing | No | Yes (with `-s` flag) |
| Best for | Local markers, personal milestones | Public releases, anything shared |
Lightweight tags are the default. When you run `git tag v1.0.0`, Git writes a tiny file inside `.git/refs/tags/` containing only the commit hash. There's no message, no author, no date. Just a sticky note.
Annotated tags are heavier and more useful for anything public. Running `git tag -a v1.0.2` opens your editor so you can attach a release note. The result is a full Git object stored in the database that records the tagger's name and email, a message, and the creation date. Annotated tags also accept GPG signatures—which is why most projects default to them for releases: they're verifiable.
Semantic Versioning in Practice
Tag names are arbitrary, but the convention most teams adopt is Semantic Versioning, written as `vX.Y.Z`:
- X** (major): Bump on breaking changes.
- Y** (minor): Bump when adding backward-compatible features.
- Z** (patch): Bump on backward-compatible bug fixes.
A typical flow:
“`bash
$ git add .
$ git commit -m "Added sample comments."
$ git tag v1.0.1
$ git tag -l
V1.0.0
V1.0.1
“`
Patch bumps cover most day-to-day work. Reserve major bumps for the moments where downstream users need to change their code.
Why Tag Style Matters
For solo scripts and scratch repos, lightweight tags are fine. The moment a tag is shared with anyone else, annotated tags earn their keep. A signed annotated tag answers two questions a lightweight tag can't: who tagged this, and has the tag been tampered with after the fact? For open source releases or anything shipping to clients, that provenance is worth the extra step.
The other quiet cost of skipping annotations is history readability. `git show v1.0.2` on an annotated tag returns the tag message, the tagger, and the signed-off-by chain. On a lightweight tag, you just get the commit. Over a multi-year repo, those messages pile up into a useful changelog without anyone maintaining a separate file.
What to watch: how your hosting platform (GitHub, GitLab, Bitbucket) handles releases through its UI. Most defaults to annotated, but knowing the difference prevents confusion when a colleague creates a tag manually with bare `git tag`.
Cutting a Release
Your team has been merging features all sprint. It's time to ship version 1.1.0. Use the annotated form so the release announcement lives inside Git:
“`bash
$ git tag -a v1.1.0 -m "Release 1.1.0: added bulk export, fixed CSV import crash"
“`
That message travels with the tag forever. Later, `git show v1.1.0` explains why 1.1.0 existed without digging through commits.
For the next patch, an inline annotated tag works the same:
“`bash
$ git tag -a v1.1.1 -m "Patch: revert CSV change for legacy clients"
“`
Bottom Line
Use annotated tags for anything a human or build pipeline will care about. Use lightweight tags for local markers and personal milestones. Pair either with Semantic Versioning and your `git log` finally reads like a release history rather than a hash registry.
Key takeaways
- A git tag is a readable pointer to a commit—friendlier than a SHA-1 hash.
- Lightweight tags are files in `.git/refs/tags`; annotated tags are full objects with metadata and optional GPG signatures.
- Default to annotated tags for any release that leaves your machine; use Semantic Versioning to keep names meaningful.
Frequently asked questions
What is the difference between a lightweight and an annotated git tag?
A lightweight tag is just a file containing the commit hash, while an annotated tag is a full Git object with a message, tagger name, email, date and optional GPG signature.
Do I need GPG to create an annotated tag?
No. GPG is optional and only used if you pass `-s` to sign the tag. An annotated tag without `-s` is still a full object with metadata.
Which tag style should I use for public releases?
Use annotated tags for any release that others consume. They carry a message, the tagger identity and the option of cryptographic verification.
What does the `v` in `v1.0.0` mean?
It is just a naming convention prefix. The Semantic Versioning scheme writes versions as `vMAJOR.MINOR.PATCH`, but the `v` itself is optional and can be dropped.