summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorsudoforge <no-reply@sudoforge.com>2025-05-23 10:07:38 -0700
committerGitHub <noreply@github.com>2025-05-23 10:07:38 -0700
commite49c93d403afd662eeb3579f9a42ac45e75b7bef (patch)
tree7826a4bcb1395127f968dd52618670604c4742b2
parent01d6899fd914b38aad34ee62ac9c620a06260306 (diff)
downloadgit-bug-e49c93d403afd662eeb3579f9a42ac45e75b7bef.tar.gz
git-bug-e49c93d403afd662eeb3579f9a42ac45e75b7bef.zip
build: reduce complexity for setting the version (#1466)
This change refactors the implementation of how the version is embedded in the binary to reduce the number of variables necessary to determine the version information from 3 to 1. The legacy build variables are still supported, however, a warning will be emitted instructing users to contact their package maintainer. The legacy GitExacTag variable, if present, will be used to set main.version if it is undefined. This ensures that unmigrated package builds will continue to provide the correct version information. The legacy build variables will be supported until 0.12.0, giving package maintainers some time to migrate. Change-Id: I05fea97169ea1af87b198174afe5b6663f860fd8
-rw-r--r--Makefile13
-rw-r--r--commands/root.go79
-rw-r--r--commands/version.go87
-rw-r--r--doc/generate.go2
-rw-r--r--doc/man/git-bug-version.137
-rw-r--r--doc/md/git-bug.md2
-rw-r--r--doc/md/git-bug_version.md38
-rw-r--r--main.go8
-rw-r--r--misc/completion/generate.go2
-rw-r--r--version.go94
10 files changed, 219 insertions, 143 deletions
diff --git a/Makefile b/Makefile
index 608679926..4745d195d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,3 @@
-all: build
-
-GIT_COMMIT:=$(shell git rev-list -1 HEAD)
-GIT_LAST_TAG:=$(shell git describe --abbrev=0 --tags 2>/dev/null || true)
-GIT_EXACT_TAG:=$(shell git name-rev --name-only --tags HEAD)
UNAME_S := $(shell uname -s)
XARGS:=xargs -r
ifeq ($(UNAME_S),Darwin)
@@ -11,10 +6,10 @@ endif
SYSTEM=$(shell nix eval --impure --expr 'builtins.currentSystem' --raw 2>/dev/null || echo '')
-COMMANDS_PATH:=github.com/git-bug/git-bug/commands
-LDFLAGS:=-X ${COMMANDS_PATH}.GitCommit="${GIT_COMMIT}" \
- -X ${COMMANDS_PATH}.GitLastTag="${GIT_LAST_TAG}" \
- -X ${COMMANDS_PATH}.GitExactTag="${GIT_EXACT_TAG}"
+TAG:=$(shell git name-rev --name-only --tags HEAD)
+LDFLAGS:=-X main.version="${TAG}"
+
+all: build
.PHONY: list-checks
list-checks:
diff --git a/commands/root.go b/commands/root.go
index 1a4109a3c..1b64b5090 100644
--- a/commands/root.go
+++ b/commands/root.go
@@ -1,25 +1,17 @@
-// Package commands contains the CLI commands
package commands
import (
- "fmt"
"os"
- "runtime/debug"
"github.com/spf13/cobra"
- bridgecmd "github.com/git-bug/git-bug/commands/bridge"
- bugcmd "github.com/git-bug/git-bug/commands/bug"
+ "github.com/git-bug/git-bug/commands/bridge"
+ "github.com/git-bug/git-bug/commands/bug"
"github.com/git-bug/git-bug/commands/execenv"
- usercmd "github.com/git-bug/git-bug/commands/user"
+ "github.com/git-bug/git-bug/commands/user"
)
-// These variables are initialized externally during the build. See the Makefile.
-var GitCommit string
-var GitLastTag string
-var GitExactTag string
-
-func NewRootCommand() *cobra.Command {
+func NewRootCommand(version string) *cobra.Command {
cmd := &cobra.Command{
Use: execenv.RootCommandName,
Short: "A bug tracker embedded in Git",
@@ -33,7 +25,7 @@ the same git remote you are already using to collaborate with other people.
PersistentPreRun: func(cmd *cobra.Command, args []string) {
root := cmd.Root()
- root.Version = getVersion()
+ root.Version = version
},
// For the root command, force the execution of the PreRun
@@ -80,64 +72,3 @@ the same git remote you are already using to collaborate with other people.
return cmd
}
-
-func Execute() {
- if err := NewRootCommand().Execute(); err != nil {
- os.Exit(1)
- }
-}
-
-func getVersion() string {
- if GitExactTag == "undefined" {
- GitExactTag = ""
- }
-
- if GitExactTag != "" {
- // we are exactly on a tag --> release version
- return GitLastTag
- }
-
- if GitLastTag != "" {
- // not exactly on a tag --> dev version
- return fmt.Sprintf("%s-dev-%.10s", GitLastTag, GitCommit)
- }
-
- // we don't have commit information, try golang build info
- if commit, dirty, err := getCommitAndDirty(); err == nil {
- if dirty {
- return fmt.Sprintf("dev-%.10s-dirty", commit)
- }
- return fmt.Sprintf("dev-%.10s", commit)
- }
-
- return "dev-unknown"
-}
-
-func getCommitAndDirty() (commit string, dirty bool, err error) {
- info, ok := debug.ReadBuildInfo()
- if !ok {
- return "", false, fmt.Errorf("unable to read build info")
- }
-
- var commitFound bool
-
- // get the commit and modified status
- // (that is the flag for repository dirty or not)
- for _, kv := range info.Settings {
- switch kv.Key {
- case "vcs.revision":
- commit = kv.Value
- commitFound = true
- case "vcs.modified":
- if kv.Value == "true" {
- dirty = true
- }
- }
- }
-
- if !commitFound {
- return "", false, fmt.Errorf("no commit found")
- }
-
- return commit, dirty, nil
-}
diff --git a/commands/version.go b/commands/version.go
index 189a2e350..ffa4a180a 100644
--- a/commands/version.go
+++ b/commands/version.go
@@ -1,63 +1,66 @@
package commands
import (
- "runtime"
+ "log/slog"
"github.com/spf13/cobra"
"github.com/git-bug/git-bug/commands/execenv"
)
-type versionOptions struct {
- number bool
- commit bool
- all bool
-}
+// TODO: 0.12.0: remove deprecated build vars
+var (
+ GitCommit string
+ GitLastTag string
+ GitExactTag string
+)
func newVersionCommand(env *execenv.Env) *cobra.Command {
- options := versionOptions{}
+ return &cobra.Command{
+ Use: "version",
+ Short: "Print version information",
+ Example: "git bug version",
+ Long: `
+Print version information.
- cmd := &cobra.Command{
- Use: "version",
- Short: "Show git-bug version information",
- Run: func(cmd *cobra.Command, args []string) {
- runVersion(env, options, cmd.Root())
- },
- }
+Format:
+ git-bug <version> [commit[/dirty]] <compiler version> <platform> <arch>
- flags := cmd.Flags()
- flags.SortFlags = false
+Format Description:
+ <version> may be one of:
+ - A semantic version string, prefixed with a "v", e.g. v1.2.3
+ - "undefined" (if not provided, or built with an invalid version string)
- flags.BoolVarP(&options.number, "number", "n", false,
- "Only show the version number",
- )
- flags.BoolVarP(&options.commit, "commit", "c", false,
- "Only show the commit hash",
- )
- flags.BoolVarP(&options.all, "all", "a", false,
- "Show all version information",
- )
+ [commit], if present, is the commit hash that was checked out during the
+ build. This may be suffixed with '/dirty' if there were local file
+ modifications. This is indicative of your build being patched, or modified in
+ some way from the commit.
- return cmd
-}
+ <compiler version> is the version of the go compiler used for the build.
-func runVersion(env *execenv.Env, opts versionOptions, root *cobra.Command) {
- if opts.all {
- env.Out.Printf("%s version: %s\n", execenv.RootCommandName, root.Version)
- env.Out.Printf("System version: %s/%s\n", runtime.GOARCH, runtime.GOOS)
- env.Out.Printf("Golang version: %s\n", runtime.Version())
- return
- }
+ <platform> is the target platform (GOOS).
- if opts.number {
- env.Out.Println(root.Version)
- return
+ <arch> is the target architecture (GOARCH).
+`,
+ Run: func(cmd *cobra.Command, args []string) {
+ defer warnDeprecated()
+ env.Out.Printf("%s %s", execenv.RootCommandName, cmd.Root().Version)
+ },
}
+}
- if opts.commit {
- env.Out.Println(GitCommit)
- return
+// warnDeprecated warns about deprecated build variables
+// TODO: 0.12.0: remove support for old build tags
+func warnDeprecated() {
+ msg := "please contact your package maintainer"
+ reason := "deprecated build variable"
+ if GitLastTag != "" {
+ slog.Warn(msg, "reason", reason, "name", "GitLastTag", "value", GitLastTag)
+ }
+ if GitExactTag != "" {
+ slog.Warn(msg, "reason", reason, "name", "GitExactTag", "value", GitExactTag)
+ }
+ if GitCommit != "" {
+ slog.Warn(msg, "reason", reason, "name", "GitCommit", "value", GitCommit)
}
-
- env.Out.Printf("%s version: %s\n", execenv.RootCommandName, root.Version)
}
diff --git a/doc/generate.go b/doc/generate.go
index 0001f1a77..005d9df76 100644
--- a/doc/generate.go
+++ b/doc/generate.go
@@ -34,7 +34,7 @@ func main() {
wg.Add(1)
go func(name string, f func(*cobra.Command) error) {
defer wg.Done()
- root := commands.NewRootCommand()
+ root := commands.NewRootCommand("")
err := f(root)
if err != nil {
fmt.Printf(" - %s: FATAL\n", name)
diff --git a/doc/man/git-bug-version.1 b/doc/man/git-bug-version.1
index 7183b696e..988849e8a 100644
--- a/doc/man/git-bug-version.1
+++ b/doc/man/git-bug-version.1
@@ -2,7 +2,7 @@
.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" ""
.SH NAME
-git-bug-version - Show git-bug version information
+git-bug-version - Print version information
.SH SYNOPSIS
@@ -10,25 +10,44 @@ git-bug-version - Show git-bug version information
.SH DESCRIPTION
-Show git-bug version information
+Print version information.
+.PP
+Format:
+ git-bug [commit[/dirty]]
-.SH OPTIONS
-\fB-n\fP, \fB--number\fP[=false]
- Only show the version number
+.PP
+Format Description:
+ may be one of:
+ - A semantic version string, prefixed with a "v", e.g. v1.2.3
+ - "undefined" (if not provided, or built with an invalid version string)
+
+.PP
+[commit], if present, is the commit hash that was checked out during the
+ build. This may be suffixed with '/dirty' if there were local file
+ modifications. This is indicative of your build being patched, or modified in
+ some way from the commit.
.PP
-\fB-c\fP, \fB--commit\fP[=false]
- Only show the commit hash
+ is the version of the go compiler used for the build.
.PP
-\fB-a\fP, \fB--all\fP[=false]
- Show all version information
+ is the target platform (GOOS).
.PP
+ is the target architecture (GOARCH).
+
+
+.SH OPTIONS
\fB-h\fP, \fB--help\fP[=false]
help for version
+.SH EXAMPLE
+.EX
+git bug version
+.EE
+
+
.SH SEE ALSO
\fBgit-bug(1)\fP
diff --git a/doc/md/git-bug.md b/doc/md/git-bug.md
index 03bebb65e..2ef0b7725 100644
--- a/doc/md/git-bug.md
+++ b/doc/md/git-bug.md
@@ -31,7 +31,7 @@ git-bug [flags]
* [git-bug push](git-bug_push.md) - Push updates to a git remote
* [git-bug termui](git-bug_termui.md) - Launch the terminal UI
* [git-bug user](git-bug_user.md) - List identities
-* [git-bug version](git-bug_version.md) - Show git-bug version information
+* [git-bug version](git-bug_version.md) - Print version information
* [git-bug webui](git-bug_webui.md) - Launch the web UI
* [git-bug wipe](git-bug_wipe.md) - Wipe git-bug from the git repository
diff --git a/doc/md/git-bug_version.md b/doc/md/git-bug_version.md
index ceba8790f..a2569aff1 100644
--- a/doc/md/git-bug_version.md
+++ b/doc/md/git-bug_version.md
@@ -1,18 +1,46 @@
## git-bug version
-Show git-bug version information
+Print version information
+
+### Synopsis
+
+
+Print version information.
+
+Format:
+ git-bug <version> [commit[/dirty]] <compiler version> <platform> <arch>
+
+Format Description:
+ <version> may be one of:
+ - A semantic version string, prefixed with a "v", e.g. v1.2.3
+ - "undefined" (if not provided, or built with an invalid version string)
+
+ [commit], if present, is the commit hash that was checked out during the
+ build. This may be suffixed with '/dirty' if there were local file
+ modifications. This is indicative of your build being patched, or modified in
+ some way from the commit.
+
+ <compiler version> is the version of the go compiler used for the build.
+
+ <platform> is the target platform (GOOS).
+
+ <arch> is the target architecture (GOARCH).
+
```
git-bug version [flags]
```
+### Examples
+
+```
+git bug version
+```
+
### Options
```
- -n, --number Only show the version number
- -c, --commit Only show the commit hash
- -a, --all Show all version information
- -h, --help help for version
+ -h, --help help for version
```
### SEE ALSO
diff --git a/main.go b/main.go
index 7a2034bab..5b7a4caa6 100644
--- a/main.go
+++ b/main.go
@@ -4,9 +4,15 @@
package main
import (
+ "os"
+
"github.com/git-bug/git-bug/commands"
)
func main() {
- commands.Execute()
+ v, _ := getVersion()
+ root := commands.NewRootCommand(v)
+ if err := root.Execute(); err != nil {
+ os.Exit(1)
+ }
}
diff --git a/misc/completion/generate.go b/misc/completion/generate.go
index b64c1034f..5c6468121 100644
--- a/misc/completion/generate.go
+++ b/misc/completion/generate.go
@@ -26,7 +26,7 @@ func main() {
wg.Add(1)
go func(name string, f func(*cobra.Command) error) {
defer wg.Done()
- root := commands.NewRootCommand()
+ root := commands.NewRootCommand("")
err := f(root)
if err != nil {
fmt.Printf(" - %s: %v\n", name, err)
diff --git a/version.go b/version.go
new file mode 100644
index 000000000..de9bd8753
--- /dev/null
+++ b/version.go
@@ -0,0 +1,94 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "runtime/debug"
+ "strings"
+
+ "github.com/git-bug/git-bug/commands"
+ "golang.org/x/mod/semver"
+)
+
+var (
+ version = "undefined"
+)
+
+// getVersion returns a string representing the version information defined when
+// the binary was built, or a sane default indicating a local build. a string is
+// always returned. an error may be returned along with the string in the event
+// that we detect a local build but are unable to get build metadata.
+//
+// TODO: support validation of the version (that it's a real version)
+// TODO: support notifying the user if their version is out of date
+func getVersion() (string, error) {
+ var arch string
+ var commit string
+ var modified bool
+ var platform string
+
+ var v strings.Builder
+
+ // this supports overriding the default version if the deprecated var used
+ // for setting the exact version for releases is supplied. we are doing this
+ // in order to give downstream package maintainers a longer window to
+ // migrate.
+ //
+ // TODO: 0.12.0: remove support for old build tags
+ if version == "undefined" && commands.GitExactTag != "" {
+ version = commands.GitExactTag
+ }
+
+ // automatically add the v prefix if it's missing
+ if version != "undefined" && !strings.HasPrefix(version, "v") {
+ version = fmt.Sprintf("v%s", version)
+ }
+
+ // reset the version string to undefined if it is invalid
+ if ok := semver.IsValid(version); !ok {
+ version = "undefined"
+ }
+
+ v.WriteString(version)
+
+ info, ok := debug.ReadBuildInfo()
+ if !ok {
+ v.WriteString(fmt.Sprintf(" (no build info)\n"))
+ return v.String(), errors.New("unable to read build metadata")
+ }
+
+ for _, kv := range info.Settings {
+ switch kv.Key {
+ case "GOOS":
+ platform = kv.Value
+ case "GOARCH":
+ arch = kv.Value
+ case "vcs.modified":
+ if kv.Value == "true" {
+ modified = true
+ }
+ case "vcs.revision":
+ commit = kv.Value
+ }
+ }
+
+ if commit != "" {
+ v.WriteString(fmt.Sprintf(" %.12s", commit))
+ }
+
+ if modified {
+ v.WriteString("/dirty")
+ }
+
+ v.WriteString(fmt.Sprintf(" %s", info.GoVersion))
+
+ if platform != "" {
+ v.WriteString(fmt.Sprintf(" %s", platform))
+ }
+
+ if arch != "" {
+ v.WriteString(fmt.Sprintf(" %s", arch))
+ }
+
+ return fmt.Sprint(v.String(), "\n"), nil
+}