$ git tag -l --sort=taggerdate --format='%(creatordate:short) %(refname:strip=2)'
2021-08-11 v0.4.2
2022-02-02 v0.7.0c
2021-08-10 v0.4.0
2021-08-10 v0.4.1
2021-09-15 v0.5.0
2021-11-04 vfix-12
2021-11-17 vfix-23
this also affected the git describe result, it is using the vfix-23 instead of v0.7.0c. Is this expected or this is some kind of bug?
update
according to @torek I should show the taggerdate instead:
$ git tag -l --sort=taggerdate --format='%(taggerdate:short) %(creatordate:short) %(refname:strip=2)'
2021-08-11 v0.4.2
2022-02-02 v0.7.0c
2021-08-10 2021-08-10 v0.4.0
2021-08-10 2021-08-10 v0.4.1
2021-09-15 2021-09-15 v0.5.0
2021-11-04 2021-11-04 vfix-12
2021-11-17 2021-11-17 vfix-23
We can see there is not taggerdate for v0.7.0c
CodePudding user response:
TL;DR
Only annotated tags have their own separate tagger date. As long as the tag is for a commit, both annotated tags and lightweight tags will have a "creator date", but only the annotated tags will have a separate "tagger date" here. Displaying both values helps sort this out; note that there's also the * format prefix to indirect from an annotated tag to the commit, so as to get consistent handling.
The git describe problem is occurring in this case for a different reason: by default, git describe ignores lightweight tags entirely. The idea here is that annotated tags are "more real" in some sense; you're supposed to use annotated tags for anything serious. Here's what the git tag documentation says about this:
Annotated tags are meant for release while lightweight tags are meant for private or temporary object labels. For this reason, some git commands for naming objects (like
git describe) will ignore lightweight tags by default.
Long
For Git tags, there are effectively two "kinds" of tags, which Git calls lightweight and annotated. It's a good idea to learn about the underlying implementation here because Git's abstractions leak a lot. In this particular case, the date-stamp details leak out. So, here are the details:
A ref is a name, generally starting with
refs/, that contains a Git hash ID or Object ID, aka OID. Git has four internal object types—commits, annotated tags, trees, and blobs—and each object gets an OID. The OIDs you see the most (because you may have to cut-and-paste them for instance) are those for commit objects; we rarely view tag OIDs, and even less rarely view tree or blob OIDs, but they all exist and some refs can store any kind of OID.Note that each name holds exactly one OID.
Branch names start with
refs/heads/, somasteris reallyrefs/heads/master,mainis reallyrefs/heads/main,feature/tallis reallyrefs/heads/feature/tall, and so on. Branch names are limited: they can only hold commit OIDs. They have some extra features: you can get "on a branch", and when you do, that's the commit you have checked out right now, and when you make a new commit, Git automatically stores the new commit's hash ID into the branch name. (This has nothing to do with the tags, but is worth knowing.)Tag names start with
refs/tags/. Unlike branch names, a tag name can hold any kind of OID. It's normal, though, to store either a commit OID or an annotated tag OID into a tag name.
The internals of a commit object are somewhat relevant here, so let's look at the top section (up to the first blank line) of an example commit:
$ git cat-file -p 22d2f70e85e767abba2e284e32c0edb7f749e29c | sed -e 's/@/ /' -e '/^$/q'
tree 84d6d269a557b1e109af0a0ff3706e29756bd490
parent f2b255141b3008a46b4946e7da44b966797e4355
author Ævar Arnfjörð Bjarmason <avarab gmail.com> 1641919223 0100
committer Junio C Hamano <gitster pobox.com> 1642109949 -0800
We'll ignore the tree and parent lines and concentrate on the author and committer lines here. These contain three parts each, besides the keyword at the front:
There's a user name (text string) that appears before the opening angle bracket. This is the name of the author or committer. It's an arbitrary byte sequence (except for the fact that it may not contain newlines or angle brackets) that is chosen by the person making the commit, so it cannot really be trusted on its own.
Then there is an opening angle bracket, an email address, and a closing angle bracket. The email address is a similar user-specified string that cannot really be trusted on its own. People don't normally put egregious lies in these fields, but you must remember that they are essentially uncontrolled by Git itself.
Finally, there are Git-generated fields consisting of two strings of digits, with the last one being signed. These are the author date (for the author line) and committer date (for the committer line).
The timestamps are Unix-style time stamps (seconds since 1970, leap second handling unspecified) along with the time zone offset in hours and minutes: negative offsets, like -0800, represent west-of-GMT values such as Pacific Standard Time (-8 hours) and positive offsets like 0100 represent east-of-GMT values (e.g., Norway, on Central European time at GMT 1).
Now let's look at a tag object, in this case the annotated tag object for Git version 2.35.1:
$ git cat-file -p v2.35.1 | sed 's/@/ /'
object 4c53a8c20f8984adb226293a3ffd7b88c3f4ac1a
type commit
tag v2.35.1
tagger Junio C Hamano <gitster pobox.com> 1643417339 -0800
Git 2.35.1
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEE4fA2sf7nIh/HeOzvsLXohpav5ssFAmH0jvsACgkQsLXohpav
5ssJ8w//aAEXTNfDH15U PwZ95OxAGV0mT4JCSK7fjB7kjqAMRLziVGPrlXzGTjc
wxKVd5nvQAjB86P sKSC/6AUx38QXlsurtUb yhRMcogXm2ESdHTQ8IsofCo4yRq
zDAQyf8RPKJFygMdVDjfXOIRP5NXTqVTAa67QihZOuqy5PvcEh7IRScd0f5pIsyD
RO55G7eC9KZZJH5yrz0OpEj3xHxqZL tO22ue777YUagUxS8Kt 183Fyc73H/eBz
69TxEc76t54AjNNFgghTiF00zirl7qv/lRCYldqFfpoutxtAHdjuiy7E h/NhY9r
fkrVVGA3ms//ln32SU26Jm3zxEu31PSrCqP2KzdTdVO/I97jWnWytEZSWvVCZB6m
BZDG1yKrLSlH48SqgxnPLGMeeqA1kldqI9e3lgpTMPUJO7xjXi0i10lFhL4N pPX
VMsjxlcaV6A1ey0CMCMtBZ2g8sJkKUuSA/HJrhhXBJkl88oWUAvPC6zIBG2xrDHy
1qc4cAFNw4zwJylnyUYXbkeRn1/Z6lg8JeUfdWdZktjmL0ZbgLTM CWKJhx/1WN2
hu5DEFa7yH4IS4GW6W6N14mxTrMC2IehqKLhHd/0L8cqEVsnUb0xWLa0zChAdM7s
G1XpNQSJxyBj74tJ8lMB 0P1cpqQF4ZkVk2Tx8e0xyVEaa5Vljg=
=/DjY
-----END PGP SIGNATURE-----
Here, we see that this annotated tag:
- carries an OID (for commit
4c53a8c20f8984adb226293a3ffd7b88c3f4ac1ain this case: that's the commit that is Git 2.35.1); - carries a
typefield, which is the type of the target object; - carries a
tagfield, which contains the tag's name, which should match the name we just used to find it, if we used a tag name to find it; and - carries a
tagger, which looks a great deal like anauthororcommitterin a commit object.
In particular the tagger line has the same three fields: name, email address, and timestamp-with-time-zone.
This particular annotated tag also carries a PGP signature (see digital signature and Pretty Good Privacy). The signature provides a testable assertion that Junio Hamano did in fact sign off on this particular tag, representing that this tag for this commit is really his work, and not some counterfeit. That, in turn, verifies—to whatever extent you trust PGP and the Merkle chains in Git1—the tagged commit and all of its history. So the presence of this PGP-signed annotated tag gives you at least some assurance that the author and committer text strings can be trusted.
1Note that current Git Merkle trees use SHA-1, which is a bit dodgy given current computing abilities. However, they place limitations on the SHA-1 usage, which firms it up a bit. Still, SHA-2 would help here.
Now we can really define annotated tags properly
Now that you know what a tag object is, looks like, and does for you, we can really see the difference between a lightweight tag and an annotated tag. A tag name, like refs/heads/v2.35.1 in the Git repository for Git, contains a hash ID:
$ git rev-parse v2.35.1
73807ff71e1acef4cfa35fe5cfb16d5878b6cd12
$ git cat-file -t 73807ff71e1acef4cfa35fe5cfb16d5878b6cd12
tag
This is the tag object we printed out earlier. That tag object contains a commit hash ID, and that commit hash ID is the tagged commit. So this is an annotated tag for Git v2.35.1. There are two special syntax constructs for git rev-parse to make it follow the tag:2
$ git rev-parse v2.35.1^{}
4c53a8c20f8984adb226293a3ffd7b88c3f4ac1a
$ git rev-parse v2.35.1^{commit}
4c53a8c20f8984adb226293a3ffd7b88c3f4ac1a
and that (4c53a8c...) is the value we saw earlier.
If we make a lightweight tag now, point it to the same commit, and use git rev-parse on it, we'll get the commit hash ID, not that of an annotated tag:
$ git tag tmp v2.35.1^{}
$ git rev-parse tmp
4c53a8c20f8984adb226293a3ffd7b88c3f4ac1a
This is a lightweight tag because it points directly to the commit. Deleting that tag and creating a tag with a message makes a new annotated tag object:
$ git tag -d tmp
Deleted tag 'tmp' (was 4c53a8c20f)
$ git tag -m message tmp v2.35.1^{}
$ git rev-parse tmp
2b92bdd78a61718279a75ce722b14979efcc40a4
$ git cat-file -p tmp | sed 's/@/ /'
object 4c53a8c20f8984adb226293a3ffd7b88c3f4ac1a
type commit
tag tmp
tagger Chris Torek <chris.torek gmail.com> 1643936151 -0800
message
So it's the annotations—whether made with -a or -m or -s or -u—that carry the extra data, including the tagger date and optional PGP, GPG, or other digital signature.3 To get these, you must create an annotated tag. Only then will git describe use it by default.
2The ^{} suffix means find the non-tag object, while the ^{commit} suffix means find the non-tag object and make sure it is a commit object. Since a tag object can point to any of the four kinds of object, a tag object does not necessarily point to a commit. It could point to a tree or blob object, for instance—or it can even point to another tag object! If it does point to another tag object, Git will follow that tag to its target, and will repeat this following process until it arrives at some non-tag object. Git calls this process "peeling the tag", using the idea that tag objects are like layers in onions, except that there should be something more than just an odor remaining once you've peeled off all the layers.
3Git is growing support for ssh-key-based signing. It's still not quite ready for most users, I think, but it's getting there.
