Remotes and Large Repositories
Topic 28

Tags and Releases

Remotes

A tag is a fixed, human-readable name for one specific commit — the way you mark "this is exactly what we shipped as v1.0." Unlike a branch, a tag is not supposed to move; once v1.0 points at a commit, everyone who fetches it expects that pointer to stay put forever.

The distinction that matters for releases is lightweight versus annotated. A lightweight tag is a bare ref pointing at a commit; an annotated tag is a real object carrying who tagged it, when, a message, and optionally a cryptographic signature. Only annotated or signed tags belong on anything the public depends on.

Lightweight vs Annotated

git tag v1.0 creates a lightweight tag: just a ref under refs/tags/ pointing at the current commit, with no metadata of its own. git tag -a v1.0 -m "Release 1.0" creates an annotated tag, which is a stored object recording the tagger, a timestamp, and the message.

If you are unsure which kind you made, git cat-file -t v1.0 tells you: it reports commit for a lightweight tag (it points straight at the commit) and tag for an annotated one (the tag object sits in between).

Signed Tags

git tag -s v1.0 -m "..." creates an annotated tag and signs it, by default with GPG, or with SSH or X.509 keys when you set gpg.format. The signature binds the release to an identity, so consumers can prove who cut it. git tag -v v1.0 verifies that signature against the signer's public key.

A signature is only worth as much as the verification step: signing without ever running git tag -v in your pipeline leaves the cryptography purely decorative.

Pushing and Listing Tags

Tags do not ride along with git push the way commits on a branch do. You push a specific one with git push origin v1.0, or push all of them with git push --tags. Until you do, the tag exists only in your local repository and never reaches the remote or CI.

To turn the nearest tag into a version string for a build, git describe --tags produces something like v1.0-14-g2a3b4c5: the last tag, how many commits since, and the abbreviated commit it names.

Tags vs Releases

A Git tag is the immutable marker in the repository. A GitHub "Release" is a layer on top of a tag: it wraps that tag with release notes, a title, and downloadable binary assets. The Release is a presentation and distribution convenience, but the tag underneath is the source of truth for what code shipped.

Because the Release points at the tag, deleting or moving the underlying tag breaks the link and leaves the Release referring to something that no longer exists where it claimed.

Lightweight vs Annotated Tag

Lightweight tag — a bare ref with no metadata of its own, fine as a private bookmark or a throwaway marker you alone use.

Annotated tag — a stored object carrying tagger, date, message, and an optional signature, and the only correct choice for a release others depend on. Use -a (or -s) for anything public.

Common Mistakes
  • Cutting a release with a lightweight tag, leaving no record of who tagged it, when, or why.
  • Forgetting tags do not push by default, so v1.0 exists locally but never reaches the remote or the release pipeline.
  • Moving a published tag with git tag -f after people have fetched it, breaking everyone who already has the old target.
  • Signing tags but never running git tag -v in CI, so the signature proves nothing and is purely decorative.
  • Relying on a GitHub Release while deleting or moving its underlying tag, breaking the link the Release depends on.
Best Practices
  • Cut every release with an annotated tag: git tag -a v1.2.0 -m "...".
  • Sign release tags with git tag -s and verify them in CI with git tag -v.
  • Push tags explicitly with git push origin <tag> rather than assuming they travel with commits.
  • Derive build version strings from git describe --tags instead of hand-maintaining them.
  • Treat published tags as immutable — never -f one; cut a new version instead.
Comparable toolsMercurial versioned tags in .hgtags plus local tags, gpg extension for signingSubversion tags are mutable directory copies under /tagsPerforce uses labelsFossil tags stored in-repo, including a release pattern

Knowledge Check

Why is a lightweight tag wrong for a public release?

  • It is a bare ref with no tagger, date, message, or signature, so there is no record of who cut it
  • It cannot point at a release commit at all, only at a branch
  • It expires and is automatically deleted after about a week, so the release marker vanishes from the repository once its short lifetime runs out
  • It can never be pushed to a remote for others to fetch, so the release marker stays trapped on the machine that created it and no one else can see it

What does an annotated tag carry that a lightweight one does not?

  • A stored tag object with tagger, date, message, and an optional signature
  • A full copy of the entire working tree captured at tag time and stored inside the tag object itself
  • A forward pointer to the next release that follows it, linking each tag to its successor so you can walk the chain of versions in order
  • The tagged commit's diff against its parent commit

Why doesn't a tag reach the remote when you push a branch?

  • Tags are not pushed with commits by default; you push them with git push origin <tag> or --tags
  • Tags exist server-side only and are created on the remote, never locally, so a branch push has nothing to send because no tag was ever made in your clone
  • The remote strips out any tags it receives during a branch push
  • Only annotated tags push with a branch; lightweight ones never travel

What is the consequence of force-moving a published tag?

  • Anyone who already fetched the old target keeps it, so their tag disagrees with the remote and builds diverge
  • Nothing happens, because Git syncs the new tag target to every clone automatically on the next fetch and quietly overwrites the old one everyone held
  • The tag is permanently deleted from the repository on the move
  • All commits downstream of the tag are rewritten to match it

What is the relationship between a Git tag and a GitHub Release?

  • The tag is the immutable source of truth; the Release wraps it with notes and assets and breaks if the tag moves
  • The Release is the immutable source of truth and the tag is generated from it, with the repository's ref derived from whatever commit the Release page records
  • They are unrelated; a Release stands alone and needs no underlying tag
  • A Release fully replaces the underlying tag once it is published

You got correct