Creating Tags
Git gives you multiple ways to create a tag depending on what you need to mark, where in history it lives, and how much metadata you want to attach. This page covers every tagging method: lightweight, annotated, signed, on past commits, on branch tips, and batch workflows — so you have the complete picture in one place.
Overview of all tag creation methods
Method | Command | Use case |
|---|---|---|
Lightweight on HEAD |
| Quick local marker, no metadata |
Annotated on HEAD |
| Official release with author & message |
Signed (GPG) on HEAD |
| Cryptographically verified release |
On a specific commit |
| Retroactive tagging of past work |
On a branch tip |
| Tag the latest commit on a branch |
From another tag |
| Tag the same commit under a new name |
Force overwrite |
| Move an existing tag to HEAD (dangerous) |
Tag the current HEAD (lightweight)
The most common usage: you are on the commit you want to tag, you have verified it is the right one, and you want a simple named pointer with no extra ceremony.
Lightweight tag on current commit
# Confirm you are on the right commit git log --oneline -1 # 7e4f1a2 (HEAD -> main) Finalize API contracts git tag v1.0 # Verify git tag # v1.0 git rev-parse v1.0 # 7e4f1a2c3b4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f
Tag the current HEAD (annotated)
For any release you intend to push to a remote, use an annotated tag. The -a flag creates the tag object; -m provides the message inline.
Annotated tag on current commit
git tag -a v1.0.0 -m "Release 1.0.0" # Multi-line message using shell quoting: git tag -a v1.0.0 -m "Release 1.0.0 Highlights: - New authentication flow - Performance improvements (40% faster cold start) - Deprecation of legacy /v0 endpoints" # Open editor instead of inline message (omit -m): git tag -a v1.0.0
Tag a specific commit hash
You can tag any reachable commit by providing its hash as the last argument. This is the go-to approach when you forgot to tag at release time, or when a CI/CD pipeline is tagging a commit that was built minutes ago.
Tag a commit that is not HEAD
# Find the commit in the log git log --oneline # 9a8b7c6 (HEAD -> main) Post-release docs update # 5d4e3f2 Fix typo in README # 3c2b1a0 Release commit for v0.9.0 ← tag this # 1e0f9d8 Add CI pipeline # Tag by abbreviated hash (lightweight) git tag v0.9.0 3c2b1a0 # Tag by abbreviated hash (annotated) git tag -a v0.9.0 3c2b1a0 -m "Release 0.9.0" # Tag by full hash git tag -a v0.9.0 3c2b1a09e8f7d6c5b4a3f2e1d0c9b8a7f6e5d4c3 -m "Release 0.9.0" # Verify it points to the right commit git log --oneline v0.9.0 # 3c2b1a0 Release commit for v0.9.0
Tag a branch tip
You can pass a branch name instead of a commit hash. Git resolves the branch to its current tip commit and tags that. This is useful when you want to tag the latest commit on a branch without first switching to it.
Tag the tip of a specific branch
# You are on develop, but want to tag the tip of main
git branch
# * develop
# main
git tag -a v1.0.0 main -m "Release 1.0.0"
# Verify — should match the tip of main
git rev-parse v1.0.0^{} # dereference tag to commit
# (equals git rev-parse main)
git rev-parse main
# same hashTag from another tag
You can tag the same commit that another tag already points to by using the existing tag name as the reference. This is occasionally useful when creating an alias (e.g., both v1.0 and v1.0.0 should point to the same release commit).
Create a tag based on another tag
# v1.0.0 already exists
git tag
# v1.0.0
# Create v1.0 pointing to the same commit
git tag v1.0 v1.0.0
# Verify both point to the same commit
git rev-parse v1.0^{}
git rev-parse v1.0.0^{}
# (both output the same commit SHA)GPG-signed tags
Create a cryptographically signed annotated tag
# Prerequisite: GPG key configured git config --global user.signingkey <your-key-id> # Use -s instead of -a git tag -s v1.0.0 -m "Release 1.0.0 (signed release)" # Verify the signature git tag -v v1.0.0 # gpg: Signature made ... # gpg: Good signature from "Alice <alice@example.com>"
Batch tagging workflow
In some projects you might need to apply multiple tags at once — for example, tagging a monorepo where multiple packages have independent version numbers, or backfilling tags for historical commits.
Batch tagging multiple packages in a monorepo
# Tag each package's release commit individually git tag -a api@1.2.0 abc1234 -m "api: release 1.2.0" git tag -a ui@3.0.0 def5678 -m "ui: release 3.0.0" git tag -a shared@0.5.1 ghi9012 -m "shared: release 0.5.1" # List to confirm git tag # api@1.2.0 # shared@0.5.1 # ui@3.0.0
Batch tagging from a script
#!/bin/bash # Script: retag-history.sh # Reads a CSV of "hash,tagname,message" and creates annotated tags while IFS=, read -r hash tagname message; do git tag -a "$tagname" "$hash" -m "$message" echo "Tagged $hash as $tagname" done < releases.csv # releases.csv example: # 3c2b1a0,v0.1.0,First internal release # 7e4f1a2,v0.2.0,Beta release # 9a8b7c6,v0.3.0,Public preview
Cannot create duplicate tag names
Handling duplicate tag errors
# Attempting to create a tag that already exists: git tag -a v1.0.0 -m "..." # fatal: tag 'v1.0.0' already exists # Option 1: delete and recreate (safe if not yet pushed) git tag -d v1.0.0 git tag -a v1.0.0 -m "Release 1.0.0 (corrected)" # Option 2: force overwrite git tag -f -a v1.0.0 -m "Release 1.0.0 (corrected)" # Updated tag 'v1.0.0' (was 3f8a2c9) # If already pushed to remote, also update there: git push origin --delete v1.0.0 git push origin v1.0.0
Verifying your tag before pushing
Checklist before pushing a release tag
# 1. Confirm the tag name is correct git tag | grep v1 # 2. Confirm it points to the intended commit git log --oneline v1.0.0 -1 # (should be your release commit, not a random commit) # 3. For annotated tags, review the full metadata git show v1.0.0 # (review tagger name, date, message) # 4. Check that the working tree was clean when you tagged # (no dirty files sneaked into the release snapshot) git status # 5. Push the tag git push origin v1.0.0