Git

What Is Git Tag? Marking Versions Made Easy

What Is Git Tag? Marking Versions Made Easy

Commit hashes work fine for Git and badly for people. A tag fixes that by putting a readable name on one commit, usually a release like v1.0.0, and leaving it there. A branch name moves when new commits arrive. A tag doesn’t.

Teams lean on tags to pin builds, deployments and hotfixes to an exact point in history.

Pro Git lists the tag object as Git’s fourth object type, after blobs, trees and commits, yet only annotated tags create one. A lightweight tag is a plain reference file under refs/tags that holds a commit hash and nothing else.

What Is git tag?

YouTube player

Mark a commit with a name such as v1.0.0 and Git keeps that name on the commit for good. New commits don’t shift it.

Each tag is stored as a reference under refs/tags, so v1.0.0 resolves to the same commit every time. Nobody has to paste a long commit hash into a build script or a bug report.

How Git Stores a Tag

A lightweight tag is just a file in refs/tags holding the hash of the tagged commit. That’s the whole thing.

An annotated tag is heavier. Git writes a tag object with the tagger name, email, date and message, the ref points at that object, and the object points at the commit.

Tag objects aren’t limited to commits either. Pro Git notes that Git’s own source repository tags a blob containing the maintainer’s GPG public key, and the first tag in the Linux kernel repository points at a tree.

git tag vs Branch

YouTube player

You see the difference when a new commit lands. A git branch pointer advances with every commit, while a tag stays on the commit it was created on.

BehaviorBranchTag
New commit on topPointer moves forwardPointer stays put
Lives underrefs/headsrefs/tags
Typical jobOngoing workRelease points and milestones

Tag to find a release later. Branch to keep working.

Why is GitHub the heart of open source?

Uncover GitHub statistics: developer community growth, repository trends, collaboration patterns, and the platform that powers modern software development.

Explore GitHub Data →

Annotated vs Lightweight Tags

YouTube player

Annotated tags for anything you publish, lightweight tags for private bookmarks. The git-tag manual for Git 2.55.0 (June 2026) draws the same line, calling annotated tags the ones meant for releases and lightweight tags the ones for private or temporary labels.

PropertyLightweightAnnotated
Stored asRef onlyRef plus tag object
CarriesCommit hashTagger, date, message, optional signature
Created withgit tag v1.0.0git tag -a v1.0.0 -m "Release 1.0.0"
Seen by git describeOnly with –tagsYes, by default
SignableNoYes, with -s

Where the Tag Type Changes Tool Behavior

  • git describe skips lightweight tags unless –tags or –all is passed. If one commit carries both kinds, the annotated one wins, and among tags of the same kind the newer date wins. When no tag sits on the commit itself, it walks back through history and picks the nearest one.
  • Azure DevOps only creates annotated tags in the web portal, so a lightweight tag means going to the command line (Microsoft Learn documentation).
  • An issue filed against jj version 0.34.0 reports that it treats every Git tag as if it were annotated, which makes the tagged commit immutable.

Release scripts that read git describe break quietly on lightweight tags. The KMA genomics project ran into this. git describe reported 1.2.29 plus 11 commits instead of release 1.4.0, because most of its releases carried lightweight tags (Bitbucket issue 60).

I’d default to annotated every time. A lightweight tag saves a few seconds and leaves a release with nothing to audit.

How to Create a git tag

YouTube player

Run git tag -a v1.0.0 -m "Release 1.0.0" on the commit you want to mark. That makes an annotated tag, and dropping -a and -m makes a lightweight one.

  1. Confirm the target with git log --oneline -1. Tags land on HEAD unless a commit is named.
  2. Create the tag: git tag -a v1.0.0 -m "Release 1.0.0" for annotated, or git tag v1.0.0-lw for lightweight.
  3. Check the result with git show v1.0.0. An annotated tag prints the tagger and message before the commit.
  4. Push the tag when it is ready to share. Tags stay local until pushed.

Tagging an Older Commit

Put the commit hash, full or abbreviated, at the end: git tag -a v1.2 9fceb02. Pro Git uses this exact pattern to tag a release that got forgotten after later work landed.

Looking to sharpen your Git skills? Branching, merging, rebasing, and everything else you need - including git rebase, git stash, and commit workflows - is on one page in the Git Cheat Sheet.

Find the hash first with git log --oneline.

What Goes Wrong

YouTube player

If the name already exists, Git refuses unless you pass -f, and -f replaces the old tag.

Then there’s the surprise annotation. -m or -F implies -a, so git tag -m "note" v1.0.0 still creates an annotated tag (git-tag manual, Git 2.55.0).

And if you create an annotated tag without a message, your editor opens. Annoying the first time, expected after that.

How to List, Inspect and Check Out Tags

YouTube player

git tag with no arguments lists every tag in alphabetical order. Add a pattern with -l to narrow the output.

