Git Release Tags: Mark and Share the Right Commit
Create an annotated release tag on the exact approved commit, review its target and push only that tag—even if your branch has moved since testing.
A release branch can move after testing finishes. Your release tag should not follow it. Create an annotated tag on the explicit commit ID that passed review, check its target, then push that one tag to the intended repository. Do not assume today's HEAD is still yesterday's approved release.
The examples use Bash or Git Bash. This guide creates an unsigned annotated tag named v1.2.0; substitute your project's version and remote. If your project requires signed tags, use its approved signing process rather than the unsigned creation command below. A tag marks source history. It does not build, test or deploy that source.
Start with the approved commit, not the current branch
Get the full commit ID from the release review or build record. If you need to locate it in local history, git log --oneline -n 20 can help, but a matching subject is not proof that you found the reviewed revision.
In the commands below, replace COMMIT_ID with that real ID, including inside quotes:
git rev-parse --verify 'COMMIT_ID^{commit}'
git show --stat COMMIT_ID
git show COMMIT_ID The first command must succeed and print the expected full commit ID. The ^{commit} suffix asks Git to resolve a commit, rather than accepting just any object. The other commands let you inspect the files and patch; compare those with the release approval. Git documents these operations in its revision parser and object inspection reference.
You do not have to switch branches to tag this commit. In our disposable Git 2.52.0 test, the branch had advanced to a later commit, but supplying the earlier ID still tagged the intended release. Staged, unstaged and untracked files are not part of that earlier commit. Tag creation does not save them or discard them. If the successful test run depended on local edits, resolve that gap before treating the commit as release-ready.
Check the destination and whether the name is already used
Check the configured fetch and push destinations before assuming origin is the release repository:
git remote get-url --all origin
git remote get-url --push --all origin Different transport URLs can identify the same repository. What matters is that the repository you inspect is the one that will receive the tag. If these point to different projects, or several push destinations appear unexpectedly, stop and confirm the release remote with a maintainer. Git's remote reference explains the separate URL views.
Look for the exact version locally and on that remote:
git tag --list 'v1.2.0'
git ls-remote --tags origin 'refs/tags/v1.2.0' An empty local listing means that name is absent locally. An empty remote listing is useful only if the remote command completed successfully; an access error is not a free-name check. If either listing returns the tag, investigate before proceeding. The remote listing manual documents this query.
This is a preflight check, not a reservation. Another releaser can create the same name before your push. Keep the eventual push response as part of the release check.
Create the annotation, then verify its target
Once the commit, version and destination are approved, create the local tag:
git tag --no-sign -a v1.2.0 COMMIT_ID -m 'Release v1.2.0' -a creates a tag object with a message and tagger information. --no-sign deliberately makes this example unsigned even when local configuration normally signs tags; it is not an alternative to a required signing policy. Git refuses an existing tag name unless you request replacement. These options are described in the tag manual.
Now inspect the annotation and the commit behind it:
git show --no-patch refs/tags/v1.2.0
git rev-parse --verify 'refs/tags/v1.2.0^{commit}' The final ID must equal the approved COMMIT_ID, not necessarily the current HEAD. Check the release name and message as well. Using the full refs/tags/ name also avoids confusion with a same-named branch.
If this newly created tag is wrong and has never been shared or consumed by another process, remove that local name with git tag -d v1.2.0, recheck the intended commit and create it again. If anyone may already rely on it, preserve the evidence and coordinate the correction instead. Do not force a published release label to a different target.
Share exactly this tag
Pushing changes the shared repository and may trigger the project's release automation. Proceed only when that is the approved next release step:
git push --no-follow-tags origin refs/tags/v1.2.0:refs/tags/v1.2.0 The explicit source and destination name the same single tag. --no-follow-tags prevents a configured push.followTags setting from adding other reachable annotated tags. Avoid --tags, which sends all local tags. Git's push reference documents these differences.
Read the result. An existing remote tag, access restriction or repository policy can reject the push. Do not add force to make a rejection disappear. In the local test, the scoped push sent only v1.2.0 even with tag-following enabled and other annotated tags present; a conflicting replacement was rejected.
After a successful push, query the exact tag and its peeled target:
git ls-remote --tags origin \
'refs/tags/v1.2.0' 'refs/tags/v1.2.0^{}' For the annotated commit tag created here, the line ending in ^{} should contain the approved commit ID. Keep the full reference names when reading the output. If the result is missing or different, use the checks for an existing release tag to separate a name, object or commit mismatch before changing anything.
Finish with a release record, not just a successful push
Record the version, full commit ID and the review or build that approved it. Then check any release job separately: acceptance of a Git reference does not establish that a build or deployment succeeded. Nor does an unsigned tag authenticate its tagger or prove that a downloaded artefact was built from the tagged source.
The useful outcome is a stable release name on the agreed commit, accepted by the intended remote, with the project's remaining release checks accounted for. Use a new version name for the next release rather than quietly reusing this one.
Commands checked on 29 October 2026 with Git 2.52.0 on Linux in disposable repositories and a local bare remote. Hosted permissions, signing and release automation were not exercised.
Subscribe
Get new articles by email. Unsubscribe with one click.