Git 标签:git tag 的用法与版本发布
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.0 | git 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 本身并不要求。关键在于选定一种格式并始终坚持,否则查找“最新发布”的脚本就会失效。
发布过程中的标签
团队中典型的发布流程如下:
- 修改合并进 main,测试全部通过。
- 更新 CHANGELOG,以及项目文件中的版本号(如果版本号保存在那里)。
- 打附注标签:
git tag -a v1.5.0 -m "Release 1.5.0"。 git push origin main --follow-tags。- 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,它会输出最近标签的名称,不带哈希后缀。