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

AnnotatedLightweight
Created withgit tag -a v1.0.0 -m "…"git tag v1.0.0
StoresTagger name, date, a message (and optionally a signature)Just a pointer to a commit
Use forReleases and anything you publishPrivate, temporary bookmarks

Creating an annotated tag

$
git tag -a v1.0.0 -m "First public release"
git tag
Output
v1.0.0
$
git show v1.0.0 --stat
Output
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
Output
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.*"
Output
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
Output
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
Output
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
Output
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
Output
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
Output
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:

IncreaseWhenExample
PATCHBug fixes only, nothing else changes1.1.0 → 1.1.1
MINORNew features that don't break existing use1.1.1 → 1.2.0
MAJORChanges that break compatibility1.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.