Retrieve latest tag from GitHub with Go

To monitor the latest activity of Git repositories, I was using the go-github golang package to interface with GitHub’s API. While the package API is very rich and powerful, I was still missing one feature: just getting the latest tag of a specific repository. It does have an option to get the latest release, which is GitHub’s way of promoting a tag, however these are not native to Git itself.

However, the API provides a function to get all tags (Github, Godoc):

func (s *RepositoriesService) ListTags(ctx context.Context, owner string, repo string, opt *ListOptions) ([]*RepositoryTag, *Response, error)

/* ListTags lists tags for the specified repository. */

Let’s get started with our imports:

import "context"
import "github.com/google/go-github"

and initialize a new GitHub client:

client := github.NewClient(nil)

Since we do not use any features requiring authentication to the GitHub APIs, we can simply pass nil as an http.Client during initialization (https://godoc.org/github.com/google/go-github/github#hdr-Authentication).

Next, we fetch all tags available:

tags, _, err := client.Repositories.ListTags(context.Background(), "owner", "repo", nil)
if err != nil {
    fmt.Println(err)
    return
}

ListTags gets a context to run in, the name of the repository owner (either a username or an organization), the name of the repository (only the name itself, not the full URL) and some pagination options, which we don’t need here and therefore we pass nil. ListTags returns the tags as an array of *RepositoryTags, GitHub’s raw response and an error, if any. We are not interested in the response here, therefore discard it with _, however we do absolutely need to handle the error.

GitHub sorts the tags automagically for us, therefore the latest tag is stored in the first element of the array. However, do not make the same mistake I initially made: forgetting to check if there are even any elements in the array!

if len(tags) > 0 {
    latestTag := tags[0]
    fmt.Printf("Latest tag '%s' from '%s' (SHA-1: %s)\n", latestTag.Name, latestTag.Commit.Author, latestTag.Commit.SHA)
} else {
    fmt.Printf("No tags yet\n")
}

That’s it!

If you are looking for a function to split a full Git URI into only the “owner” and “repository” part (to pass those along to ListTags for instance), here is one I wrote:

/* split [https://|http://|git://]github.com/owner/repo[.git]/... into owner, repo */
func splitUrl(url string) (owner string, repo string, err error) {
	str := url
	str = strings.TrimPrefix(str, "https://")
	str = strings.TrimPrefix(str, "http://")
	str = strings.TrimPrefix(str, "git://")

	if strings.HasPrefix(str, "github.com/") {
		str = strings.TrimPrefix(str, "github.com/")
	} else {
		err = errors.New("Not a github url ")
		return
	}

	fields := strings.Split(str, "/")
	if len(fields) < 2 {
		err = errors.New("Invalid format for github")
		return
	}

	owner = fields[0]
	repo = strings.TrimRight(fields[1], ".git")

	return
}

Feel free to use wherever you like, suggestions for improvement are very welcome!