Home » Git » git describe with two tags on the same commit

git describe with two tags on the same commit

Posted by: admin November 15, 2021 Leave a comment

Questions:

We occasionally have two tags on the same commit. When we use git describe for that commit, git describe always returns the first tag. My reading of the git-describe man page seems to indicate that the second tag should be returned (which makes more sense).

  SEARCH STRATEGY
     For each committish supplied, git describe will first look for a tag which tags
     exactly that commit. Annotated tags will always be preferred over lightweight tags, 
     and tags with newer dates will always be preferred over tags with older dates. 
     If an exact match is found, its name will be output and searching will stop.

Is there a way to have git describe return the second tag?

Answers:

Have you tried any of the options to git describe?

   --all
       Instead of using only the annotated tags, use any ref found in .git/refs/. This option enables
       matching any known branch, remote-tracking branch, or lightweight tag.

   --tags
       Instead of using only the annotated tags, use any tag found in .git/refs/tags. This option
       enables matching a lightweight (non-annotated) tag.

###

From all I can tell, ‘git describe’ cannot disambiguate lightweight tags and so prints the first one it encounters. This snippet assumes tags follow a pattern sortable by ‘sort -R’ and will return the ‘latest’ tag on a given SHA:

git tag --contains SHA | sort -R | tail -1

###

Git’s behaviour in this case is bizarre and confusing.

When I make two tags to the same commit, I notice that in .git/refs/tags that each of the tags has it’s own commit so it’s theoretically possible to checkout an exact tag in a unambiguous fashion.

In practice that’s not so.

Lets say I have commit ABCD. I make two tags to it (annotated), v1.0 and v2.0.

I then have something like this..


master -> ABCD
hotfix -> ABCD
v1.0 (3423) -> ABCD
v1.0 (4234) -> ABCD

When I checkout a branch such as master or hotfix, I notice that git simply stores in .git/HEAD the ref to the branch so all is good, it’s not ambiguous but a specific branch.

When I checkout the commit directly, it will be inherently ambiguous. HEAD will contain simply the hash of the commit, ABCD.

When you checkout a tag such as v1.0 or v2.0, HEAD will not contain the tag ref or tag commit but instead the commit id, as if you checked out the commit directly!

Where this becomes confusing is that if you checkout a branch such as master, then check out a tag, git status and describe will show the right tag, the one that you checked out, even though it’s ambiguous!

If you then however checkout another tag pointing to the same, it’ll show the original tag. Switching from branch to tag, remembers the tag, switching from tag to tag doesn’t.

I don’t know if this is a bug or how git is even doing it (I’m guessing it repeats (.git/logs/HEAD) but given the behaviour appears arbitrary I’d hazard a guess that if you simply want to use a command to get at what the user has selected from the top down be it a tag, branch or commit then I don’t think that’s reliably supported.

If you’re trying to use a command to get at the version automatically then you’re going to need to either have the user manually enter the tag or have some procedure in place to eliminate collisions.

Lightweight tags (not annotated, don’t have a commit themselves, are just a pointer straight to a commit) behave in the same strange manner. Given it’s able to preserve exactly where the user checked out in one case but fails in the other, I’d propose that it’s a bug and should be reported.

The use case for this is that the user only checks out one thing, even though that identifier might point to something with many other identifiers. For convenience, you want to get the identifier the user put in to use as an identifier for example for a build. Git’s ability to remember that identifier is inexplicably inconsistent.

In this case your scripts will need to attempt to derive a single best identifier but if the identifier is ambiguous it should produce an error. You cannot rely on things such as git status or describe as sometimes they’ll not produce what was last checked out as seen when switching from tag to tag rather than branch to tag.

This can be seen in .git/logs/HEAD which appears to contain branch to tag reports but once you’re on a tag doesn’t log anything.

Describe appears to always return the most recent annotated (non-lightweight) tag. If you’re mixing tag types, you should not assume consistent behaviour. Light weight tags appear to use the most recent version as well (presumably based on the file’s time stamp rather than commit time) but aren’t searched without --all or --tags. Even with –all, annotated tags appear to take precedence over more recent lightweight tags.

The only convenient way to get all identifiers for the current tag that I can find is to run git show-ref with dereference and to grep for your current commit. This won’t include timestamps for sorting.

###

I had two tagging conventions on the same commit and wanted to keep the git describe features such as dirty, sha, and commit counts past tag etc.
So in git version 2.24.1 since I had a unique differentiating string in one tag vs the other I just used the match operator. You can also use exclude

git describe --tags --match '*xXx*'

###

Two methods, depending on your need.

Method 1:

git tag -l | sort -V | tail -1

If you want to check for a specific format, e.g. semantic version

git tag -l | grep "v[0-9]*.[0-9]*.[0-9]*$" | sort -V | tail -1

Method 2:

git tag --sort=committerdate | tail -1

If you want to check for a specific format, e.g. semantic version

git tag --sort=committerdate | grep "v[0-9]*.[0-9]*.[0-9]*$" | tail -1