I'm trying to determine the commit SHA associated with a particular tag. When I execute show-ref
, I get the following output
$ git show-ref my_tag
6a390ca7bca7b52b2009069138873fdbc7922c1d refs/tags/my_tag
When I execute rev-list
I get this output
$ git rev-list -n 1 my_tag
b6dcf8fa20296d146e9501ab9d25784879adeac8
The commit SHA's are different, but I don't understand why. It looks like b6dcf8
, generated by rev-list
, is the correct one. If I try and checkout the first commit with git checkout 6a390c
and then look at the log, I'm not actually on 6a390c
; b6dcf8
is displayed.
Can anyone explain why there might be a disconnect? Why am I redirected to b6dcf8
when I try and checkout 6a390ca
.
Update
I also noticed that when I execute git show my_tag
, I get output that looks like this
tag my_tag
Tagger: Me <[email protected]>
Date: Mon Apr 4 14:43:46 2016 -0400
Tagging Release my_tag
tag my_tag_Build_1
Tagger: Me <[email protected]>
Date: Thu Mar 31 10:46:18 2016 -0400
Tagging my_tag_Build_1
commit b6dcf8fa20296d146e9501ab9d25784879adeac8
Author: Me <[email protected]>
Date: Wed Mar 30 18:12:10 2016 -0400
Remove secret_key_base values from secrets.yml
It's picking up two tags my_tag
and my_tag_Build_1
. However, if I run git tag
, the list of tags only has
my_tag
If I run git show my_tag_Build_1
, I get
fatal: ambiguous argument 'my_tag_Build_1': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
It seems like git is confused. Maybe the my_tag_Build_1
tag existed at some point, but it doesn't seem to exist anymore.
I'll add one more answer, even though Marcelo Ávila de Oliveira's answer is correct, because I want to draw the graph bits. :-)
Normally I like to draw commit graphs like this, at least for StackOverflow:
...--A--B--C <-- foobranch
\
D--E <-- barbranch
Here the two tip (rightmost) commits on the two branches, C
and E
, each have a branch name pointing to them. That is, refs/heads/foobranch
contains the ID of commit C
, and refs/heads/barbranch
contains the ID of commit E
.
Lightweight tags work exactly like branch names. If we add the tag bartag
to point to commit E
, we get:
...--A--B--C <-- foobranch
\
D--E <-- barbranch, tag: bartag
where refs/heads/bartag
(an actual file in .git
unless it has become "packed" and is now stored in the file .git/packed-refs
instead) also stores the ID of commit E
. There are three differences between a lightweight tag and a branch:
refs/tags/
instead of refs/heads/
.A lightweight tag, then, is simply a reference whose full name is spelled refs/tags/...
. This external reference exists somewhere—often as a separate file like .git/refs/tags/bartag
—and it points to a Git object in the repository (.git/objects/...
, possibly packed into a pack-file). When it points to a commit, which is the normal case, this gets us into the commit DAG: the tag locates the commit, which can get us a work-tree, and also lets us explore earlier (ancestor) commits by following the "parent" ID, going from commit E
back to D
.
An annotated tag uses almost the same picture, except now, instead of the lightweight tag bartag
pointing directly to a commit, now Git stores an annotated tag object into the repository. This annotated tag object has its own data (date, tagger, message, optional digital signature, and whatever else you like), and also stores one hash ID. The hash ID is the target (or object
, as Git spells it) for the tag.
I don't have a preferred style for drawing these here, so I'll just make something up:
...--A--B--C <-- foobranch
\
D--E <-- barbranch
^
:
t <-- tag: annotag
Here, Git has stored a new annotated tag object t
in the repository, and now we have the external reference refs/tags/annotag
pointing to t
. Meanwhile it's the tag object t
that points to commit E
.
This means there are two hash IDs involved with tag annotag
: the ID of the annotated tag object, and the ID of commit E
. Again, the reference points to the annotated tag object, and the object points to the next thing—in this case, to commit E
.
As with lightweight tags, though, annotated tag objects can point to other object types than commits. A lightweight tag cannot point to an annotated tag object, but that's only because, when the reference points to an annotated object, we no longer call it a "lightweight" tag, we now call it an "annotated" tag. An annotated tag object, however, can point to another annotated tag object. Let's do that and make zomgtag
point to object t
:
...--A--B--C <-- foobranch
\
D--E <-- barbranch
^
:
t <-- tag: annotag
^
:
z <-- tag: zomgtag
Now let's try deleting tag annotag
. One interesting thing about Git is that deleting a reference does not actually delete the underlying object. Underlying objects are normally left around until there is too much garbage cluttering up the repository, at which point Git runs git gc --auto
for you. The GC (Garbage Collector) finds the unreferenced objects and actually removes them. This GC is thus a sort of Grim Reaper, or perhaps Grim Collector, that recycles the dead objects back into usable disk space.
This is true for branch name references, for instance: deleting a branch name simply abandons the branch-tip commit, rather than actually deleting it. Moreover, if there's some other way to reach that commit, the commit itself won't go away, even when the Grim Collector comes by. If there's still some linkage, GC leaves the object in place. For normal (non-deleted) branches, when you rebase (which copies commit chains to new chains), the original commit-chain tip IDs are stored in the branch's reflog, which keeps the entire chain reachable until the reflog entries expire. (This means you can go back and recover rebased commits for at least 30 days by default, since 30 and 90 days are the default reflog expiration times.)
But these same rules apply to annotated tag objects! So if we delete annotag
while leaving in zomgtag
, the picture is now:
...--A--B--C <-- foobranch
\
D--E <-- barbranch
^
:
t
^
:
z <-- tag: zomgtag
There is no longer a name for tag object t
, but it is reachable through z
, which we reach through refs/tags/zomgtag
, so t
sticks around in the repository forever. (Well, unless zomgtag
is also deleted, so that t
becomes unreferenced.)
Now there are two Git objects involved in zomgtag
: starting from the external reference, we find the annotated tag object z
. From this we find the annotated tag object t
, and from t
we find commit E
.
Git has a special syntax described in the gitrevisions
documentation for "peeling" a tag: zomgtag^{}
. The description says:
A suffix
^
followed by an empty brace pair means the object could be a tag, and dereference the tag recursively until a non-tag object is found.
If we make more annotated tags, we can have refs/tags/wacky
point to a tag object that points to a second tag object that points to yet another tag object, that eventually, after following many tags, points to z
which points to t
which points to E
. The notation wacky^{}
means "find the non-tag-object" (in this case, a commit, though as always, the endpoint can also be a tree or blob).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With