Listing and Filtering Tags

Git’s own source repository holds more than 500 tags, according to Pro Git, so a pattern keeps the list readable:

git tag -l "v1.8.5*"

The pattern is a shell wildcard, and -l is mandatory whenever you supply one.

  • --contains lists tags that include a given commit
  • --points-at lists tags on a given object
  • --sort=-v:refname sorts by version, newest first
  • -n prints annotation lines, and falls back to the commit message for lightweight tags
  • --merged and --no-merged filter by what a given commit can reach

Inspecting a Tag

git show v1.0.0 prints the tagger, date and message of an annotated tag, then the commit. A lightweight tag gets the commit alone.

git describe works the other way around. It names a commit after the nearest tag behind it, such as v1.0.4-14-g2414721, which reads as tag v1.0.4, 14 commits on top, abbreviated hash 2414721 (git-describe manual).

Tag names also show up next to commits in git log output when you run git log --decorate.

Checking Out a Tag Without Losing Work

git checkout v2.0.0 shows the files as they were at that tag, and it drops the repository into detached HEAD state. A commit made there leaves the tag unchanged and belongs to no branch, so only its exact hash can reach it.

To keep changes, branch from the tag with git checkout -b version2 v2.0.0.

The new branch moves forward with each commit. The tag stays where it was.

Why git Tags Are Not Pushed by Default

YouTube player

Git treats a tag as a personal label until you publish it, so a plain push skips every tag. The git-tag manual explains the thinking. Someone pulling a one-off change shouldn’t inherit another developer’s private anchor points.

Publishing takes one of a few push forms, and each selects a different set of tags.

CommandTags sentWatch out for
git push origin v1.0.0That one tagNothing else travels with it
git push origin --tagsEvery tag under refs/tagsPrivate lightweight tags go too
git push --follow-tagsAnnotated tags reachable from pushed commitsLightweight tags stay behind

Making Annotated Tags Travel With Commits

Set push.followTags to true and every git push behaves as if --follow-tags were passed. A single --no-follow-tags overrides it for one push (git-push manual, Git 2.55.0).

Lightweight tags never ride along this way. One more reason to keep releases annotated.

What the Other Side Receives

Pro Git says a clone or pull brings down all of your tags. The git-fetch manual is narrower. By default it only downloads tags that point into the history being fetched, and --tags is needed for the rest.

Both statements hold for a tag on a pushed branch. A tag on a commit outside every fetched branch only arrives with --tags.

To check what the remote repository actually holds, run git ls-remote --tags origin.

How to Delete, Rename or Move a Tag

YouTube player

Delete with git tag -d v1.0.0 locally and git push origin --delete v1.0.0 on the remote. Git has no rename command, and moving a published tag breaks the trust other clones place in its name.

Deleting a Tag

git tag -d v1.0.0 removes the tag from this repository only. To remove it from the remote, use git push origin --delete v1.0.0, or the older form git push origin :refs/tags/v1.0.0.

Teammates keep their copies until each person deletes them. git fetch --prune --prune-tags clears local tags that no longer exist on the remote.

That last command has a catch. Per the git-fetch manual, --prune-tags also deletes local tags you never pushed and force-updates ones that differ, while plain --prune leaves auto-followed tags alone.

Renaming a Tag

  1. Print the tagged commit with git rev-list -n 1 v1.0.0-beta.
  2. Create the new name on it: git tag -a v1.0.0-beta.1 9fceb02 -m "Beta 1".
  3. Push the new tag with git push origin v1.0.0-beta.1.
  4. Delete the old name locally and on the remote.

The new tag gets its own tagger date and message. Nothing from the old annotation carries over.

Moving a Published Tag

Different sources reach the same verdict from different directions.

On the push side, every update to a ref under refs/tags is rejected unless --force or a leading + is used (Git 2.55.0 manual). Fetch used to be looser. Before Git 2.20, tag updates were accepted without force, and since 2.20 they are rejected, same as on push.

Semantic Versioning 2.0.0 comes at it from the version side. The contents of a released version must not be modified, and any change ships as a new version.

The git-tag manual recommends the same thing: admit the mistake and publish X.1 instead of reusing X.

If you do move the tag, git tag -f plus a forced push keeps the name your docs and build scripts already use. But clones that fetched the old tag keep it, so two people end up holding different commits under one name. Each of them has to run git tag -d and fetch the tag again.

Publishing a new tag instead changes nobody’s copy, and the mistake stays visible in history. The cost is one burned version number.

If the tag was never pushed, git tag -f is safe and none of the above applies.

git tag Naming Conventions

Name release tags vMAJOR.MINOR.PATCH, such as v2.4.1, unless the project releases on a calendar. Git accepts almost any string, so the convention is the only thing keeping tag lists sortable.

Semantic Versioning Tags

