[git] How do you rename a Git tag?

Today I was looking through the logs for a project and realized that I fat fingered a tag name some time ago. Is there some way to rename the tag? Google hasn't turned up anything useful.

I realize I could check out the tagged version and make a new tag, I even tried that. But that seems to create a tag object that isn't quite right. For one,

git tag -l

lists it out of order relative to all of the other tags. I have no idea if that's significant, but it leads me to believe that the new tag object isn't quite what I want. I can live with that, because I really only care that the tag name matches the documentation, but I'd rather do it "right", assuming there is a right way to do this.

This question is related to git git-tag

The answer is


For the adventurous it can be done in one command:

mv .git/refs/tags/OLD .git/refs/tags/NEW

This wiki page has this interesting one-liner, which reminds us that we can push several refs:

git push origin refs/tags/<old-tag>:refs/tags/<new-tag> :refs/tags/<old-tag> && git tag -d <old-tag>

and ask other cloners to do git pull --prune --tags

So the idea is to push:

  • <new-tag> for every commits referenced by <old-tag>: refs/tags/<old-tag>:refs/tags/<new-tag>,
  • the deletion of <old-tag>: :refs/tags/<old-tag>

See as an example "Change naming convention of tags inside a git repository?".


Follow the 3 step approach for a one or a few number of tags.

Step 1: Identify the commit/object ID of the commit the current tag is pointing to

command: git rev-parse <tag name>
example: git rev-parse v0.1.0-Demo
example output: db57b63b77a6bae3e725cbb9025d65fa1eabcde

Step 2: Delete the tag from the repository

command: git tag -d <tag name>
example: git tag -d v0.1.0-Demo
example output: Deleted tag 'v0.1.0-Demo' (was abcde)

Step 3: Create a new tag pointing to the same commit id as the old tag was pointing to

command: git tag -a <tag name>  -m "appropriate message" <commit id>
example: git tag -a v0.1.0-full  -m "renamed from v0.1.0-Demo" db57b63b77a6bae3e725cbb9025d65fa1eabcde
example output: Nothing or basically <No error>

Once the local git is ready with the tag name change, these changes can be pushed back to the origin for others to take these:

command: git push origin :<old tag name> <new tag name>
example: git push origin :v0.1.0-Demo v0.1.0-full
example output: <deleted & new tags>

Here is how I rename a tag old to new:

git tag new old
git tag -d old
git push origin new :old

The colon in the push command removes the tag from the remote repository. If you don't do this, Git will create the old tag on your machine when you pull. Finally, make sure that the other users remove the deleted tag. Please tell them (co-workers) to run the following command:

git pull --prune --tags

Note that if you are changing an annotated tag, you need ensure that the new tag name is referencing the underlying commit and not the old annotated tag object that you're about to delete. Therefore, use git tag -a new old^{} instead of git tag new old (this is because annotated tags are objects while lightweight tags are not, more info in this answer).


The original question was how to rename a tag, which is easy: first create NEW as an alias of OLD: git tag NEW OLD then delete OLD: git tag -d OLD.

The quote regarding "the Git way" and (in)sanity is off base, because it's talking about preserving a tag name, but making it refer to a different repository state.


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.


Regardless of the issues dealing with pushing tags and renaming tags that have already been pushed, in case the tag to rename is an annotated one, you could first copy it thanks to the following single-line command line:

git tag -a -m "`git cat-file -p old_tag | tail -n +6`" new_tag old_tag^{}

Then, you just need to delete the old tag:

git tag -d old_tag

I found this command line thanks to the following two answers:

Edit:
Having encountered problems using automatic synchronisation of tags setting fetch.pruneTags=true (as described in https://stackoverflow.com/a/49215190/7009806), I personally suggest to first copy the new tag on the server and then delete the old one. That way, the new tag does not get randomly deleted when deleting the old tag and a synchronisation of the tags would like to delete the new tag that is not yet on the server. So, for instance, all together we get:

git tag -a -m "`git cat-file -p old_tag | tail -n +6`" new_tag old_tag^{}
git push --tags
git tag -d old_tag
git push origin :refs/tags/old_tag

In addition to the other answers:

First you need to build an alias of the old tag name, pointing to the original commit:

git tag new old^{}

Then you need to delete the old one locally:

git tag -d old

Then delete the tag on you remote location(s):

# Check your remote sources:
git remote -v
# The argument (3rd) is your remote location,
# the one you can see with `git remote`. In this example: `origin`
git push origin :refs/tags/old

Finally you need to add your new tag to the remote location. Until you have done this, the new tag(s) will not be added:

git push origin --tags

Iterate this for every remote location.

Be aware, of the implications that a Git Tag change has to consumers of a package!


As an add on to the other answers, I added an alias to do it all in one step, with a more familiar *nix move command feel. Argument 1 is the old tag name, argument 2 is the new tag name.

[alias]
    renameTag = "!sh -c 'set -e;git tag $2 $1; git tag -d $1;git push origin :refs/tags/$1;git push --tags' -"

Usage:

git renametag old new

You can also rename remote tags without checking them out, by duplicate the old tag/branch to a new name and delete the old one, in a single git push command.

Remote tag rename / Remote branch ? tag conversion: (Notice: :refs/tags/)

git push <remote_name> <old_branch_or_tag>:refs/tags/<new_tag> :<old_branch_or_tag>

Remote branch rename / Remote tag ? branch conversion: (Notice: :refs/heads/)

git push <remote_name> <old_branch_or_tag>:refs/heads/<new_branch> :<old_branch_or_tag>

Output renaming a remote tag:

D:\git.repo>git push gitlab App%2012.1%20v12.1.0.23:refs/tags/App_12.1_v12.1.0.23 :App%2012.1%20v12.1.0.23

Total 0 (delta 0), reused 0 (delta 0)
To https://gitlab.server/project/repository.git
 - [deleted]               App%2012.1%20v12.1.0.23
 * [new tag]               App%2012.1%20v12.1.0.23 -> App_12.1_v12.1.0.23

If it's published, you can't delete it (without risking being tarred and feathered, that is). The 'Git way' is to do:

The sane thing. Just admit you screwed up, and use a different name. Others have already seen one tag-name, and if you keep the same name, you may be in the situation that two people both have "version X", but they actually have different "X"'s. So just call it "X.1" and be done with it.

Alternatively,

The insane thing. You really want to call the new version "X" too, even though others have already seen the old one. So just use git-tag -f again, as if you hadn't already published the old one.

It's so insane because:

Git does not (and it should not) change tags behind users back. So if somebody already got the old tag, doing a git-pull on your tree shouldn't just make them overwrite the old one.

If somebody got a release tag from you, you cannot just change the tag for them by updating your own one. This is a big security issue, in that people MUST be able to trust their tag-names. If you really want to do the insane thing, you need to just fess up to it, and tell people that you messed up.

All courtesy of the man pages.