生产环境必须使用附注标签(annotated tag),因其包含作者、时间、GPG签名及描述等完整元数据,支持校验与追溯;轻量标签无元信息、不可信,严禁用于发布;推送需显式执行,且禁止强制覆盖已发布的tag。

Tag 本质是不可变的提交快照指针
Git 的 tag 不是分支,也不随 git push 自动上传,它只是对某个 commit 的静态引用。生产环境部署必须依赖可重现、不可篡改的标识,而轻量标签(lightweight tag)没附带元信息、不签名、易被覆盖——生产环境只应使用附注标签(annotated tag)。
附注标签会创建独立的 Git 对象,包含打标者、时间、邮箱、GPG 签名(可选)、完整消息体,能被 git verify-tag 校验,这才是唯一可信标识的基础。
- 用
git tag -a v1.2.0 -m "release for prod"创建附注标签(-a不可省) - 轻量标签
git tag v1.2.0看似省事,但 CI/CD 流水线无法验证来源,上线后出问题难溯源 - 标签名建议遵循语义化版本规范
vX.Y.Z,避免用prod-20240520这类无序字符串
推送 Tag 到远程仓库需显式操作
执行 git tag 只在本地创建,远端仓库完全不知情。CI/CD 工具(如 GitHub Actions、GitLab CI)拉取代码时默认不获取 tag,不显式推送,生产构建就可能拿到错误的 commit。
- 推单个 tag:
git push origin v1.2.0 - 推所有本地 tag:
git push origin --tags(慎用,可能误推调试用的临时 tag) - CI 脚本中建议用
git push origin $TAG_NAME,通过变量控制,避免污染远端 - GitHub/GitLab 的 release 页面依赖附注 tag 推送后触发,轻量 tag 不会生成 release
在 CI/CD 中安全提取 Tag 并用于部署
很多团队用 git describe --tags 获取最近 tag,但这依赖本地 tag 完整性,CI 环境常因 git clone --depth=1 导致无 tag 可查——必须确保 CI 拉取时带 tag 数据。
- GitHub Actions 中,用
fetch-depth: 0或显式git fetch --tags - GitLab CI 中,在
before_script加git fetch --all --tags - 判断当前构建是否为 tag 构建:GitHub Actions 用
${{ startsWith(github.ref, 'refs/tags/') }};GitLab CI 用$CI_COMMIT_TAG是否非空 - 从 ref 提取版本号:GitHub Actions 中
${{ github.ref_name }}直接得v1.2.0,比解析git describe更可靠
Tag 被强制覆盖或删除会破坏生产可追溯性
Git 允许用 git tag -f 强制覆盖本地 tag,再 git push --force origin v1.2.0 覆盖远端——这在生产环境中等同于“篡改发布历史”。一旦部署系统已基于旧 v1.2.0 构建镜像,新覆盖会导致线上运行版本与 tag 描述严重不符。
- 禁止对已推送的生产 tag 执行
-f或git push --force - 远端保护:GitHub 可设 branch protection rule 限制
refs/tags/v*的 force push;GitLab 可在 Protected Tags 中设置Allowed to push: No one - 若真需修正(如打错版本号),正确做法是删旧 tag(
git tag -d v1.2.0 && git push origin :refs/tags/v1.2.0),再打新 tag(v1.2.0-rc1→v1.2.0),并同步通知运维和 QA
最易被忽略的一点:Docker 镜像构建时若用 git describe 生成镜像 tag,却没在 CI 中校验该描述是否对应真实附注 tag,就可能把一个未推送的本地 tag 当成生产标识——交付物和 Git 记录实际已脱钩。


















