Git provides a couple of mechanisms for identifying changes by labels instead of by unique hash values.
The first, we’ve already seen, is branches. When we switch between two branches, we’re really using the descriptive label to identify a specific commit to switch to.
The second, which we’ll introduce here, is tags. A tag is like a branch, in that it identifies a specific commit with a descriptive label.
Branches versus Tags
What’s the difference between tags and branches? The workspace is (almost always) associated with a branch, called
master by default. When it is, a commit will automatically update the
master reference to point to that new commit; in other words, branches are mutable references.
A tag, on the other hand, is created to point to a specific commit and thereafter does not change, even if the branch moves on. In other words, tags are immutable references.
Git has two flavours of tags; annotated and non-annotated. When using them, there is little difference between the two; both will allow you to refer to a specific commit in a repository.
An annotated tag creates an additional tag object in the Git repository, which allows you to store information associated with the tag itself. This may include release notes, the meta-information about the release, and optionally a signature to verify the authenticity of the commit to which it points.
We can create a simple tag, based on the current repository’s version, with:
$ git tag example
This creates a lightweight tag as a reference in
.git/refs/tags/example, which points to the current commit. If we want to make it as an annotated tag, we need to supply
-a, and a message with
$ git tag -a v1 -m "Version 1 release"
This will create an (unsigned) annotated tag object, containing that message and a pointer to the commit object. Now the reference in
.git/refs/tags/v1 will point to the tag object, which then points to the commit.
If we wanted to guarantee the authenticity of the tag, we could use
-s on the
git tag command. This uses
gpg to sign, based on your email address – though you can use
-u to specify a different
gpg identity instead. You can verify the signature of an existing tag with
To list the local repository’s tags, run
git tag without any arguments; or, for a pattern, use
* as a wildcard:
$ git tag example v1 v1s $ git tag -l *s v1s
Finally, to get rid of tags, you can delete them with
$ git tag -d v1 $ git tag example v1s
Deleting tags are OK if you never made them publicly available, but you really should avoid deleting tags once you’ve pushed them to a publicly readable location. Similarly, you shouldn’t change a tag once it has been released to the wild either.
Contents and Describe
In order to see what the tag contains, you can use
git show, as you can with other git objects:
$ git show v1s tag v1s Tagger: Al Blue <email@example.com> Date: Tue Apr 20 09:00:00 2011 +0100 Version 1 signed -----BEGIN PGP SIGNATURE----- Version: GnuPG/MacGPG2 v2.0.14 (Darwin) iF4EABEITke4AkyRUh8ACgkQWwXM3hQMKHZq5QD/esqKyinelXGM1TSzUqEzuBdI Ah2Cq/5TS3j4kiP4+UUA/2nN2SVoWwYryN9234kgWUvZIrV1P0FGTG+lAEN5avj3 =JICf -----END PGP SIGNATURE----- commit 91a2b24....
If the tag is an annotated tag, you’ll see the message and the tag object, followed by the commit. If the tag is a lightweight tag, then you’ll see only the commit object.
A key difference between annotated and non-annotated tags is in the use of
git describe. This gives an identifier of the repository, based off of the nearest annotated tag. If we were to run now, we’d see a reference to the
v1s annotated tag:
$ git describe v1s
If the current commit exactly matches that of a tag, then only the tag name is printed. If there are changes, then
git describe will print out the tag name, a hyphen, the number of commits made, a hyphen, the letter ‘g’ and then the commit identifier. This allows anyone to use that explicit revision to identify the commit, through the hash at the end. As such, it is often useful to include that in file versions as a means of identifying it at a later stage.
# Add a commit $ touch file $ git add file $ git commit -m "Adding file" $ git describe v1s-1-g24242c3
g is added to denote a
git managed version; so other repositories can use the same format but substitute that letter for a different one.
If no annotated tags are found then it will print
fatal: No names found, cannot describe anything. To allow
describe to use non-annotated tags, run with
git describe --tags. It’s also possible to get it to describe against a branch using
git describe --all, although this only makes sense if the branch is known remotely.
Pushing and Pulling
Since a tag (either annotated or lightweight) is just a reference on your local repository, it is not sent up by default to the remote repository during pushes. (This is one observable difference between Git and Hg.) Instead, you can
git push the tag individually, or you can run
git push --tags which will push all tags. For “release” tags (e.g. V1.0.0) it is conventional for these to be annotated tags; it is relatively rare that you will push a lightweight tag to a central repository.
For pulling, any tags associated with your current branch will be fetched when you check it out. This may result in not having all the tags in your local repository that the remote repository has. If you’d like to fetch them all, you can do
git fetch --tags to pull them all in, or
git fetch tag to pull a single one.
Tags in git are lightweight references that point to an SHA hash of a commit. Unlike branches, they are not mutable and once created should not be deleted. Tags may be lightweight (in which case they refer to the commit directly) or annotated (in which case they point to a tag object which points to the commit). Tags used to denote versioned releases typically use annotated tags, and for many open source projects, the tags will also be signed.
Come back next week for another instalment in the Git Tip of the Week series.