50

I've tagged a commit with a lightweight tag, and pushed that tag to a remote repo, shared with other developers. I have now realised I should have annotated it so that it appears in git describe.

Is there a way to convert it/re-tag the commit without breaking things?

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
Stuart K
  • 3,212
  • 4
  • 22
  • 27

4 Answers4

41

A lightweight tag is just a 'ref' that points at that commit. You can force-create a new annotated tag on top of the old tag:

git tag -a -f <tagname> <tagname>

As of Git v1.8.2, you need to use --force to replace any tags on a remote with git push, even if you are replacing a lightweight tag with something that is effectively a fast-forward or a true tag object pointing at the same commit as the existing tag reference.

git push --force origin <tagname>
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 5
    But won't this use a confusing date? – SamB Jan 09 '14 at 19:06
  • 3
    Also, are there any implications for developers that already pulled the original lightweight tag? Will those developers see the annotation to the new tag after they've pulled again, or do they need to do something special (like deleting their local tag) before? – sschuberth Jul 23 '15 at 10:23
  • Doesn't seem to work: `hint: Updates were rejected because the tag already exists in the remote.` I think you really do have to delete the tag first. – Zac Thompson May 20 '16 at 22:19
  • 1
    @ZacThompson: This changed in v1.8.2 (March 2013) – CB Bailey May 20 '16 at 23:47
  • 4
    If you'd rather not have tags tagging tags, do `git tag -a -f ^0` instead. Otherwise you create a new reference to your old tag instead of the stolen `refs/tags/`, excluding it from garbage collection. – Ferenc Wágner Jul 21 '16 at 14:56
  • @sschuberth Those which already have the lightweight tag will not receive the replacement annotated tag until they explicitly run `git fetch --tags` (or do an equivalent manual delete an re-fetch of the tag). – arand Nov 07 '19 at 13:10
  • @arand, by default `git fetch` fetches [any tag that points into the histories being fetched](https://git-scm.com/docs/git-fetch#_description). As the new annotated tag points to the same history / commit than the previous lightweight tag, I'd expected the new annotated tag to be fetched even without the explicit `--fetch`. – sschuberth Nov 07 '19 at 13:26
  • 5
    Warning: If you do this on a GitHub repo, and the lightweight tag you are replacing was associated with a release, deleting it will *silently delete* the entire release (and release notes) associated with the lightweight tag. As noted [here](https://github.community/t/can-deleted-release-notes-be-restored/670), there's no way to restore an accidentally-deleted release. – Edward Jun 25 '20 at 22:25
  • @Edward this warning is of paramount importance. If there is a release associated with a lightweight tag, is there a way to make that tag annotated in-place? i.e., without affecting the release.. – Rafs Sep 16 '22 at 19:16
  • @Edward Second RTD – Dr. Vortex Feb 08 '23 at 23:18
  • @Dr. Vortex and RTD unfortunately, I don't know of a way to make a lightweight tag annotated without affecting its associated GitHub release. I came to this site hoping to find out how to do that but all of the answers here involve deleting and replacing the lightweight tag. – Edward Feb 09 '23 at 17:53
  • @Edward I believe I've found a way to do it: Create a new temporary tag (it can be lightweight), Change the GitHub release to use temporary tag, Replace old tag (using one of the methods described in the answers, and then Change GitHub release to use new tag – Dr. Vortex Feb 10 '23 at 19:25
25

Based on Charles' answer and on this blog post, I think it is better to use something like this:

#!/bin/sh
tag=$1
date="$(git show $tag --format=%aD | head -1)"
GIT_COMMITTER_DATE="$date" git tag -a -f $tag $tag
Community
  • 1
  • 1
SamB
  • 9,039
  • 5
  • 49
  • 56
  • 3
    Nice :) Instead of `| head -1` you can use `-s` or `--no-patch`. And maybe using the _committer_ date (`%cD`) (and also their name, `%cn`, as `GIT_COMMITER_NAME`) might be desirable. – Tobias Kienzler Feb 10 '17 at 09:49
  • And email! `GIT_COMMITTER_EMAIL` – `%ce` (attn. committer is always spelled with double `TT`) – Kay Jul 14 '19 at 12:22
  • 1
    @TobiasKienzler Hmm, I don't think I would use their name unless I had reason to believe they had actually tagged it. Using the committer date rather than the author date does make sense, though, as the tag can't have been made before the commit was, and committer dates are normally not before author dates ... – SamB Aug 14 '21 at 20:16
  • @SamB Ah true, of course I assumed the committer tagged, which _now_ (sorry :) that you mention it is probably impossible to determine from the lightweight tag alone, good point – Tobias Kienzler Aug 16 '21 at 04:11
5

Convert all tags to annotated (based on Charles Bailey's example and Ferenc Wágner's comment):

for tag in $(git tag -l); do git tag -a -f $tag $tag^0 -m $tag; done
git push --tags --force
3

You can also simply use git describe --tags to also include lightweight tags in the search.

Claudio
  • 5,740
  • 5
  • 33
  • 40