Get last git tag from a remote repo without cloning

fl00r picture fl00r · May 18, 2012 · Viewed 34.2k times · Source

How to get last tag from a (non checked-out) remote repo?

On my local copy I use describe

git describe --abbrev=0 --tags

But I cannot use describe with remote storage

Answer

Potherca picture Potherca · Oct 3, 2012

TL;DR

With git ls-remote you can get a list of references from a remote repository.

To see what the latest version is, look at the last line of output from:

git -c 'versionsort.suffix=-' ls-remote --tags --sort='v:refname' <repository>

To only output the latest tag (for instance in a shell script) of a repository that uses Semantic Versioning use:

git -c 'versionsort.suffix=-' \
    ls-remote --exit-code --refs --sort='version:refname' --tags <repository> '*.*.*' \
    | tail --lines=1 \
    | cut --delimiter='/' --fields=3

For older versions of Git that don't have the --sort flag (pre v2.18), or versions that don't support versionsort.suffix (pre v2.4) use:

git ls-remote --refs --tags <repository> \
    | cut --delimiter='/' --fields=3     \
    | tr '-' '~'                         \
    | sort --version-sort                \
    | tail --lines=1

Older versions of sort that don't have the --version-sort flag are out of scope for this question...


The long version

Tags only

Using --tags makes sure the list only contains tag references.

This will include both referenced and de-referenced tags. That means some tags will have ^{} at the end of the refname. (For more information about that see this question elsewhere on StackOverflow.)

For human consumption this doesn't matter much, but if you don't want to see those ^{}'s add --refs.

Sorting

It is possible sort the list of references using --sort.

The sort option uses the same sort keys as git for-each-ref. As we don't have all of the information locally, not all of the options are available to us (for instance date related sort keys).

We want to use version sort, based on the reference name. To do so, we use the version:refname key. This can also be abbreviated to v:refname.

This will sort the versions ascending, meaning the latest version will be last.

To reverse the list prepend the sort key with -: --sort='-v:refname'.

Pre-release sorting

At this point, version-sort will place release candidates (for instance v2.28.0-rc2) after the stable version that they should come in front of.

Since v2.12 we can use a configuration flag that tells Git to sort refnames with a specific character suffix after references without that character suffix: git -c 'versionsort.suffix=-'.

To always use versionsort.suffix like this, it can be set globally:

git config --global 'versionsort.suffix' '-'

Between v2.4 and v2.12 the flag is called versionsort.prereleaseSuffix.

Sorting in older versions of Git

For older Git versions a trick can be used: a dash character - is sorted before a space but a tilde ~ is sorted after a space.

So by replacing the dash - with a tilde ~, things get sorted in the right order. This can be done using tr '-' '~'

One line only

As we don't really care for all of the output, other than the last line, we only show the tail: tail --lines=1. Of course, if the list is retrieved in descending order (with --sort='-v:refname'), this would be: head --lines=1.

Just the Refname

The output from the ls-remote command also outputs the reference hash:

ada126bd28d66c8c8ff5966a52d63ce2c9e4d031        refs/tags/v2.28.0-rc0

To only see the actual tag (i.e. the reference name), we can cut of the first part of the line: cut --delimiter='/' --fields=3

Reference filter

The last thing to note is that ls-remote can be given a filter to only show reference that match the filter pattern. For instance, for Semantic Versioning we could use: '*.*.*'. Anything that does not match that pattern will not be shown.

If the repository always prefixes a version tag with a v, it could be narrowed down further to 'v*.*.*'.

Another example is to only retrieve the latest tag for a specific main version. For instance, to only see tags for verion 2 of a repo, we could use 'v2.*'.

Make sure to use quotes around the filter, otherwise that star * will cause you trouble!

Reference not found

When using a filter it is a good idea to use the --exit-code flag.

This is because Git will always exit with status code 0 to indicate it successfully talked with the remote repository.

For human consumption this is fine, as you'll see on the screen if any refs have been found.

If this code is used in a shell script, however, that can be problematic.

Git can be told to use status code 2 when no matching refs are found in the remote repository. This is done by using the --exit-code flag.

That way a script will know when something goes wrong!

Obviosuly, if no filter is used, using --exit-code does not really make sense.

Time for an example!

Lets say we wanted to know what the latest tag of Git is.

We would do:

git ls-remote --sort='version:refname' --tags https://github.com/git/git.git

That would return a long list with all the tags in order, as shown below (truncated for sanity's sake).

    ...

4c8bcdda4d6e4757caf876ddc401b5392e874e21        refs/tags/v2.28.0
ada126bd28d66c8c8ff5966a52d63ce2c9e4d031        refs/tags/v2.28.0-rc0
bd42bbe1a46c0fe486fc33e82969275e27e4dc19        refs/tags/v2.28.0-rc0^{}
49bfe36405d1631a303992cac5cc408980a0454e        refs/tags/v2.28.0-rc1
3ddac3d691c3633cd4d9a74c07e3b2301f546f77        refs/tags/v2.28.0-rc1^{}
84a0d5cc2107b83a791aa4034cc54874e1d50668        refs/tags/v2.28.0-rc2
b066807397fd55553f4910ede74839e319b661fd        refs/tags/v2.28.0-rc2^{}
47ae905ffb98cc4d4fd90083da6bc8dab55d9ecc        refs/tags/v2.28.0^{}

This tells us the latest tag is v2.28.0.

Another example would be to set versionsort.suffix globally and then get just the last tag:

git config --global 'versionsort.suffix' '-'

git ls-remote --refs --sort=':refname' --tags https://github.com/git/git.git \
    | tail --lines=1 | cut --delimiter='/' --fields=3

Now, let's see if there is already a version 3 of Git!

$ git ls-remote --exit-code --refs --tags https://github.com/git/git.git 'v3.*'
$ echo $?
2 # nope, not yet