This answer solves the problem by creating a duplicate annotated tag — including all tag info such as tagger, message, and tag date — by using the tag info from the existing tag.
SOURCE_TAG=old NEW_TAG=new; deref() { git for-each-ref \
"refs/tags/$SOURCE_TAG" --format="%($1)" ; }; \
GIT_COMMITTER_NAME="$(deref taggername)" \
GIT_COMMITTER_EMAIL="$(deref taggeremail)" \
GIT_COMMITTER_DATE="$(deref taggerdate)" git tag "$NEW_TAG" \
"$(deref "*objectname")" -a -m "$(deref contents)"
git tag -d old
git push origin new :old
Update the SOURCE_TAG
and NEW_TAG
values to match your old and new tag names.
Motivation
From what I can tell, all the other answers have subtle gotchas, or don't fully duplicate everything about the tag (e.g. they use a new tag date, or the current user's info as the tagger). Many of them call out the re-tagging warning, despite that not applying to this scenario (it's for moving a tag name to a different commit, not for renaming to a differently named tag). I've done some digging, and I believe I've pieced together a solution that addresses these concerns.
Goal
The git-tag
documentation specifies the parts of an annotated tag. To truly be an indistinguishable rename, these elements should be the same in the new tag.
Tag objects (created with -a
, -s
, or -u
) are called "annotated" tags; they contain a creation date, the tagger name and e-mail, a tagging message, and an optional GnuPG signature.
I'm only addressing unsigned tags in this answer, though it should be a simple matter to extend this solution to signed tags.
Procedure
An annotated tag named old
is used in the example, and will be renamed to new
.
Step 1: Get existing tag information
First, we need to get the information for the existing tag. This can be achieved using for-each-ref
:
Command:
git for-each-ref refs/tags --format="\
Tag name: %(refname:short)
Tag commit: %(objectname:short)
Tagger date: %(taggerdate)
Tagger name: %(taggername)
Tagger email: %(taggeremail)
Tagged commit: %(*objectname:short)
Tag message: %(contents)"
Output:
Tag commit: 88a6169
Tagger date: Mon Dec 14 12:44:52 2020 -0600
Tagger name: John Doe
Tagger email: <[email protected]>
Tagged commit: cda5b4d
Tag name: old
Tag message: Initial tag
Body line 1.
Body line 2.
Body line 3.
Step 2: Create a duplicate tag locally
A duplicate tag with the new name can be created using the info gathered in step 1 from the existing tag.
The commit ID & commit message can be passed directly to git tag
.
The tagger information (name, email, and date) can be set using the git environment variables GIT_COMMITTER_NAME
, GIT_COMMITTER_EMAIL
, GIT_COMMITTER_DATE
. The date usage in this context is described in the On Backdating Tags documentation for git tag
; the other two I figured out through experimentation.
GIT_COMMITTER_NAME="John Doe" GIT_COMMITTER_EMAIL="[email protected]" \
GIT_COMMITTER_DATE="Mon Dec 14 12:44:52 2020 -0600" git tag new cda5b4d -a -m "Initial tag
Body line 1.
Body line 2.
Body line 3."
A side-by-side comparison of the two tags shows they're identical in all the ways that matter. The only thing that's differing here is the commit reference of the tag itself, which is expected since they're two different tags.
Command:
git for-each-ref refs/tags --format="\
Tag commit: %(objectname:short)
Tagger date: %(taggerdate)
Tagger name: %(taggername)
Tagger email: %(taggeremail)
Tagged commit: %(*objectname:short)
Tag name: %(refname:short)
Tag message: %(contents)"
Output:
Tag commit: 580f817
Tagger date: Mon Dec 14 12:44:52 2020 -0600
Tagger name: John Doe
Tagger email: <[email protected]>
Tagged commit: cda5b4d
Tag name: new
Tag message: Initial tag
Body line 1.
Body line 2.
Body line 3.
Tag commit: 30ddd25
Tagger date: Mon Dec 14 12:44:52 2020 -0600
Tagger name: John Doe
Tagger email: <[email protected]>
Tagged commit: cda5b4d
Tag name: old
Tag message: Initial tag
Body line 1.
Body line 2.
Body line 3.
As a single command, including retrieving the current tag data:
SOURCE_TAG=old NEW_TAG=new; deref() { git for-each-ref "refs/tags/$SOURCE_TAG" --format="%($1)" ; }; GIT_COMMITTER_NAME="$(deref taggername)" GIT_COMMITTER_EMAIL="$(deref taggeremail)" GIT_COMMITTER_DATE="$(deref taggerdate)" git tag "$NEW_TAG" "$(deref "*objectname")" -a -m "$(deref contents)"
Step 3: Delete the existing tag locally
Next, the existing tag should be deleted locally. This step can be skipped if you wish to keep the old tag along with the new one (i.e. duplicate the tag rather than rename it).
git tag -d old
Step 4: Push changes to remote repository
Assuming you're working from a remote repository, the changes can now be pushed using git push
:
git push origin new :old
This pushes the new
tag, and deletes the old
tag.
git log --oneline --decorate --graph
is helpful when cleaning up tags. – Joel Purra