We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

Git Tags: How to Version and Release Your Code Properly

ThePrimeagen
ThePrimeagenEx-Netflix engineer, NeoVim ricer, and Git rebaser

Last published

Table of Contents

Branches move. Every time you commit, your branch pointer slides forward to the newest commit. But sometimes you want the opposite: a name that's permanently stuck to one specific commit, usually because that commit is a release you'll want to find again later. That's what git tags are for. In this post you'll learn how to create, list, and push tags, and how semantic versioning gives your tags names that actually mean something.

All the content from our Boot.dev courses are available for free here on the blog. This one is the "Tags" chapter of Learn Git 2. If you want to try the far more immersive version of the course, do check it out!

What Is a Git Tag?

A tag is a name linked to a commit that doesn't move between commits, unlike a branch. Tags can be created and deleted, but not modified.

That immutability matters. A branch is a moving pointer that follows your work. A tag is a fixed pointer: once it's on a commit, it stays there forever (or until you delete it).

Branch Tag
Points to a commit Yes Yes
Moves when you commit Yes No
Can be modified Yes No, only deleted/recreated

Under the hood, both are just references to a commit hash. If you want to see how Git stores all this, read up on Git internals.

How to Create and List Tags

To list all current tags:

git tag

To create a tag on the current commit:

git tag -a "tag name" -m "tag message"

The -a flag creates an annotated tag, which stores extra metadata: the tagger's name, email, date, and a message. Without -a, you get a lightweight tag, which is just a name pointing at a commit. For anything you care about (like releases), use annotated tags.

Say your latest commit is a release candidate you want to mark:

git tag -a candidate -m "ready for release"

Tags show up right in your git log:

git log --oneline
# 7ac2ad2 (HEAD -> main, tag: candidate) Prepare release
# 3f9be21 Add login page

Because tags can't be modified, "fixing" a tag means deleting it and creating a new one:

git tag -d candidate

What Is Semantic Versioning?

It's kinda weird to just name tags any old thing. We're developers, we like structure, sameness, and sometimes even bike-shedding.

"Semver", or "Semantic Versioning", is a naming convention for versioning software. You've probably seen it around, it looks like this:

The "v" isn't technically part of "semver", but it's often there to say "this is a version".

It has two primary purposes:

  1. To give us a standard convention for versioning software
  2. To help us understand the impact of a version change and if it's safe (how hard it will be) to upgrade to

Each part is a number that starts at 0 and increments upward forever. The rules are simple:

  • MAJOR increments when we make "breaking" changes (this is typically a big release, for example, Python 2 -> Python 3)
  • MINOR increments when we add new features in a backward-compatible manner
  • PATCH increments when we make backward-compatible bug fixes

To sort them from highest to lowest, you first compare the major versions, then the minor versions, and finally the patch versions. For example, a major version of 2 is always greater than a major version of 1, regardless of the minor and patch versions.

As a special case, major version 0 is typically considered to be pre-release software and thus the rules are more relaxed.

Tagging Releases With Semver

Tags are used for all sorts of reasons, but sometimes they're used to denote releases. In that case, tags that follow semver are common:

git tag -a v3.10.2 -m "Fixed a lil bug"

Pretty much anywhere you can use a commit hash, you can use a tag name when working with the git CLI. It's a "commitish". That means you can check out a release with git checkout v3.10.2, or diff two releases with git diff v1.0.0 v2.0.0. Tags even make great fixed endpoints when you're hunting down a regression with git bisect.

Commits can also have multiple tags. Tagging the same commit as both candidate and v1.0.0 is perfectly fine.

One gotcha: git tag lists tags in alphabetical order, so v10.0.0 sorts before v2.0.0. To sort by version instead, run:

git tag -l --sort=version:refname

You can make that the default in your Git config with git config --global tag.sort version:refname. There are a few more variations in this post on semver tag ordering.

How Do You Push Tags to a Remote?

Tags don't ride along with a normal git push. You push them explicitly:

git push origin --tags

Or push a single tag by name with git push origin v1.0.0. If remotes are new to you, origin is just the conventional name for the remote your repo was cloned from.

Once your tags are on GitHub, they power releases: a GitHub release is a tag wrapped in a larger UI, with release notes and downloadable artifacts attached. The tag is the git-native part; the release is the GitHub feature built on top of it.

That's really all there is to tags: fixed names on commits, ideally semver-shaped, pushed so your team can see them. They're covered alongside reflog, cherry-pick, and bisect in Learn Git 2, and if you're still shaky on the fundamentals, start with Learn Git.

Frequently Asked Questions

What is a git tag used for?

A tag marks a specific commit with a permanent name. Tags are most often used to mark release points, like v1.0.0, so you can always find the exact code that shipped.

What is the difference between a git tag and a branch?

A branch is a moving pointer that advances with every new commit. A tag is a fixed pointer that stays on one commit forever. Tags can be created and deleted, but not modified.

What is the difference between annotated and lightweight tags?

An annotated tag (created with -a) stores metadata like the tagger's name, email, date, and a message. A lightweight tag is just a name pointing at a commit. Use annotated tags for releases.

How do I push tags to a remote repository?

Tags are not pushed by default. Run git push origin --tags to push all your tags, or git push origin v1.0.0 to push a single tag by name.

Can a commit have more than one tag?

Yes. Multiple tags can point at the same commit, so you can tag one commit as both candidate and v1.0.0 without any issues.

Related Articles