Git Tags and Releases

Mark important commits such as releases with tags, choose between lightweight and annotated tags, and share tags with a remote.

Lesson 12 of 30Intermediate18 min read

Commits are identified by long hashes like 52ac9e1, which are perfect for computers but hard for people to remember. When you release version 1.0 of your app, publish a website or hand in a project, you'll want a clear, permanent label on that exact commit, so you can always find, rebuild or compare it later.

That label is a tag. Unlike a branch, a tag never moves: v1.0.0 points at the same commit forever. Tags are how projects mark their releases, and services like GitHub turn them into downloadable release pages.

In this lesson you'll create lightweight and annotated tags, list and inspect them, tag older commits, share tags with a remote, look at the code from a tag, delete tags, and use semantic versioning, the numbering scheme almost every project follows.

Lightweight and annotated tags

Terminal
git tag v0.9                                   # lightweight
git tag -a v1.0.0 -m "First public release"    # annotated
  • A lightweight tag is just a name pointing at a commit, like a bookmark.
  • An annotated tag is a full object stored in Git, with its own message, the tagger's name and email, and the date.

Use annotated tags for releases. They record who released what and why, git describe uses them, and many tools only show annotated tags. Lightweight tags suit temporary, personal markers.

Listing, inspecting and tagging old commits

CommandWhat it does
git tagList all tags alphabetically
git tag -l "v1.*"List tags matching a pattern
git show v1.0.0Show the tag's details and the commit it points to
git tag -a v0.8 9d41e07 -m "..."Tag an older commit by its hash
git describe --tagsDescribe HEAD relative to the latest tag, e.g. v1.0.0-3-g52ac9e1

The git describe output means "three commits after v1.0.0, at commit 52ac9e1", which is handy for build numbers.

Sharing and using tags

git push doesn't send tags by default. Push them explicitly:

Terminal
git push origin v1.0.0      # one tag
git push origin --tags      # every tag

Others receive tags when they fetch. To look at the code exactly as it was at a tag, check it out:

Terminal
git switch --detach v1.0.0

This puts you in detached HEAD state: you're looking at a commit rather than a branch. That's fine for reading or building old code. If you want to make changes from there, create a branch first: git switch -c hotfix-1.0 v1.0.0.

Semantic versioning and deleting tags

Most projects number releases as MAJOR.MINOR.PATCH, called semantic versioning:

  • PATCH (1.0.0 to 1.0.1): bug fixes only.
  • MINOR (1.0.1 to 1.1.0): new features that don't break anything.
  • MAJOR (1.1.0 to 2.0.0): changes that break existing behaviour.

Mistakes happen. Delete a tag locally with git tag -d v1.0.0 and on the remote with git push origin --delete v1.0.0. Avoid changing a tag that others may already have downloaded; it's usually better to release a new version number.

Sorting tags and finding which release has a fix

git tag sorts names alphabetically, which puts v1.10.0 before v1.2.0. Ask for version order instead:

Terminal
git tag --sort=-v:refname     # newest version first

The v:refname key compares the numbers in each name as versions, and the leading - reverses the order so the latest release comes first.

When someone reports that a bug fix isn't in their version, find out which releases include the commit:

Terminal
git tag --contains 52ac9e1

This lists every tag whose history includes that commit. If it prints nothing, the fix hasn't been released yet.

Two details about git describe matter in scripts. Without --tags it only considers annotated tags, which is another reason to annotate releases. And git describe --abbrev=0 prints just the most recent tag name, such as v1.0.0, without the commit count and hash, which is handy when a build needs to know the last version number.

If you'd rather not push tags as a separate step, git push --follow-tags pushes your commits together with any annotated tags that point at commits being pushed. Lightweight tags are left behind, so personal bookmarks stay on your machine.

Git Tags Syntax

Tag commands

git tag -a v1.2.0 -m "Release message"
git tag -a v1.1.0 <commit> -m "..."
git tag -l "v1.*"
git show v1.2.0
git push origin v1.2.0
git push origin --tags
git switch --detach v1.2.0
git tag -d v1.2.0
git push origin --delete v1.2.0

Git Tags Summary

  • Tags give permanent names to specific commits; unlike branches, they don't move.
  • Use annotated tags (git tag -a v1.0.0 -m "...") for releases.
  • git tag, git tag -l "pattern" and git show <tag> list and inspect tags.
  • Push tags explicitly with git push origin <tag> or --tags.
  • Follow semantic versioning: MAJOR.MINOR.PATCH.

Git Tags Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Tagging and inspecting a release

Terminal
git tag -a v1.0.0 -m "First public release"
git tag
git show v1.0.0 --no-patch
Output
v1.0.0
tag v1.0.0
Tagger: Sam Lee <sam@example.com>
Date:   Mon Sep 2 14:20:11 2024 +0100

First public release

commit 52ac9e1d0b7f3a8e9c2d4f6a1b3c5d7e9f0a2b4c (HEAD -> main, tag: v1.0.0)
Author: Sam Lee <sam@example.com>
Date:   Mon Sep 2 13:55:42 2024 +0100

    Add dark colour scheme
