教程

Git 标签:git tag 的用法与版本发布

更新于: · Skillaria.Top

git tag 为某个具体提交添加一个永久的标记,通常用于标识发布版本,例如 v1.2.0。附注标签使用 git tag -a v1.2.0 -m "说明" 创建,标签需要单独推送到服务器:git push origin v1.2.0 或 git push --tags。

git tag 是附加在某个具体提交上的命名标记。与分支不同,标签不会随着新提交而移动:一年之后,v1.2.0 仍然指向同一份代码快照。因此标签常用于标识发布版本,CI/CD 系统也经常在出现新标签时触发构建和发布。

轻量标签与附注标签

Git 中有两种标签,它们的区别很重要。

轻量标签(lightweight)附注标签(annotated)
创建方式git tag v1.0.0git tag -a v1.0.0 -m "..."
它是什么仅仅是指向提交的指针数据库中的独立对象
作者、日期、说明没有有
GPG/SSH 签名不支持支持(-s)
默认是否被 git describe 识别否是
适用场景个人的临时标记发布版本以及所有对外公开的内容

发布版本请使用附注标签:其中记录了由谁、在何时发布,并且可以签名。

如何创建标签

在当前提交上

git tag -a v1.2.0 -m "Release 1.2.0: export reports, fix API timeout"

如果不加 -m,Git 会打开编辑器让你填写说明,适合想写下更新内容清单的情况。

在较早的提交上

发布时忘了打标签也没关系,可以把标签打在任何提交上:

git log --oneline -5
# 7be41d0 feat: export reports
# 1c02e8f fix: request timeout
git tag -a v1.1.0 1c02e8f -m "Release 1.1.0"

签名标签

git tag -s v1.2.0 -m "Release 1.2.0"
git tag -v v1.2.0     # 验证签名

需要配置好 GPG 密钥(当 gpg.format=ssh 时则使用 SSH 密钥)。GitHub 和 GitLab 会在这类标签上显示已验证的标记。

如何查看标签

git tag                       # 所有标签,按字母顺序
git tag -l "v1.*"             # 按模式筛选
git tag -n                    # 同时显示说明的第一行
git tag --sort=-v:refname     # 按版本排序,最新的在前
git show v1.2.0               # 标签数据以及对应的提交
git describe --tags           # 最近的标签:v1.2.0-3-g7be41d0

注意排序:默认情况下,v1.10.0 会排在 v1.9.0 之前,因为字符串是逐字符比较的。使用 --sort=v:refname 可以按版本号排序。

git describe 的输出含义是:当前提交位于标签 v1.2.0 之后的第三个提交,当前提交为 7be41d0(g 代表 git)。这是在两次发布之间的构建中生成版本号的好方法。

如何把标签推送到服务器

普通的 git push 不会推送标签,这正是“为什么 GitHub 上看不到标签”的常见原因。

git push origin v1.2.0        # 推送单个标签
git push --tags               # 推送所有本地标签
git push --follow-tags        # 推送提交,以及指向这些提交的附注标签

--follow-tags 比 --tags 更安全:它不会推送你为自己临时打的轻量标签。可以设为默认:git config --global push.followTags true。

从服务器获取标签:git fetch --tags。普通的 git fetch 也会获取指向所下载提交的标签。

如何删除或迁移标签

删除

git tag -d v1.2.0                     # 删除本地标签
git push origin --delete v1.2.0       # 删除服务器上的标签
# 旧写法的等价命令:git push origin :refs/tags/v1.2.0

把标签迁移到另一个提交

git tag -a -f v1.2.0 <新哈希> -m "Release 1.2.0"
git push origin -f v1.2.0

技术上可行,但在公开项目中不应迁移已经发布的标签。已经获取过该标签的人不会自动更新:没有 --force 时,fetch 不会覆盖本地已存在的标签。结果,不同的人手中的“v1.2.0”会指向不同的代码。正确的做法是发布一个新版本,例如 v1.2.1。

如何查看某次发布包含哪些提交

标签可以作为边界,用于各种历史查看命令:

git log v1.1.0..v1.2.0 --oneline        # 两次发布之间的提交
git diff v1.1.0 v1.2.0 --stat           # 哪些文件发生了变化
git tag --contains 1c02e8f              # 这个提交包含在哪些版本中
git merge-base --is-ancestor 1c02e8f v1.2.0 && echo "在该发布中"

最后两条命令回答了维护人员常问的问题:“这个修复已经交付给用户了吗?”如果修复只进入了 main,而发布是在它之前打的标签,git tag --contains 的结果就会为空,此时就需要打一个新标签。

另一个技巧是导出某个版本的源代码,不包含历史:git archive --format=zip -o app-1.2.0.zip v1.2.0。这常用于向客户交付压缩包,或在隔离环境中构建。

如何切换到某个标签对应的版本

git switch --detach v1.1.0
# HEAD is now at 1c02e8f fix: request timeout

此时你处于 detached HEAD 状态:可以查看和构建代码,但新提交不属于任何分支。如果需要修复旧版本,应当基于标签创建分支:

git switch -c hotfix/1.1.x v1.1.0
# 修改,提交
git tag -a v1.1.1 -m "Security fix"
git push origin hotfix/1.1.x --follow-tags

版本编号:SemVer

