Search code examples
gitsortingtagsbranchgit-tag

What does "tags whose commits are reachable from the specified commit" mean?


In one of my projects, I am trying to get the most recent tag after checking out a branch, or from origin/master. If I run git tag -l --sort=-creatordate --merged | head -n1 or a git describe, I get the same result, but its from a tag from far in the past. If i get rid of the --merged, it shows the correct, most recent tag in the project.

Checking out a specific branch gives me back the correct, last known tag in the repo, but if its from origin/master or FETCH_HEAD, it gives me one in the past.

I'm mainly wondering how the --merged flag works, because the manual pages just says it Only list tags whose commits are reachable from the specified commit.


Solution

  • There are two parts to this:

    • the general concept of reachability, and
    • how git tag -l --merged works, which uses the concept itself.

    Reachability is a property of a graph, or in Git's case, a directed graph. Consult either the Wikipedia article, or Think Like (a) Git, for a proper explanation. (Or see one of my many StackOverflow answers showing how to determine which branches contain various commits.) The rest of this assumes you already understand reachability.

    A tag, in Git, is just a fancy pointer: in effect, an arrow pointing to one particular commit. But there's one other special thing about tags in Git: They can point to a tag object, rather than directly to a commit.1 The tag object has the annotations for git tag -a or message for git tag -m. The tag object then holds the raw hash ID of the underlying commit.

    This brings us to the wording in the documentation:

    the manual pages just says [--merged]

    Only list tags whose commits are reachable from the specified commit.

    We run:

    git tag -l --merged <commit-specifier>
    

    so we pick one commit. That's the "specified commit".

    The git tag -l operation will now iterate through every tag. The tag either points directly to a commit, or to an annotated tag object that points to a commit. That commit exists somewhere in the overall Git commit graph. The question that --merged applies is: can we reach the tagged commit from the specified commit?

    If we can reach the tagged commit, git tag -l lists the tag. If not (or if we must skip the tag because it does not tag a commit after all), git tag -l omits the tag.


    1A tag name is actually allowed to point to a tree or blob object, as well as to a commit or annotated tag object. In this case there is no commit at all, and git tag -l --merged will just ignore that tag. Also, an annotated tag object can actually refer to any other kind of object, not just a commit object. So the true rule for resolving a tag name is:

    • If tag name refers to an annotated tag object, switch to that annotated tag object.
    • While we have an annotated tag object, switch to the object that is the target of this annotated tag. (Git refers to this internally as peeling tags, analogous to peeling an onion, layer by layer.)
    • Stop: we have an object.

    We now know the object to which the tag refers. If it's a commit, it's a candidate for this --merged listing. If not, it's not.