From 83dd4f7938a35fd1a8a1fbbc1bd2bb3aaeffacb5 Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 11 Oct 2026 23:40:54 +0200 Subject: [PATCH] feat(version): print the version like gh version tea version and --version printed "Version: developmentgolang: 1.27.2". They now print "tea version 1.0.0 (2026-10-01)", the release notes URL for tagged releases, and "built with go1.27.2" with the build tags. Builds from go install take the module version from the build info, and the date comes from the commit the binary was built from. Signed-off-by: bircni --- internal/app/app_test.go | 3 +- internal/version/version.go | 55 +++++++++++++++++++++++++------- internal/version/version_test.go | 30 +++++++++++++++++ 3 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 internal/version/version_test.go diff --git a/internal/app/app_test.go b/internal/app/app_test.go index cfe5935b..87c932aa 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -16,6 +16,7 @@ import ( "unicode" "gitea.dev/tea/internal/clierr" + "gitea.dev/tea/internal/version" "github.com/spf13/cobra" "github.com/stretchr/testify/require" ) @@ -68,7 +69,7 @@ func TestCommandLineEdgeCases(t *testing.T) { err = Execute(context.Background(), deps, []string{"config", "list", "--jq", ".[", "--remote", "-h"}) require.ErrorContains(t, err, "invalid --jq expression") require.NoError(t, Execute(context.Background(), deps, []string{"--version"})) - require.NotContains(t, out.String(), "tea version") + require.Equal(t, version.Format()+"\n", out.String()) } func TestAliasesAfterGlobalFlagsAndNestedHelp(t *testing.T) { diff --git a/internal/version/version.go b/internal/version/version.go index 22cf1646..9dc4ff25 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,12 +1,15 @@ // Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT +// Package version reports the version of the tea build. package version import ( - "fmt" + "regexp" "runtime" + "runtime/debug" "strings" + "time" ) // Version holds the current tea version. @@ -18,16 +21,44 @@ var Version = "development" // Tags holds the build tags used var Tags = "" -// Format returns a human-readable version string including -// Go version and build tags when available. +// release matches the versions of tagged releases, such as 1.0.0 or 1.1.0-rc1; builds between +// releases carry a +commit suffix. +var release = regexp.MustCompile(`^\d+\.\d+\.\d+(-[0-9A-Za-z.]+)?$`) + +// Format describes the build like gh version: the version and its date, the release notes of a +// release, and the Go version and build tags: +// +// tea version 1.0.0 (2026-10-01) +// https://gitea.com/gitea/tea/releases/tag/v1.0.0 +// built with go1.27.2 func Format() string { - s := fmt.Sprintf("Version: %s\tgolang: %s", - Version, - strings.ReplaceAll(runtime.Version(), "go", "")) - - if len(Tags) != 0 { - s += fmt.Sprintf("\tbuilt with: %s", strings.ReplaceAll(Tags, " ", ", ")) - } - - return s + info, _ := debug.ReadBuildInfo() + return format(Version, Tags, runtime.Version(), info) +} + +func format(version, tags, goVersion string, info *debug.BuildInfo) string { + date := "" + if info != nil { + // go install gitea.dev/tea@v1.0.0 stamps the module version instead of the ldflags. + if version == "development" && info.Main.Version != "" && info.Main.Version != "(devel)" { + version = strings.TrimPrefix(info.Main.Version, "v") + } + for _, setting := range info.Settings { + if setting.Key != "vcs.time" { + continue + } + if built, err := time.Parse(time.RFC3339, setting.Value); err == nil { + date = " (" + built.UTC().Format(time.DateOnly) + ")" + } + } + } + text := "tea version " + version + date + "\n" + if release.MatchString(version) { + text += "https://gitea.com/gitea/tea/releases/tag/v" + version + "\n" + } + text += "built with " + goVersion + if tags = strings.Join(strings.Fields(tags), ", "); tags != "" { + text += " (tags: " + tags + ")" + } + return text } diff --git a/internal/version/version_test.go b/internal/version/version_test.go new file mode 100644 index 00000000..afb0b0f0 --- /dev/null +++ b/internal/version/version_test.go @@ -0,0 +1,30 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package version + +import ( + "runtime/debug" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFormatDescribesTheBuild(t *testing.T) { + stamped := &debug.BuildInfo{Settings: []debug.BuildSetting{{Key: "vcs.time", Value: "2026-10-01T09:30:00Z"}}} + for _, tc := range []struct { + name, version, tags string + info *debug.BuildInfo + want string + }{ + {"release", "1.0.0", "", stamped, "tea version 1.0.0 (2026-10-01)\nhttps://gitea.com/gitea/tea/releases/tag/v1.0.0\nbuilt with go1.27.2"}, + {"release candidate with tags", "1.1.0-rc1", "sqlite netgo", nil, "tea version 1.1.0-rc1\nhttps://gitea.com/gitea/tea/releases/tag/v1.1.0-rc1\nbuilt with go1.27.2 (tags: sqlite, netgo)"}, + {"between releases", "0.16.0+12-gabcdef", "", stamped, "tea version 0.16.0+12-gabcdef (2026-10-01)\nbuilt with go1.27.2"}, + {"go install", "development", "", &debug.BuildInfo{Main: debug.Module{Version: "v1.0.1"}}, "tea version 1.0.1\nhttps://gitea.com/gitea/tea/releases/tag/v1.0.1\nbuilt with go1.27.2"}, + {"local build", "development", "", &debug.BuildInfo{Main: debug.Module{Version: "(devel)"}}, "tea version development\nbuilt with go1.27.2"}, + } { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, format(tc.version, tc.tags, "go1.27.2", tc.info)) + }) + } +}