Git Tag & Release Management
Trigger: Use when creating version tags, cutting releases, or setting up automated release pipelines.
Tag Types
# Lightweight tag (just a pointer)
git tag v1.0.0
# Annotated tag (recommended — stores author, date, message)
git tag -a v1.0.0 -m "Release v1.0.0 — user authentication overhaul"
Semantic Versioning
Given MAJOR.MINOR.PATCH (e.g., v2.5.1):
| Increment | When | Example |
|---|---|---|
| MAJOR | Breaking API change | v2.0.0 → v3.0.0 |
| MINOR | New feature (backward compatible) | v2.0.0 → v2.1.0 |
| PATCH | Bug fix (backward compatible) | v2.0.0 → v2.0.1 |
Pre-release: v1.0.0-alpha.1, v1.0.0-rc.2
Build metadata: v1.0.0+build.20240729
Tag Workflow
Creating Tags
# Create and push a single tag
git tag -a v1.2.0 -m "Release v1.2.0"
git push origin v1.2.0
# Create tag at a specific commit
git tag -a v1.0.0 <commit-sha> -m "Initial release"
# Sign tags with GPG (verified on GitHub)
git tag -s v1.0.0 -m "Release v1.0.0"
Listing and Viewing
git tag # List all tags
git tag -l "v2.*" # Filter by pattern
git tag --sort=-version:refname # Sort by version (newest first)
git show v1.0.0 # Show tag details + commit
git log --oneline --decorate=full # See where tags point
Deleting and Moving
# Delete local
git tag -d v1.0.0
# Delete remote
git push origin --delete v1.0.0
git push origin :refs/tags/v1.0.0 # Alternative syntax
# Move a tag (e.g., after a fix)
git tag -f v1.0.0 <new-sha> # Force move tag
git push origin --force v1.0.0 # Force push (use with caution!)
GitHub Releases
CLI (gh)
# Create release from a tag
gh release create v1.2.0 \
--title "v1.2.0 — New Features" \
--notes "See changelog for details" \
--target main
# With release notes from commits
gh release create v1.2.0 \
--generate-notes
# Upload assets
gh release create v1.2.0 \
./dist/app-linux.tar.gz \
./dist/app-macos.zip \
--title "v1.2.0"
# List/View/Delete
gh release list
gh release view v1.2.0
gh release delete v1.2.0
Auto-Release from Tags (GitHub Actions)
name: Release
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- run: go build -o myapp .
- uses: softprops/action-gh-release@v2
with:
files: myapp
generate_release_notes: true
Changelog Generation
# Generate changelog from git log
git log --oneline --no-merges v1.0.0..v1.1.0 | \
sed 's/^/* /' > CHANGELOG.md
# Conventional commits to changelog
git log --oneline --no-merges v1.0.0..v1.1.0 | \
grep "^.*feat\|^.*fix" | sed 's/^/* /'
Pitfalls
- Tags without releases: A git tag exists, but no GitHub release — users can still
git checkoutit - Force-pushing tags breaks CI: Anyone who fetched the old tag has a mismatch
- Lightweight tags on GitHub: Show without release notes — prefer annotated
- Tag name format: Use
vprefix (v1.0.0not1.0.0) — conventional andghexpects it - Signed tags on clone:
git clonefetches them;git tag -v <tag>verifies
Verification
git tag -n # List all tags with annotations
git describe --tags # Current tag + commits since
gh release list # Verify on GitHub