YouTube player

Semantic versioning puts compatibility into the number itself. The major number goes up for incompatible API changes, the minor for backward compatible new functionality, and the patch for backward compatible bug fixes.

The Semantic Versioning FAQ states that v1.2.3 is not itself a semantic version. The version is 1.2.3, and v1.2.3 is the tag name, which is exactly how Git uses the prefix.

Versions below 1.0.0 mark initial development, where anything can change. A pre-release suffix such as -rc.1 labels a release candidate and ranks below the final 1.0.0.

git tag --sort=version:refname orders names as versions, and the versionsort.suffix setting adjusts where suffixes like -rc land, per the git-tag manual.

Calendar Versioning Tags

Ubuntu has used a YY.0M.MICRO scheme since release 4.10 in October 2004, according to calver.org.

Twisted uses YY.MM.MICRO and removes deprecated features only after one year and two releases, so the date in the tag also dates the breakage.

SchemeThe number signalsExample tag
Semantic VersioningCompatibility of API changesv2.4.1
Calendar VersioningRelease datev24.10.0
HybridDate acts as the major versionv24.10.2.0

Calver.org points CalVer at large or constantly changing systems and at time-driven releases such as certificate bundles. A library with dependents belongs on SemVer. An operating system belongs on CalVer.

Characters Git Rejects

A tag name has to pass the rules in git-check-ref-format, so some strings fail before the tag exists.

  • Spaces and control characters
  • The characters ~ ^ : ? \* [ and the backslash
  • Two consecutive dots
  • The sequence @{
  • A name ending in a dot, or a path component ending in .lock

Hosting platforms add limits of their own. Azure DevOps advises keeping tag names under 250 ASCII characters (Microsoft Learn documentation).

How to Sign and Verify a git tag

Create a signed tag with git tag -s v1.0.0 -m "Release 1.0.0" and check it with git tag -v v1.0.0. The signature proves which key created the tag, so anyone can confirm a release came from the maintainer.

  1. Confirm a signing key exists with gpg --list-keys. Generate one with gpg --gen-key if the list is empty.
  2. Point Git at the key: git config --global user.signingkey 0A46826A! (the key ID here is Pro Git’s example).
  3. Sign the tag with -s in place of -a. Use -u to pick a specific key.
  4. Verify with git tag -v v1.0.0. A good signature prints the signer and key fingerprint.

A signed tag is always an annotated one, because -s creates a tag object to hold the signature. Lightweight tags have nowhere to store it.

Two settings automate the signing. tag.gpgSign signs every tag, and tag.forceSignAnnotated signs annotated ones only. The manual warns that in scripts the first causes many passphrase prompts, so run a GPG agent.

As of the Git 2.55.0 manual, the gpg.format variable selects the signing backend. OpenPGP is the default, and X.509 and SSH are supported.

When Verification Fails

The usual cause is a missing public key. GPG reports that it cannot check the signature because the public key was not found, and Git adds that it could not verify the tag.

Import the signer’s public key into your keyring and run the check again. Every machine that verifies the tag needs that key.

git tag FAQ

What is the difference between a git tag and a GitHub release?

A tag marks a commit inside Git. A GitHub release is a page built on a tag that adds release notes, attached binaries, and zip and tarball links of the source at that tag, according to GitHub Docs.

A tag can exist without any release.

How do I edit the message of an existing annotated tag?

Re-create the tag on its own commit with git tag -f -a v1.0.0 -m "New message" v1.0.0^{commit}. The new tag object carries a fresh tagger date.

If the tag is already published, it needs a forced push and a refetch by everyone, so a new version name is often safer.

Can a lightweight tag be signed?

No. A signature needs a tag object to live in, and a lightweight tag has none. Create an annotated tag with -s instead, adding -f if the lightweight name already exists.

Does deleting a git tag delete the commit?

No. Deleting a tag removes only the name.

The commit stays as long as a branch or another reference reaches it, and Git’s garbage collection removes commits only once nothing references them.

How many tags can point at the same commit?

As many as you like. Each tag is its own reference under refs/tags, so a release candidate and the final release can name one commit.

Automating Release Tagging Without Retagging

A release pipeline should run git tag itself, after the build passes, and never rewrite a tag once it is pushed.

Annotated tags are the only kind that git describe reads by default and that --follow-tags pushes, so a pipeline creating lightweight tags fails on both counts.

The order is simple. Build and test the commit, create an annotated tag from the computed version, then push that single tag by name.

Tagging first forces a retag whenever the build fails, because a published version name then points at broken code.

The downside is that builds running before the tag exists are identified only by commit hash.

This position holds for the Git 2.56.0 manuals listed on git-scm.com in October 2026, and a change in how fetch treats tag updates would reopen it. Wiring the tag into a job is the next step, covered in how GitHub Actions workflows run.

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.