Use git tags to add (GoReleaser-compatible) semver to your Go package.
Go to file
AJ ONeal 37c1fd4b56 fix typos in example fmt.Printf 2020-09-12 08:57:44 +00:00
examples rename outfile xversion.go to lower init() priority, add outfile option 2019-06-21 00:01:02 -06:00
gitver separate ExecAndParse, bugfix source format 2019-07-01 02:32:19 -06:00
.gitignore rename outfile xversion.go to lower init() priority, add outfile option 2019-06-21 00:01:02 -06:00
LICENSE Initial commit 2019-06-11 03:59:40 +00:00
README.md fix typos in example fmt.Printf 2020-09-12 08:57:44 +00:00
gitver.go fail softly, as documented 2019-07-08 14:36:22 -06:00
go.mod v1.0.0: get version from git, or fail gracefully 2019-06-20 22:55:24 -06:00
version.go v1.1.2 2019-07-01 02:33:28 -06:00

README.md

git-version.go

Use git tags to add semver to your go package in under 150 lines of code.

Goals: 

      1. Use an exact `git tag` version, like v1.0.0, when clean
      2. Translate the `git describe` version  (v1.0.0-4-g0000000)
	     to semver (v1.0.1-pre4+g0000000) in between releases
      3. Note when `dirty` (and have build timestamp)

      Fail gracefully when git repo isn't available.

How it works

  1. You define the fallback version and version printing in main.go:
//go:generate go run git.rootprojects.org/root/go-gitver

package main

var (
	GitRev       = "0000000"
	GitVersion   = "v0.0.0-pre0+0000000"
	GitTimestamp = "0000-00-00T00:00:00+0000"
)

func main() {
	if (len(os.Args) > 1 && "version" === os.Args[1]) {
		fmt.Printf("Foobar %s (%s)\n", GitVersion, GitTimestamp)
	}
	// ...
}
  1. You go generate or go run git.rootprojects.org/root/go-gitver to generate xversion.go:
package main

func init() {
	GitRev = "0921ed1e"
	GitVersion = "v1.1.2"
	GitTimestamp = "2019-07-01T02:32:58-06:00"
}

Demo

Generate an xversion.go file:

go run git.rootprojects.org/root/go-gitver
cat xversion.go
// Code generated by go generate; DO NOT EDIT.
package main

func init() {
	GitRev = "6dace8255b52e123297a44629bc32c015add310a"
	GitVersion = "v1.1.4-pre2+g6dace82"
	GitTimestamp = "2020-07-16T20:48:15-06:00"
}

Note: The file is named xversion.go by default so that the generated file's init() will come later, and thus take priority, over most other files.

See go-gitvers self-generated version:

go run git.rootprojects.org/root/go-gitver version
6dace8255b52e123297a44629bc32c015add310a
v1.1.4-pre2+g6dace82
2020-07-16T20:48:15-06:00

QuickStart

Add this to the top of your main file, so that it runs with go generate:

//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver

Add a file that imports go-gitver (for versioning)

// +build tools

package example

import _ "git.rootprojects.org/root/go-gitver"

Change you build instructions to be something like this:

go mod vendor
go generate -mod=vendor ./...
go build -mod=vendor -o example cmd/example/*.go

You don't have to use -mod=vendor, but I highly recommend it (just go mod tidy; go mod vendor to start).

Options

version           print version and exit
--fail            exit with non-zero status code on failure
--package <name>  will set the package name
--outfile <name>  will replace `xversion.go` with the given file path

ENVs

# Alias for --fail
GITVER_FAIL=true

For example:

//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver --fail

go run -mod=vendor git.rootprojects.org/root/go-gitver version

Usage

See examples/basic

  1. Create a tools package in your project
  2. Guard it against regular builds with // +build tools
  3. Include _ "git.rootprojects.org/root/go-gitver" in the imports
  4. Declare var GitRev, GitVersion, GitTimestamp string in your package main
  5. Include //go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver as well

tools/tools.go:

// +build tools

// This is a dummy package for build tooling
package tools

import (
	_ "git.rootprojects.org/root/go-gitver"
)

main.go:

//go:generate go run git.rootprojects.org/root/go-gitver --fail

package main

import "fmt"

var (
	GitRev       = "0000000"
	GitVersion   = "v0.0.0-pre0+0000000"
	GitTimestamp = "0000-00-00T00:00:00+0000"
)

func main() {
  fmt.Println(GitRev)
  fmt.Println(GitVersion)
  fmt.Println(GitTimestamp)
}

If you're using go mod vendor (which I highly recommend that you do), you'd modify the go:generate ever so slightly:

//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver --fail

The only reason I didn't do that in the example is that I'd be included the repository in itself and that would be... weird.

Why a tools package?

import "git.rootprojects.org/root/go-gitver" is a program, not an importable package

Having a tools package with a build tag that you don't use is a nice way to add exact versions of a command package used for tooling to your go.mod with go mod tidy, without getting the error above.

git: behind the curtain

These are the commands that are used under the hood to produce the versions.

Shows the git tag + description. Assumes that you're using the semver format v1.0.0 for your base tags.

git describe --tags --dirty --always
# v1.0.0
# v1.0.0-1-g0000000
# v1.0.0-dirty

Show the commit date (when the commit made it into the current tree). Internally we use the current date when the working tree is dirty.

git show v1.0.0-1-g0000000 --format=%cd --date=format:%Y-%m-%dT%H:%M:%SZ%z --no-patch
# 2010-01-01T20:30:00Z-0600
# fatal: ambiguous argument 'v1.0.0-1-g0000000-dirty': unknown revision or path not in the working tree.

Shows the most recent commit.

git rev-parse HEAD
# 0000000000000000000000000000000000000000

Errors

cannot find package "."

package git.rootprojects.org/root/go-gitver: cannot find package "." in:
	/Users/me/go-example/vendor/git.rootprojects.org/root/go-gitver
cmd/example/example.go:1: running "go": exit status 1

You forgot to update deps and re-vendor:

go mod tidy
go mod vendor