伪版本格式为vX.Y.Z-yyyymmddhhmmss-commit或其变体,如v0.0.0-20230405102030-abc123def456、v1.5.1-0.20220805095508-6c1f3628ef7a等,其中vX.Y.Z由最近合法tag推导,时间戳取author date,哈希为commit前12位,不可手写,须由go命令自动生成。

伪版本长什么样?先看合法格式
Go 工具链生成的伪版本不是随便拼的,它必须符合 vX.Y.Z-yyyymmddhhmmss-commit 或变体结构。其中 vX.Y.Z 部分不是乱写的——它由目标 commit 前最近的合法 tag 推导而来,不是凭空捏造的“下一个版本号”。
-
v0.0.0-20230405102030-abc123def456:仓库完全没打过任何v*tag 时,go 默认用v0.0.0作前缀 -
v1.5.1-0.20220805095508-6c1f3628ef7a:上一个合法 tag 是v1.5.0,就推导出v1.5.1-0(注意是-0,不是-rc1) -
v1.5.0-alpha.1-0.20230101120000-9f8e7d6c5b4a:上一个 tag 是v1.5.0-alpha.1,则伪版本前缀保持预发布后缀不变,只加-0
时间戳取自 commit 的 author date(不是 committer date),哈希取 commit ID 前 12 位。你不能手写伪版本,go 命令也不认——go get 只接受 @commit 或 @branch,然后自己算出对应伪版本写进 go.mod。
为什么 go list -m -versions 不显示你想要的 commit?
go list -m -versions 列出的不是所有提交,而是远程仓库里**已打且格式合规的 Git tag**。它只匹配正则 ^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$,其他一概忽略。
- tag 名是
1.2.3(缺v)→ 不出现 - tag 名是
v1.2.3-rc1(分隔符错)→ 不出现,go mod tidy会 fallback 并可能报invalid pseudo-version - tag 名是
v1.2.3+2023(含构建元数据)→ 不出现,Go 完全不认+后内容 - 你用
go get github.com/user/repo@abc123→ 成功,但go list -m -versions仍不会列出abc123,因为它不是 tag
所以别指望靠 go list 找 commit;要查某次提交是否可用,直接 git ls-remote origin | grep abc123 更靠谱。
伪版本参与最小版本选择(MVS),这点最容易被忽略
伪版本不是“临时占位符”,它在 Go 的依赖解析中和语义化版本一样参与 MVS 算法。它的排序依据是整个字符串——先比 vX.Y.Z,再比时间戳,最后比哈希。
-
v1.5.0-0.20220101-aaa和v1.5.0-0.20220201-bbb:后者会被认为“更新”,即使它指向更旧的代码(author date 被篡改或本地 commit) -
v1.5.1-0.20220101-xxx比v1.5.0语义上“高”,可能意外覆盖你显式 require 的v1.5.0,尤其当其他依赖间接拉入更高伪版本时 - 一旦伪版本进了
go.mod,go get -u会把它当作合法版本参与升级,而不是跳过
真正危险的是:你本地开发时用了 @commit,CI 流水线跑 go mod tidy 会保留这个伪版本,而它的时间戳来自你本地机器的 author date——如果多人协作,不同机器生成的同一 commit 的伪版本可能不一致(author date 不同),导致 go.sum 校验失败。
怎么安全地用伪版本,又避免掉坑?
伪版本不是 bug,是设计使然,但要用得清醒:
- 临时调试用
go get example.com/lib@commit-hash,别手写伪版本;go 会自动算并写入go.mod - 上线前务必把伪版本换成正式 tag:打
v1.5.1→go get example.com/lib@v1.5.1→go mod tidy - 私有模块必须配
GOPRIVATE,否则 go 会尝试走 proxy.golang.org 查 tag,查不到就 fallback 生成伪版本,还可能因网络问题失败 - CI/CD 中禁止删
go.sum,也不允许go mod download时不校验——伪版本对应的 zip 包哈希必须和go.sum一致,否则go build直接报checksum mismatch
最常被漏掉的一点:伪版本里的 vX.Y.Z 前缀一旦生成,后续即使你打了新 tag,go 也不会自动更新它——除非你显式 go get 那个 tag 或运行 go mod tidy -compat=1.21(仅影响新引入项)。它就静静躺在 go.mod 里,像一枚定时炸弹,等着某次 go get -u 把它当成“更高版本”提上去。

















