Tags are named pointers to specific commits in your repository's history. While branches move forward automatically with every new commit, tags are permanent markers that stay fixed to the exact commit they point to. They are most commonly used to mark release points — v1.0, v2.0, v2.1.3 — creating a permanent, human-readable reference to the state of a project at a significant moment.
Two Types of Tags
Git supports two kinds of tags with meaningfully different purposes:
| Type | What It Is | When to Use It |
|---|---|---|
| Lightweight | A simple pointer to a commit — just a name and a hash, nothing more. | Temporary markers, personal bookmarks, or quick references during development. |
| Annotated | A full object stored in Git's database. Contains tagger name, email, date, a message, and can be GPG-signed. | Release versions and any tag meant to be permanent and shared. |
Annotated tags are the recommended default for anything meaningful. They carry context — who tagged it, when, and why — that a lightweight tag cannot.
Creating an Annotated Tag
Use the -a flag with a message:
git tag -a v1.4 -m "Release version 1.4"
To write the message in your editor instead (like a commit message):
git tag -a v1.4
Viewing an annotated tag shows the full tag metadata before the commit it points to:
git show v1.4
tag v1.4
Tagger: Wariz <wariz@example.com>
Date: Sat May 3 20:19:12 2024 -0700
Release version 1.4
commit ca82a6dff817ec66f44342007202690a93763949
Author: Wariz <wariz@example.com>
Date: Mon Mar 17 21:52:11 2024 -0700
Change version number
Creating a Lightweight Tag
Provide only the tag name — no -a, -s, or -m flags:
git tag v1.4-lw
A lightweight tag stores only the commit checksum. Running git show on it skips the tag metadata and goes straight to the commit:
git show v1.4-lw
commit ca82a6dff817ec66f44342007202690a93763949
Author: Wariz <wariz@example.com>
Date: Mon Mar 17 21:52:11 2024 -0700
Change version number
Listing Tags
List all tags in the repository:
git tag
v1.0
v1.2
v1.3
v1.4
v1.4-lw
v1.5
Tags are listed alphabetically. To search for tags matching a pattern, use -l with a wildcard:
git tag -l "v1.8.5*"
v1.8.5
v1.8.5-rc0
v1.8.5-rc1
v1.8.5.1
v1.8.5.2
The -l flag is optional for a plain listing but required when using wildcard patterns.
Tagging a Past Commit
You can tag any commit in your history after the fact by providing its hash at the end of the command:
# Find the commit you want to tag
git log --oneline
# Tag it
git tag -a v1.2 9fceb02 -m "Version 1.2"
This is useful when you forgot to tag a release at the time and need to add the marker retroactively.
Pushing Tags to a Remote
By default, git push does not transfer tags to remote repositories. Tags must be pushed explicitly.
Push a single tag
git push origin v1.4
Push all tags at once
git push origin --tags
This transfers all local tags not already present on the remote. Note that --tags pushes both lightweight and annotated tags together.
To push only annotated tags (which is often preferable since they carry more information):
git push origin --follow-tags
Deleting Tags
Delete a local tag
git tag -d v1.4-lw
This removes the tag from your local repository only. The remote is unaffected.
Delete a remote tag
# Modern syntax (preferred)
git push origin --delete v1.4-lw
# Older syntax (still works)
git push origin :refs/tags/v1.4-lw
Deleting a remote tag requires two steps — first delete it locally, then push the deletion to the remote.
Checking Out a Tag
You can check out a tag to view the state of the project at that exact point:
git checkout v2.0.0
However, this puts your repository in detached HEAD state — meaning you are no longer on any branch. Any commits you make in this state will not belong to a branch and can be lost once you switch away.
Note: switching to 'v2.0.0'.
You are in 'detached HEAD' state.
If you need to make changes based on a tagged version — for example, applying a hotfix to an older release — create a new branch from the tag first:
git checkout -b hotfix-v2.0 v2.0.0
Now you are on a proper branch pointing to the same commit as the tag, and any new commits will be tracked safely.
Semantic Versioning
Tags are most useful when paired with a consistent naming convention. The widely adopted standard is semantic versioning (SemVer), which uses the format vMAJOR.MINOR.PATCH:
| Segment | When to increment |
|---|---|
| MAJOR | Breaking changes — the API or behaviour changes incompatibly |
| MINOR | New features added in a backwards-compatible way |
| PATCH | Bug fixes and small backwards-compatible changes |
Examples: v1.0.0, v1.2.0, v1.2.3, v2.0.0
Pre-release suffixes like -rc1 (release candidate) or -beta are also common: v2.0.0-rc1.
Using SemVer consistently makes your tags meaningful to anyone reading the project history — they can immediately understand the significance of a version change from the number alone.
Summary
| Command | What It Does |
|---|---|
git tag | Lists all tags |
git tag -l "pattern*" | Lists tags matching a pattern |
git tag -a v1.0 -m "msg" | Creates an annotated tag |
git tag v1.0 | Creates a lightweight tag |
git tag -a v1.0 <hash> | Tags a past commit |
git show v1.0 | Shows tag details and the tagged commit |
git push origin v1.0 | Pushes a single tag to remote |
git push origin --tags | Pushes all tags to remote |
git push origin --follow-tags | Pushes only annotated tags to remote |
git tag -d v1.0 | Deletes a local tag |
git push origin --delete v1.0 | Deletes a remote tag |
git checkout v1.0 | Checks out a tag (detached HEAD) |
git checkout -b <branch> v1.0 | Creates a branch from a tag |