// Package version holds Guard's version, set at build time with // // go build -ldflags "-X guard/internal/version.Version=1.2.3 -X guard/internal/version.Tag=v1.2.3" // // The Makefile sets Version from the VERSION file, and Tag only when the // build is of that release: HEAD is the tag v and no tracked file // is changed. Only such a build reports a version number. Other builds // are development builds, identified by their commit (which Go records // from git by itself) and by where they stand relative to the last tag // (Describe, from git describe). package version import ( "regexp" "runtime/debug" "strconv" "strings" "sync" ) // Version is the Guard version (the VERSION file). var Version = "0.1.0-dev" // Tag is the release tag the build is of ("" when it isn't a release). var Tag = "" // Commit is the git commit the build is of. It is read from the build // information when not set at build time. var Commit = "" // Describe is 'git describe --tags --long' of the build's commit // ("v0.2.0-rc.1-3-gca0f148b78b6"), set by the Makefile; it says how many // commits a development build is past the last tag. var Describe = "" // Repo is Guard's source repository. const Repo = "https://gitea.adbraun.lol/adam/guard" // Build describes what a binary was built from. type Build struct { Version string `json:"version"` Tag string `json:"tag,omitempty"` Commit string `json:"commit,omitempty"` Modified bool `json:"modified,omitempty"` // built with uncommitted changes // Since is the last tag before a development build's commit, and // Ahead how many commits the build is past it (0: the tag's commit, // with uncommitted changes). Since string `json:"since,omitempty"` Ahead int `json:"ahead,omitempty"` // Label is the short form to show: the tag of a release, otherwise // the abbreviated commit (or the version when the commit is unknown). Label string `json:"label"` // URL is the release or commit in the repository. URL string `json:"url"` } var info = sync.OnceValue(func() Build { commit, modified := Commit, false if bi, ok := debug.ReadBuildInfo(); ok { for _, s := range bi.Settings { switch s.Key { case "vcs.revision": if commit == "" { commit = s.Value } case "vcs.modified": modified = s.Value == "true" } } } b := describe(Version, Tag, commit, modified) b.Since, b.Ahead = parseDescribe(Describe, b.Commit) return b }) // parseDescribe splits git describe --long output, if it is of commit. func parseDescribe(d, commit string) (since string, ahead int) { m := describeRe.FindStringSubmatch(d) if m == nil || commit == "" || !strings.HasPrefix(commit, m[3]) { return "", 0 } n, _ := strconv.Atoi(m[2]) return m[1], n } var describeRe = regexp.MustCompile(`^(v.+)-(\d+)-g([0-9a-f]+)$`) func describe(version, tag, commit string, modified bool) Build { b := Build{Version: version, Tag: tag, Commit: commit, Modified: modified} switch { case tag != "": b.Modified = false // the Makefile only sets Tag for clean trees b.Label, b.URL = tag, Repo+"/releases/tag/"+tag case commit != "": b.Label, b.URL = short(commit), Repo+"/commit/"+commit if modified { b.Label += "-modified" } default: b.Label, b.URL = version, Repo } return b } // Info describes this build. func Info() Build { return info() } // String is the version for logs and 'version' commands. Only a release // build gives a version ("0.2.0"); any other build says it's a // development build: "dev a1b2c3d4e5f6 (3 commits after v0.2.0-rc.1)". func String() string { return Info().String() } func (b Build) String() string { switch { case b.Tag != "": return b.Version case b.Commit == "": // No git information (built from a source archive). return b.Version + " (source build)" } s := "dev " + b.Label switch { case b.Since == "": case b.Ahead == 1: s += " (1 commit after " + b.Since + ")" case b.Ahead > 1: s += " (" + strconv.Itoa(b.Ahead) + " commits after " + b.Since + ")" case b.Modified: s += " (" + b.Since + " with uncommitted changes)" default: s += " (at " + b.Since + ")" } return s } // Short is the version for a User-Agent: the version of a release, // otherwise the label ("a1b2c3d4e5f6"). func Short() string { if b := Info(); b.Tag == "" && b.Commit != "" { return b.Label } return Version } func short(commit string) string { if len(commit) > 12 { return commit[:12] } return commit }