mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-08-27 02:27:45 +00:00
fix(main): only trust build version when major matches module path
debug.ReadBuildInfo() can report a version whose major version does not match the module path's major version suffix (e.g. a v2 tag on a module without a /v2 path), which is unreliable per Go's module versioning rules. Extract the decision into resolveVersion() and only accept the build version when its major matches the module path's implied major version, otherwise fall back to the dev version. Fixes #231. Co-Authored-By: Codet <codet@commitgo.dev> (GPT-5-Codex)
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"path"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/cmd"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/flag"
|
||||
@@ -11,13 +14,65 @@ var Version = "dev"
|
||||
|
||||
func init() {
|
||||
if Version == "dev" {
|
||||
if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" && info.Main.Version != "(devel)" {
|
||||
Version = info.Main.Version
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
Version = resolveVersion(Version, info)
|
||||
}
|
||||
}
|
||||
flag.Version = Version
|
||||
}
|
||||
|
||||
// resolveVersion returns the version reported by debug.ReadBuildInfo when its
|
||||
// major version matches the major version encoded in the module path (e.g.
|
||||
// "/v2" suffix). Otherwise it falls back to devVersion, since Go's module
|
||||
// versioning rules make a mismatched major version untrustworthy (see #231).
|
||||
func resolveVersion(devVersion string, info *debug.BuildInfo) string {
|
||||
if info == nil {
|
||||
return devVersion
|
||||
}
|
||||
buildVersion := info.Main.Version
|
||||
if buildVersion == "" || buildVersion == "(devel)" {
|
||||
return devVersion
|
||||
}
|
||||
|
||||
buildMajor := majorVersionOf(buildVersion)
|
||||
pathMajor := majorVersionFromModulePath(info.Main.Path)
|
||||
if buildMajor != pathMajor {
|
||||
return devVersion
|
||||
}
|
||||
|
||||
return buildVersion
|
||||
}
|
||||
|
||||
// majorVersionOf extracts the numeric major version from a semver-like
|
||||
// string such as "v1.2.3", returning 0 if it cannot be parsed.
|
||||
func majorVersionOf(version string) int {
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
dot := strings.IndexByte(version, '.')
|
||||
if dot >= 0 {
|
||||
version = version[:dot]
|
||||
}
|
||||
major, err := strconv.Atoi(version)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return major
|
||||
}
|
||||
|
||||
// majorVersionFromModulePath returns the major version encoded in a module
|
||||
// path's "/vN" suffix, or 1 if the module path has no such suffix (as is the
|
||||
// case for v0 and v1 modules).
|
||||
func majorVersionFromModulePath(modulePath string) int {
|
||||
suffix := path.Base(modulePath)
|
||||
if len(suffix) < 2 || suffix[0] != 'v' {
|
||||
return 1
|
||||
}
|
||||
major, err := strconv.Atoi(suffix[1:])
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
return major
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResolveVersion(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
dev string
|
||||
info *debug.BuildInfo
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "nil build info falls back to dev version",
|
||||
dev: "dev",
|
||||
info: nil,
|
||||
want: "dev",
|
||||
},
|
||||
{
|
||||
name: "devel version falls back to dev version",
|
||||
dev: "dev",
|
||||
info: &debug.BuildInfo{Main: debug.Module{Path: "gitea.com/gitea/gitea-mcp", Version: "(devel)"}},
|
||||
want: "dev",
|
||||
},
|
||||
{
|
||||
name: "empty version falls back to dev version",
|
||||
dev: "dev",
|
||||
info: &debug.BuildInfo{Main: debug.Module{Path: "gitea.com/gitea/gitea-mcp", Version: ""}},
|
||||
want: "dev",
|
||||
},
|
||||
{
|
||||
name: "v1 version accepted for module path without major suffix",
|
||||
dev: "dev",
|
||||
info: &debug.BuildInfo{Main: debug.Module{Path: "gitea.com/gitea/gitea-mcp", Version: "v1.2.3"}},
|
||||
want: "v1.2.3",
|
||||
},
|
||||
{
|
||||
name: "v2 version rejected when module path has no /v2 suffix",
|
||||
dev: "dev",
|
||||
info: &debug.BuildInfo{Main: debug.Module{Path: "gitea.com/gitea/gitea-mcp", Version: "v2.0.0"}},
|
||||
want: "dev",
|
||||
},
|
||||
{
|
||||
name: "v2 version accepted when module path has /v2 suffix",
|
||||
dev: "dev",
|
||||
info: &debug.BuildInfo{Main: debug.Module{Path: "gitea.com/gitea/gitea-mcp/v2", Version: "v2.0.0"}},
|
||||
want: "v2.0.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := resolveVersion(tc.dev, tc.info)
|
||||
if got != tc.want {
|
||||
t.Errorf("resolveVersion(%q, %+v) = %q, want %q", tc.dev, tc.info, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user