06 · Tags & Releases¶
Tags mark a specific commit permanently — most often "this is version 1.0.0." GitHub Releases build on top of tags to add release notes, binaries, and a page users can find.
Lightweight vs annotated tags¶
They look the same in git tag -l, but they're different object types:
v1.0.0 is its own object (type tag) with a message and tagger
identity; lightweight-v1 is nothing but a ref pointing directly at a
commit — no separate object at all.
tag v1.0.0
Tagger: You <you@example.com>
Date: Fri Sep 11 10:26:03 2026 +0530
First release
commit 4284e2119776d33fa9fd59891fab801f0d72991c
Author: You <you@example.com>
...
git show on an annotated tag prints the tag's own metadata and then
the commit it points to — proof there are two objects involved, not one.
Use annotated tags for releases. They carry a real identity (tagger,
date, message) and are what git describe and GitHub Releases expect;
reserve lightweight tags for quick personal bookmarks.
Pushing tags¶
Tags are not pushed automatically with git push — they need to be
pushed explicitly:
Semantic versioning¶
The near-universal convention is MAJOR.MINOR.PATCH:
- MAJOR — breaking/incompatible changes
- MINOR — new backward-compatible functionality
- PATCH — backward-compatible bug fixes
v1.0.0 → v1.1.0 adds a feature with no breakage; v1.1.0 → v2.0.0 may
break existing users' code; v1.1.0 → v1.1.1 is a bugfix only.
Creating a GitHub Release¶
From gh release create --help:
If a matching git tag does not yet exist, one will automatically get created
from the latest state of the default branch.
Use `--target` to point to a different branch or commit for the automatic tag creation.
Use `--verify-tag` to abort the release if the tag doesn't already exist.
To fetch the new tag locally after the release, do `git fetch --tags origin`.
To create a release from an annotated git tag, first create one locally with
git, push the tag to GitHub, then run this command.
Use `--notes-from-tag` to get the release notes from the annotated git tag.
Two workflows exist: let gh release create auto-create the tag on
whatever branch/commit you point it at, or (recommended for real
projects) create and push the annotated tag yourself first, then run
gh release create v1.0.0 --notes-from-tag to pull the tag message in as
the release notes automatically.
Auto-generated release notes¶
GitHub can build notes from merged PRs since the last tag:
This groups merged PRs by label (bugs, features, etc.) into a changelog —
configurable via a .github/release.yml categorization file.
How It Actually Works¶
- A lightweight tag is literally a file (or a packed-refs line) at
refs/tags/<name>containing nothing but a commit SHA — functionally identical to a branch ref, except Git/GitHub treatrefs/tags/*as immutable by convention (nothing moves a tag forward the way commits move a branch forward). - An annotated tag is a real object in the object database, of type
tag, containing: the SHA it points to, the "tagged object" type, the tagger's name/email/date, and a message — with its own SHA, computed over that content the same way commit/blob/tree SHAs are computed. The ref atrefs/tags/v1.0.0points to the tag object's SHA, not directly to the commit; the tag object then points to the commit. That extra layer of indirection is exactly whatgit cat-file -tand the two-partgit showoutput revealed above. git pushexcludes tags by default specifically because tags are often local/experimental bookmarks; GitHub Releases build on the pushed tag, so a release always corresponds to a specific, permanent, shareable commit — which is the whole point of tagging a release in the first place rather than just pointing people at a branch that keeps moving.- A GitHub Release is a database record (title, notes, assets, "latest" flag) associated with a tag name; deleting a release does not delete the underlying git tag, and deleting the tag does not delete the release record — they're linked but independently managed, which is why GitHub's UI has separate delete actions for each.
Exercise¶
Create an annotated tag v0.1.0 on your scratch repo's latest commit,
push it, and use gh release create v0.1.0 --notes-from-tag to publish a
release whose notes come from the tag's own annotation message. Confirm
with gh release view v0.1.0 that the notes match what you wrote in
git tag -a.