Line-by-line explanation
  1. Line 1Create an annotated tag on the current commit.
  2. Line 2List the tags in the repository.
  3. Line 3Show the tag's own details, then the commit it points to. --no-patch hides the file changes.
Example 2

Tagging an older commit and listing by pattern

Terminal
git log --oneline -3
git tag -a v0.9.0 9d41e07 -m "Beta for testers"
git tag -l "v0.*"
git describe --tags
Output
52ac9e1 (HEAD -> main, tag: v1.0.0) Add dark colour scheme
9d41e07 Add contact form
6b2a1f3 Style the navigation bar
v0.9.0
v1.0.0
Line-by-line explanation
  1. Line 2Give the hash to tag a commit from the past.
  2. Line 3-l with a pattern lists only matching tags.
  3. Line 4HEAD is exactly at a tag, so describe prints the tag name alone.
Example 3

Publishing tags and checking out an old release

Terminal
git push origin v1.0.0
git switch --detach v0.9.0
git switch main
Output
To https://github.com/samlee/portfolio.git
 * [new tag]         v1.0.0 -> v1.0.0
HEAD is now at 9d41e07 Add contact form
Switched to branch 'main'
Line-by-line explanation
  1. Line 1Tags must be pushed explicitly. On GitHub, v1.0.0 now appears under Tags.
  2. Line 2Look at the code as it was in the beta. You're in detached HEAD state.
  3. Line 3Return to the main branch when you're done.

Common Git Tags Mistakes

Expecting git push to send tags

Don't
git tag -a v1.1.0 -m "New features"
git push              # the tag stays on your computer
Do
git push origin v1.1.0

Committing in detached HEAD

Don't
git switch --detach v1.0.0
git commit -am "Hotfix"   # commit isn't on any branch and is easy to lose
Do
git switch -c hotfix-1.0.1 v1.0.0
git commit -am "Hotfix"

Moving a published tag

Don't
git tag -f v1.0.0          # retag a different commit after people downloaded it
git push --force origin v1.0.0
Do
git tag -a v1.0.1 -m "Fix missing image"
git push origin v1.0.1

Git Tags in Real Projects

Tags and releases

Libraries on npm, PyPI and Packagist are published from tagged commits, and GitHub turns each tag into a release page with downloadable source code. Deployment pipelines often build and ship automatically when a tag like v2.3.0 is pushed, and changelogs list what changed between one tag and the next.

Git Tags FAQ

How do I list tags along with their messages?

Run git tag -n. It lists each tag followed by the first line of its annotation message; for a lightweight tag, which has no message, it shows the commit's message instead. Use -n5 to show up to five lines of each message.

How do I get tags from a remote?

A normal git fetch or git pull downloads tags that point at commits you're fetching. To download every tag the remote has, run git fetch --tags. Then git tag lists them locally.

How can I tell if a tag is annotated or lightweight?

Run git cat-file -t v1.0.0. It prints tag for an annotated tag, because there's a separate tag object, and commit for a lightweight tag, which points straight at a commit. git show also gives it away: annotated tags show a "Tagger" line and message before the commit.

How do I list tags by date?

Use git for-each-ref --sort=-creatordate --format='%(refname:short) %(creatordate:short)' refs/tags. It lists every tag with its date, newest first. For annotated tags the date is when the tag was made; for lightweight tags it's the date of the commit.

Should version tags start with v?

It's a convention, not a rule. Most projects write v1.2.0, and many release tools expect that prefix, but the semantic versioning specification itself treats the version as just 1.2.0. Pick one style and use it consistently, because tags sort and match by their exact names.

Git Tags Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the command that creates an annotated tag.

git tag  v2.0.0  "Second major release"
Multiple choiceeasy

What's the main difference between a tag and a branch?

Multiple choicemedium

Which kind of tag should you use for a release?

Predict the outputmedium

A project is at version 2.4.1 and you fix a bug without adding features. What's the next version?

Multiple choicehard

How do you share every local tag with origin?

Git Tags Quiz

5 questions to check what you learned.

  1. Which command lists all tags?

    • git tag
    • git tags --all
    • git show tags
    • git log --tags only
  2. What does "detached HEAD" mean?

    • The repository is broken
    • HEAD has been deleted
    • You're on a remote branch
    • You're looking at a specific commit instead of a branch
  3. In semantic versioning, which number changes for a breaking change?

    • MAJOR
    • MINOR
    • PATCH
    • None
  4. How do you tag a commit from the past?

    • You can't
    • git tag --old v0.5
    • git tag -a v0.5 <commit-hash> -m "..."
    • Check it out and wait
  5. What does v1.0.0-3-g52ac9e1 from git describe mean?

    • Version 1.0.3
    • Three commits after the v1.0.0 tag, currently at commit 52ac9e1
    • Three tags called v1.0.0
    • A broken tag