Table of Contents

Introduction

Branches and tags are both names for commits, and both live as small files under .git/refs/. The difference is what happens when you commit: a branch follows you, a tag stays where you put it. That's why releases get tags (on GitHub, every release is built on one). A year later, v1.0 still points at exactly the commit you shipped.

In this article, we'll:

  1. Watch git tag v1.0 label the current commit
  2. Compare a tag with a branch on the same graph
  3. Cover annotated tags, tagging older commits, and pushing tags

What is git tag?

git tag <name> writes a file .git/refs/tags/<name> containing the hash of the current commit. That's a lightweight tag. git tag -a <name> -m "message" creates an annotated tag, which is a full object in the database with a tagger, date, message and optional signature, and the ref file points at that object instead. Releases should be annotated. Lightweight tags are fine for private bookmarks.

Watch it happen

Our sample repo has the current commit on main. Here's git tag v1.0:

  1. Before: HEAD is attached to main at 8c02d5b. The repository has no tags.
  2. Git creates the tag v1.0, a ref pointing at 8c02d5b.
  3. main and HEAD don't move, and no commits are created.

Before and after

The graph gained one label. Make another commit now and main would move forward while v1.0 stayed on 8c02d5b. That's the entire difference between the two kinds of ref, and it's why a tag is the right way to mark a release.

The raw git output, if you want to read along in text

git log --oneline --graph --all

before

* 1117a34 (feature) Add search tests
* e5869f0 Fix typo in search box
* fc19889 Add search box
| * 8c02d5b (HEAD -> main) Update dependencies
| * a0b2db3 Add user settings page
|/  
* 96c4fc2 Fix header layout
* ae65976 Add login page
* 3e1ffe4 Add project skeleton
* 4114b2c Initial commit

after

* 1117a34 (feature) Add search tests
* e5869f0 Fix typo in search box
* fc19889 Add search box
| * 8c02d5b (HEAD -> main, tag: v1.0) Update dependencies
| * a0b2db3 Add user settings page
|/  
* 96c4fc2 Fix header layout
* ae65976 Add login page
* 3e1ffe4 Add project skeleton
* 4114b2c Initial commit

Tags don't push by default

git push sends branches, not tags. To publish a tag:

git push origin v1.0

or git push --tags for all of them. Forgetting this is the usual reason a tag exists on your machine and nowhere else.

git-sim's releases are tagged with annotated tags, and I built git-tagup years ago because I kept forgetting the sequence: bump the version, commit, tag, push the tag separately. Four steps that all have to agree is exactly the kind of thing worth scripting.

Is it safe?

Safe git-sim pre-flight

creates tag 'v1.0' pointing at HEAD; nothing is discarded.

Creating a tag adds one ref. Nothing existing changes. Deleting one is git tag -d <name>, and moving one (git tag -f) is worth avoiding once a tag has been pushed, for the same reason as any other history rewrite.

Useful forms

  • git tag lists tags.
  • git tag -a v1.0 -m "First release" creates an annotated tag.
  • git tag v0.9 96c4fc2 tags an older commit.
  • git show v1.0 shows the tag and the commit it points at.
  • git checkout v1.0 visits the tagged commit, with HEAD detached.

How to undo it

git tag -d v1.0

Try it on your repository

pip install git-sim
git-sim tag v1.0

git-sim shows where the tag would land on your own graph.

Common questions

What is the difference between a tag and a branch?

Both name a commit. A branch moves to each new commit you make on it, while a tag stays on the commit it was created at.

What is the difference between lightweight and annotated tags?

A lightweight tag is just a ref pointing at a commit. An annotated tag is an object with a tagger, date and message, which the ref points at. Use annotated tags for releases.

Why isn't my tag on the remote?

git push doesn't send tags. Run git push origin <tag> or git push --tags.

How do I tag an older commit?

git tag <name> <sha>, for example git tag v0.9 96c4fc2.

Summary

In this article, we watched git tag put a fixed label on the current commit, compared it with a branch, and covered annotated tags and why tags need to be pushed separately.

Next steps

git tag -d <name> removes a tag locally, and git push origin --delete <name> removes it from the remote. git branch is the moving kind of ref.