<aside> 🔀

Module 12 — Tags and Releases (git tag, describe, semver). A commit hash is precise but unmemorable; "v2.4.0" is what humans, deploys, and changelogs actually reference. Tags are Git's permanent, human-readable names for the commits that matter — releases. This module covers lightweight vs annotated tags, semantic versioning, git describe, pushing tags (they don't travel automatically — a classic surprise), and the signed tags that make a release cryptographically provable.

🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

</aside>

<aside> 🚦

Before you start. You need Module 3 (objects and refs — a tag is one more object type and one more ref), Module 8 (pushing — tags push separately), and ideally Module 1's note that commit identity is unverified (signing is the fix, Part D). A fresh kitchen repo works; the exercises build what they need. Part D's signed tags require a signing key configured — the exercise says how, and is optional if you have none.

</aside>

Part A — Two kinds of tag

A1. Lightweight and annotated

<aside> 📖

Official docs: Git Book §2.6 — Tagging · git-tag manual

</aside>

A tag is a name pinned to a specific commit — and unlike a branch (Module 5's moving pointer), a tag never moves. It marks one commit forever: "this exact snapshot is v1.0.0." Git has two kinds. A lightweight tag is just a ref — a file under .git/refs/tags/ holding a commit hash, nothing more (Module 3's ref, pointed at a commit instead of a branch). An annotated tag is a real object in the database — Git's fourth object type (Module 3 named it and deferred it to here): it stores the tagger's name, email, date, a message, and (Part D) an optional signature, then points at the commit. git tag <name> makes a lightweight tag; git tag -a <name> -m "message" makes an annotated one. For releases, use annotated — the metadata (who cut it, when, why) is exactly what a release needs, and only annotated tags can be signed.

🧪 Exercise 12.1 — make one of each, then look inside

cd ~/git-course/kitchen              # a repo with a few commits
git tag v0.9-beta                                 # lightweight
git tag -a v1.0.0 -m "First stable release: core bread recipes"   # annotated
git tag                                           # list them
git cat-file -t v0.9-beta            # what kind of object does each name?
git cat-file -t v1.0.0
git cat-file -p v1.0.0               # read the annotated tag object

<aside> 🏷️

Real-world analogy — the museum plaque vs a sticky dot

A lightweight tag is a sticky dot on a painting: "this one." Useful, but it tells you nothing — who placed it, when, why this painting. An annotated tag is the engraved plaque beside the artwork: title, date acquired, curator's note, and the museum's seal (the signature). Both point at the same painting; only the plaque carries the story of the designation.

Where the analogy stops working. A plaque can be re-engraved and a painting reframed; the record drifts. A Git tag is anchored by content-addressing — the annotated tag object names the commit by hash, so it is bound to that exact snapshot and provably so. And a museum's seal can be forged onto a plaque; a signed tag's cryptographic signature (Part D) cannot — verification recomputes it against a public key. The plaque is a description; the signed tag is evidence.

</aside>

A2. Tagging, listing, deleting

<aside> 📖

Official docs: git-tag manual

</aside>

Tags are not limited to "right now." git tag -a <name> <commit> tags any historical commit — the common "we forgot to tag last week's release" fix. Listing scales: git tag -l "v1.*" filters by pattern, -n1 shows each tag's message, and --sort=-v:refname sorts by version number (not alphabetically — critical, because plain sort puts v1.10.0 before v1.9.0). Deleting is git tag -d <name> — but note this deletes only your local tag; a pushed tag needs separate remote deletion (Part C).

🧪 Exercise 12.2 — tag the past, filter, delete

cd ~/git-course/kitchen
FIRST=$(git rev-list --max-parents=0 HEAD)   # the root commit's hash
git tag -a v0.1.0 -m "Initial prototype" $FIRST
git tag -l "v0.*" -n1                 # matching tags, with messages
git tag --sort=-v:refname            # version-sorted, newest first
git tag -d v0.9-beta                 # remove the lightweight beta locally
git tag

🎯 Interview questions — Part A