Git Tags

Tag management: marking releases, versioning, and milestone snapshots.

Tag Fundamentals

# TWO TYPES OF TAGS
# Lightweight: just a pointer to a commit (like a branch that doesn't move)
# Annotated:   full object with tagger, date, message, optional GPG signature

# CREATE LIGHTWEIGHT TAG
git tag v1.0                             # Tag current HEAD
git tag v1.0 abc1234                     # Tag specific commit

# CREATE ANNOTATED TAG (preferred for releases)
git tag -a v1.0 -m "Release 1.0"        # With message
git tag -a v1.0 abc1234 -m "Release 1.0"  # Tag specific commit
git tag -a v1.0                          # Opens editor for message

# WHEN TO USE WHICH
# Lightweight: temporary markers, personal bookmarks, CI/CD triggers
# Annotated:   releases, milestones, anything shared publicly
# Rule: if others will see it, annotate it

# LIST TAGS
git tag                                  # All tags
git tag -l                               # Same
git tag -l "v1.*"                        # Glob pattern
git tag -l --sort=-v:refname             # Sort by version (newest first)
git tag -l --sort=-creatordate           # Sort by date (newest first)

# TAG DETAILS
git show v1.0                            # Full tag info + commit
git tag -n                               # Tags with first line of message
git tag -n5                              # Tags with 5 lines of message
git cat-file -t v1.0                     # Show type (tag vs commit)

# CHECK IF TAG EXISTS
git tag -l "v1.0" | grep -q "v1.0" && echo "exists" || echo "not found"
git rev-parse --verify "v1.0" 2>/dev/null && echo "exists"

Tag Operations

# DELETE LOCAL TAG
git tag -d v1.0                          # Delete locally

# DELETE REMOTE TAG
git push origin --delete v1.0            # Delete from remote
git push origin :refs/tags/v1.0          # Older syntax, same effect

# RENAME TAG (delete + recreate)
git tag new-tag old-tag                  # Copy to new name
git tag -d old-tag                       # Delete old local
git push origin new-tag :old-tag         # Push new, delete old remote

# MOVE TAG TO DIFFERENT COMMIT
git tag -f v1.0 abc1234                  # Force overwrite local
git push --force origin v1.0             # Force update remote
# WARNING: Only do this for tags nobody else has fetched

# PUSH TAGS
git push origin v1.0                     # Push specific tag
git push origin --tags                   # Push ALL local tags
git push --follow-tags                   # Push commits + annotated tags only

# PULL/FETCH TAGS
git fetch --tags                         # Fetch all tags from origin
git fetch origin --tags                  # Explicit remote
git fetch --prune-tags                   # Remove local tags deleted on remote

# CHECKOUT TAG (detached HEAD)
git checkout v1.0                        # Detached HEAD at tag
git checkout -b release-v1.0 v1.0        # Create branch from tag

# COMPARE TAGS
git diff v1.0 v2.0                       # Diff between releases
git log v1.0..v2.0 --oneline            # Commits between releases
git shortlog v1.0..v2.0                  # Changelog between releases

Tag Workflows

# SEMANTIC VERSIONING (semver)
# Format: vMAJOR.MINOR.PATCH
# v1.0.0 → v1.0.1 (patch: bug fix)
# v1.0.0 → v1.1.0 (minor: new feature, backward compatible)
# v1.0.0 → v2.0.0 (major: breaking change)

# RELEASE WORKFLOW
git checkout main
git pull origin main                     # Ensure up to date
git tag -a v1.2.0 -m "Release 1.2.0: Add feature X, fix bug Y"
git push origin v1.2.0                   # Push tag only
# OR
git push --follow-tags                   # Push commits + tags

# PRE-RELEASE TAGS
git tag -a v2.0.0-rc.1 -m "Release candidate 1"
git tag -a v2.0.0-beta.1 -m "Beta 1"
git tag -a v2.0.0-alpha.1 -m "Alpha 1"

# LIST LATEST RELEASE TAG
git describe --tags --abbrev=0           # Latest tag
git describe --tags                      # Latest tag + distance
# Output: v1.2.0-3-gabc1234
#   v1.2.0 = last tag
#   3      = commits since tag
#   gabc1234 = current commit prefix