最常见的标签命名方案是语义化版本(Semantic Versioning),格式为 MAJOR.MINOR.PATCH。

  • MAJOR:API 发生不兼容变化时递增,例如 1.4.2 → 2.0.0。
  • MINOR:在保持兼容的前提下增加新功能,例如 1.4.2 → 1.5.0。
  • PATCH:修复缺陷,例如 1.4.2 → 1.4.3。
  • 预发布版本使用后缀标记:2.0.0-beta.1、2.0.0-rc.1。

前缀 v(如 v1.4.2)是标签名的常见约定,SemVer 本身并不要求。关键在于选定一种格式并始终坚持,否则查找“最新发布”的脚本就会失效。

发布过程中的标签

团队中典型的发布流程如下:

  1. 修改合并进 main,测试全部通过。
  2. 更新 CHANGELOG,以及项目文件中的版本号(如果版本号保存在那里)。
  3. 打附注标签:git tag -a v1.5.0 -m "Release 1.5.0"。
  4. git push origin main --follow-tags。
  5. CI 检测到新标签,构建产物并发布;在 GitHub 和 GitLab 上,标签会生成一个包含说明和文件的 Release 页面。

这样,生产环境中的任何版本都能与代码一一对应:构建名称中的标签可以用 git show 查看其中包含的内容。而 git log v1.4.0..v1.5.0 --oneline 则能为发布说明提供两个版本之间的变更列表。

用标签生成发布说明

如果提交说明写得清楚,标签就能直接转化为现成的变更清单。无需手动查找,就能找到最近和上一个发布版本:

LAST=$(git describe --tags --abbrev=0)
PREV=$(git describe --tags --abbrev=0 "$LAST^")
git log "$PREV..$LAST" --no-merges --pretty=format:"- %s (%h)"
# - feat: export reports (7be41d0)
# - fix: request timeout (1c02e8f)

如果团队的提交遵循 Conventional Commits 约定(feat:、fix:、feat!:),下一个版本号就可以自动计算:fix 递增 PATCH,feat 递增 MINOR,带感叹号或 BREAKING CHANGE 的提交递增 MAJOR。许多自动发布工具正是基于这一规则:它们自动打标签并生成 CHANGELOG。

标签与 CI/CD

流水线通常区分两类事件:推送到分支,以及出现新标签。分支推送触发测试和测试环境构建;标签则触发正式发布。在 GitHub Actions 中可以这样写:

on:
  push:
    tags:
      - "v*.*.*"

在 GitLab CI 中,对应的是变量 CI_COMMIT_TAG 以及规则 rules: - if: $CI_COMMIT_TAG。流水线中的标签名会成为 Docker 镜像或软件包的版本号,因此标签格式必须严格、可预测。

这也是不迁移标签的另一个原因:如果流水线已经把 1.2.0 版本的包发布到仓库中,通常不允许使用相同版本号再次发布,而用户手中的 1.2.0 镜像也会保持旧内容。

标签命名规则

标签名遵循与分支名相同的规则:不能包含空格,不能使用 ~ ^ : ? * [ 等字符,也不能包含连续的两个点。可以使用斜杠,因此在单体仓库中常见 api/v1.2.0、web/v3.0.1 这样的标签——每个包一套版本号。提前检查名称是否合法,可以使用 git check-ref-format "refs/tags/v1.2.0":如果不合法,它会返回非零状态码。

保护标签

在团队仓库中,应当保护发布标签。GitHub 和 GitLab 允许按模式(例如 v*)禁止除维护者以外的人创建、覆盖或删除标签。这样,别人机器上一次误执行的 git push --tags 不会发布未计划的版本,而错误的 git push origin --delete 也不会抹掉版本历史。

常见错误

  • 标签没有推送到服务器。 忘记了 git push origin <标签> 或 --follow-tags。
  • 标签与分支同名。 Git 会警告 refname is ambiguous;为标签使用 v 前缀,并避免与分支同名。
  • 在未提交的状态上打标签。 标签打在提交上,而不是文件上:未提交的修改不会进入发布版本。
  • 发布时使用轻量标签。 它没有作者、日期和说明,git describe 默认也不会识别它。

总结

git tag 在历史中固定了一个不可更改的位置。发布版本应使用 git tag -a 创建附注标签,按照 SemVer 命名,并显式推送到服务器:git push origin <标签> 或 --follow-tags。已经发布的标签不要迁移,而应发布一个新版本。标签与 rebase、stash、cherry-pick 一起,在 Git 课程中有实践讲解。

常见问题

标签与分支有什么区别?

分支会随着每一次新提交而前移,标签则永远指向同一个提交。

同一个提交能打多个标签吗?

可以,一个提交上的标签数量没有限制,例如 v2.0.0 和 latest-stable。

删除分支时标签会被删除吗?

不会。标签与分支相互独立,即使分支已被删除,带标签的提交仍然可以访问。

如何知道某个提交进入了哪个发布版本?

执行 git tag --contains 哈希值,命令会列出所有包含该提交的标签。

在脚本中如何获取最近的标签?

使用 git describe --tags --abbrev=0,它会输出最近标签的名称,不带哈希后缀。

通过视频学习本主题

课程“Git 从零基础到胜任开发者”结合实践详细讲解了这一主题。

前往课程