Tags and Releases
A tag is a permanent name for one commit — typically a release such as v1.0.0. Unlike a branch, a tag never moves, so v1.0.0 always means exactly the code you shipped. Hosting services build their release pages on top of tags.
Two kinds of tag
| Annotated | Lightweight | |
|---|---|---|
| Created with | git tag -a v1.0.0 -m "…" | git tag v1.0.0 |
| Stores | Tagger name, date, a message (and optionally a signature) | Just a pointer to a commit |
| Use for | Releases and anything you publish | Private, temporary bookmarks |
Creating an annotated tag
git tag -a v1.0.0 -m "First public release"
git tag
v1.0.0
git show v1.0.0 --stat
tag v1.0.0
Tagger: Sam Rivera <sam@example.com>
Date: Mon Mar 2 10:10:00 2026 +0000
First public release
commit 715e6d6574ce039c560512d8de0adebe39f87ca9 (HEAD -> main, tag: v1.0.0, origin/main)
Author: Sam Rivera <sam@example.com>
Date: Mon Mar 2 09:49:00 2026 +0000
Add checkout page
checkout.html | 1 +
1 file changed, 1 insertion(+)The tag records who created it, when, and the message, followed by the commit it points to.
Tagging an older commit
Forgot to tag a release when you made it? Name the commit:
git tag -a v0.9.0 -m "Beta for friends and family" HEAD~1
git log --oneline --decorate
715e6d6 (HEAD -> main, tag: v1.0.0, origin/main) Add checkout page 7b5bd75 (tag: v0.9.0) Add basket page 581bb60 Create home page
Listing and finding tags
git tag --list "v1.*"
v1.0.0 v1.1.0 v1.1.1
git describe names the current commit relative to the most recent tag — useful for build version strings:
git describe
v1.1.1-1-g5d52df4
v1.1.1-1-g… reads as "1 commit after v1.1.1, at commit …" (the g stands for Git).
Sharing tags
git push doesn't send tags on its own. Push one tag by name, or all of them:
git push origin v1.0.0
Enumerating objects: 1, done. Counting objects: 100% (1/1), done. Writing objects: 100% (1/1), 169 bytes | 169.00 KiB/s, done. Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 To https://github.com/sam-rivera/shop.git * [new tag] v1.0.0 -> v1.0.0
git push origin --tags
Enumerating objects: 11, done. Counting objects: 100% (11/11), done. Delta compression using up to 10 threads Compressing objects: 100% (7/7), done. Writing objects: 100% (9/9), 972 bytes | 972.00 KiB/s, done. Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 To https://github.com/sam-rivera/shop.git * [new tag] v0.9.0 -> v0.9.0 * [new tag] v1.1.0 -> v1.1.0 * [new tag] v1.1.1 -> v1.1.1
git push --follow-tags pushes your commits together with any annotated tags pointing at them — set push.followTags true to make that the default. Tags arrive automatically when others fetch or pull.
Checking out a tag
Switching to a tag puts you in detached HEAD (lesson 23), since tags can't move. To fix a bug in an old release, create a branch from the tag:
git switch -c hotfix-1.0 v1.0.0
Switched to a new branch 'hotfix-1.0'
Deleting and moving tags
git tag -d v0.9.0 git push origin --delete v0.9.0
Deleted tag 'v0.9.0' (was 3956571) To https://github.com/sam-rivera/shop.git - [deleted] v0.9.0
Avoid changing a tag after you've published it: others may already have the old one, and Git won't update their copy automatically. If a release was wrong, release a new version instead.
Semantic versioning
Most projects name releases MAJOR.MINOR.PATCH, a scheme called semantic versioning:
| Increase | When | Example |
|---|---|---|
| PATCH | Bug fixes only, nothing else changes | 1.1.0 → 1.1.1 |
| MINOR | New features that don't break existing use | 1.1.1 → 1.2.0 |
| MAJOR | Changes that break compatibility | 1.2.0 → 2.0.0 |
Pre-releases get a suffix: 2.0.0-beta.1. The leading v in tag names is a common convention, not part of the version.
GitHub Releases
On GitHub, a release is a tag plus release notes and optional downloadable files. Create one from the repository's Releases page (it can even create the tag for you and generate notes from merged pull requests), or from the terminal:
gh release create v1.1.1 --title "v1.1.1" --generate-notes
Try it yourself
Tag three commits in a repository as v0.1.0, v0.2.0 and v1.0.0 (annotated), make one more commit, and run git describe. Push the tags to a remote, then delete v0.1.0 both locally and remotely.