# GENERATE CHANGELOG FROM TAGS
git log v1.0.0..v1.1.0 --oneline        # Changes in v1.1.0
git log v1.0.0..v1.1.0 --pretty=format:"- %s (%h)"  # Markdown bullet list

# FIND WHICH TAG CONTAINS A COMMIT
git tag --contains abc1234               # Tags that include this commit
git describe --contains abc1234          # Nearest tag after commit

# TAG-BASED CI/CD
# Many CI systems trigger on tag push:
# - GitHub Actions: on: push: tags: ['v*']
# - GitLab CI: rules: - if: $CI_COMMIT_TAG

Infrastructure Repository Tags

# DOMUS-* TAG CONVENTIONS
# Documentation repos: tag when content milestones hit
git tag -a docs-v1.0 -m "Initial documentation complete"
git tag -a docs-v1.1 -m "Added ISE troubleshooting runbooks"

# SNAPSHOT TAGS (before major changes)
git tag -a pre-restructure-2026-04 -m "Snapshot before codex restructure"
git tag -a pre-migration -m "Before Antora migration"

# TAG ALL REPOS AT MILESTONE
for repo in ~/atelier/_bibliotheca/domus-*/; do
  name=$(basename "$repo")
  git -C "$repo" tag -a "milestone-2026-q2" -m "Q2 2026 milestone"
  git -C "$repo" push origin "milestone-2026-q2"
done

# LIST TAGS ACROSS REPOS
for repo in ~/atelier/_bibliotheca/domus-*/; do
  name=$(basename "$repo")
  tags=$(git -C "$repo" tag -l --sort=-creatordate | head -3)
  if [[ -n "$tags" ]]; then
    echo "=== $name ==="
    echo "$tags"
  fi
done

# CLEAN UP OLD TAGS
git tag -l "temp-*" | xargs git tag -d   # Delete local temp tags
git tag -l "temp-*" | xargs -I{} git push origin --delete {}  # Delete remote

Tag Gotchas

# WRONG: Pushing without --tags or --follow-tags
git push origin main
# Tags stay local! Others won't see them

# CORRECT: Push tags explicitly
git push origin v1.0                     # Specific tag
git push --follow-tags                   # Commits + annotated tags

# WRONG: Moving a tag that others fetched
git tag -f v1.0 abc1234
git push --force origin v1.0
# Others still have old v1.0 → confusion

# CORRECT: Don't move published tags
# If you must, communicate the change and have others:
git fetch --tags --force                 # Force update local tags

# WRONG: Lightweight tags for releases
git tag v1.0
# No metadata, no message, no audit trail

# CORRECT: Annotated tags for anything shared
git tag -a v1.0 -m "Release 1.0: description"

# WRONG: Forgetting tags during clone
git clone repo.git
# Gets tags by default, BUT...

# EDGE CASE: Shallow clone misses tags
git clone --depth 1 repo.git
# May not have all tags
git fetch --tags --unshallow             # Fix: get full history

# WRONG: Deleting tag locally but not remotely
git tag -d v1.0
# Still on remote! Others can fetch it again

# CORRECT: Delete both
git tag -d v1.0
git push origin --delete v1.0

Quick Reference

# CREATE
git tag v1.0                             # Lightweight
git tag -a v1.0 -m "message"            # Annotated (preferred)
git tag -a v1.0 abc1234 -m "msg"        # Tag specific commit

# LIST
git tag -l "v1.*"                        # Glob filter
git tag -l --sort=-v:refname             # Newest version first

# INSPECT
git show v1.0                            # Tag details
git describe --tags                      # Latest tag + distance

# PUSH
git push origin v1.0                     # Specific tag
git push --follow-tags                   # Commits + annotated tags
git push origin --tags                   # All tags

# DELETE
git tag -d v1.0                          # Local
git push origin --delete v1.0            # Remote

# COMPARE RELEASES
git log v1.0..v2.0 --oneline            # Changes between tags
git diff v1.0 v2.0                       # Diff between tags

# CHECKOUT
git checkout -b release-v1.0 v1.0        # Branch from tag

See Also

  • Remotes — pushing and fetching tags

  • Branches — release branches from tags

  • History — find which tag contains a commit