gitgit-taggit-describe

Git: How to get the name of checked out tag when 2/more tags on same commit


  1. I have a git commit with 2 tags, like this: commit1-----tagA,tagB
  2. Checkout "tagA" by git checkout tagA
  3. Question: How to get the tag name of current checkout? I have try git describe, but it's always return name "tagB", expect return "tagA".

    Seems git describe only be able to return the most recent tag name, see from git manual

    The command finds the most recent tag that is reachable from a commit. If the tag points to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.

Is there any other method?

Purpose related this question:

I want to make file auto build the version number from the tag name, git describe work well when 1 commit with 1 tag, but it's not useful in above case.


Solution

  • Both tagA and tagB point to that one specific commit, so either name is equally good (or equally bad) as far as git is concerned, if you want to check out or look at that particular commit.

    The documentation's phrasing "most recent tag" is perhaps misleading here (although it's likely why you get tagB as output).


    How to get the result you want

    If you want to know what tag-name you gave to git checkout, you can either save that yourself, or consult the reflog for HEAD:

    8004647 HEAD@{0}: checkout: moving from master to v2.3.1
    

    The reflog method has the advantage that it's already implemented; but reflogs are only retained for a while (configurable, default 90 days for reachable references) before being expired away to keep reflogs from growing forever.


    How "git describe" comes up with the answer you didn't want

    "Most recent" means that if tags have associated date-and-time stamps—annotated tags do, lightweight tags don't—then those with a later time are considered "better", as described below.

    Note that at any time, any git command can easily look up all external references and translate them to SHA-1s. To see how this works, simply run git for-each-ref (you may want to pipe this to a pager like less). The output looks something like this:

    c2e8e4b9da4d007b15faa2e3d407b2fd279f0572 commit refs/heads/maint
    9ab698f4000a736864c41f57fbae1e021ac27799 commit refs/heads/master
    [snip]
    74d2a8cf12bf102a8cedaf66736503bb3fe88dfb tag    refs/tags/v2.2.0
    [more snippage]
    

    These are branches and tags—in this git repository (for git itself) all the tags are annotated—and their corresponding SHA-1s. If there were an active stash it would show up as well (under refs/stash).

    In any case, suppose that git describe has, at this point, one of these commit IDs (an SHA-1), and describe has also found two or more names that resolve to that ID. These may just be annotated tag names, which is what you get without options, or they might be another name (like a branch name) allowed by --all, for instance; but the important thing is to suppose that there are two or more names, all all pointing to the same commit.

    The describe command could try remembering all of these names, but it doesn't. Instead, it runs the names through a sort of two-at-a-time tournament to see which one "wins a contest":

    Once all the names for this commit have been contested against each other to choose a "winning name", the "winning name" is saved along with the commit SHA-1.


    Now, how do we get that commit ID in the first place? The answer is that git describe starts with your argument(s):

    $ git describe       # no args, means ...
    $ git describe HEAD  # use HEAD to get the SHA-1
    

    Your arguments (or HEAD) are turned into the appropriate raw SHA-1 using git rev-parse (well, its C code equivalent):

    $ git rev-parse HEAD
    9ab698f4000a736864c41f57fbae1e021ac27799
    

    Then, git describe invokes git for-each-ref (or its C code equivalent) to turn all the names you allow it to use—by default, all annotated tags—into SHA-1 IDs. If any of them match this SHA-1, they're saved. If there are multiple matches, they're run through the contest to pick a single winner.

    For your particular case, this part succeeds: both tagA and tagB are exact matches, so the whole thing stops after picking a winning tag between these two. In your case that was the tag you didn't want.

    In general, though, git describe often has to keep going. Consider, for instance, the following commits from the git project itself:

    088c9a8 strbuf.h: format asciidoc code blocks as 4-space indent
    aa07cac strbuf.h: drop asciidoc list formatting from API docs
    6afbbdd strbuf.h: unify documentation comments beginnings
    bdfdaa4 strbuf.h: integrate api-strbuf.txt documentation
    eae6953 tests: correct misuses of POSIXPERM
    1767c51 t/lib-httpd: switch SANITY check for NOT_ROOT
    b4a56a3 "log --pretty" documentation: do not forget "tformat:"
    

    Now suppose we make one tag X pointing to commit b4a56a3, one tag Y pointing to commit eae6953, and one tag Z pointing to commit aa07cac. If we then ask git to "describe" commit 088c9a8, it could be described as any of the following:

    The output of git describe will use tag Z, because it requires fewer steps to go from "tag Z" (commit aa07cac) to the commit in question (088c9a8). How this actually happens is remarkably (perhaps unnecessarily, although that's a value judgement :-) ) complicated.

    What git describe does here is to walk through (part of) the commit graph (applying --first-parent if specified) to find a "nearby" commit that does have a name. The search order is somewhat difficult to describe: it's partly date-based (a la git log's date-sorted commit listings), but it stops early if it encounters an annotated tag. Otherwise it accumulates a list of "candidate names" (using the contest method described above), stopping when it has accumulated enough (default 10) candidate names, then sorts the list. The sort is based on "graph distance", so assuming X, Y, and Z are all lightweight tags—if any were an annotated tag it would have been picked by now and the search stopped—Z wins here.

    Last, having found a name that points to a suitable commit that is not actually the given-as-argument commit, git describe prints the name, the number of steps distant ("depth"), and the g and part of the actual SHA-1:

    v2.3.3-220-g9ab698f
    

    (though the depth and g can be suppressed, and the whole thing can fail if no names at all can be found or if you've asked for exact matches only, and so on).