Macs, Modularity and More

Git Tip of the Week: Tags

This week’s Git Tip of the Week is about working with tags. You can subscribe to the feed if you want to receive new instalments automatically.

Descriptive labels

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.

Annotated Tags

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.

Examples

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 -m:

$ 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 -v.

To list the local repository’s tags, run git tag without any arguments; or, for a pattern, use -l with * as a wildcard:

$ git tag
example
v1
v1s
$ git tag -l *s
v1s

Finally, to get rid of tags, you can delete them with -d:

$ 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:

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.

The letter 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.

Summary

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.