tag 是给某个 commit 贴一个永久名字。最常见的用途是版本发布,打完一个版本就在这个 commit 上打个 v1.2.0,之后随时能找回来,也能在 GitHub 上直接生成 Release。
两种 tag:轻量标签 vs 附注标签
Git 有两种 tag,行为不同:
轻量标签(lightweight):只是一个 commit 的别名指针,本质上和分支名差不多,但不会移动。不存储额外信息。
1 | git tag v1.0.0 |
附注标签(annotated):独立的 Git 对象,包含打标签者的名字、邮件、时间、tag message,还能用 GPG 签名。git show 能看到这些元信息。
1 | git tag -a v1.0.0 -m "Release v1.0.0" |
什么时候用哪个:
- 正式版本发布 → 用附注标签(
-a),信息完整,git describe默认只识别附注标签 - 临时标记某个 commit(自用)→ 轻量标签够用
打标签
给当前 commit 打标签
1 | # 轻量标签 |
给历史 commit 补打标签
先找到目标 commit 的 hash:
1 | git log --oneline |
然后指定 hash 打标签:
1 | git tag -a v0.9.0 8b9e012 -m "Release v0.9.0" |
查看标签
1 | # 列出所有标签(按字母序) |
推送到远端
本地打的标签不会自动同步到远端,需要显式推送。
1 | # 推送单个标签(和推送分支语法一样) |
日常版本发布推荐用 --follow-tags,只把正式的附注标签推上去。
删除标签
删除本地标签
1 | git tag -d v1.0.0 |
删除远端标签
1 | # 写法一(Git 1.7.0+) |
两种写法效果一样,推荐第一种,语义更清晰。
检出标签:detached HEAD 的坑
1 | git checkout v1.0.0 |
执行后会进入 detached HEAD 状态,HEAD 直接指向一个 commit 而不是分支。这时候如果做了新的提交,切走以后提交会丢失(没有分支引用它)。
如果只是查看历史代码,detached HEAD 没问题。但如果要在某个版本基础上修复 bug,正确做法是从标签创建一个分支:
1 | git checkout -b hotfix/v1.0.1 v1.0.0 |
这样在 v1.0.0 的代码基础上,工作在 hotfix/v1.0.1 分支,提交不会丢。
常用命令速查
| 命令 | 说明 |
|---|---|
git tag <name> |
创建轻量标签(当前 commit) |
git tag -a <name> -m "<msg>" |
创建附注标签 |
git tag -a <name> <hash> -m "<msg>" |
给历史 commit 打附注标签 |
git tag |
列出所有本地标签 |
git tag -l "<pattern>" |
通配符过滤标签列表 |
git show <name> |
查看标签详情 |
git rev-parse <name> |
查看标签指向的 commit hash |
git push origin <name> |
推送单个标签 |
git push origin --follow-tags |
推送所有附注标签 |
git push origin --tags |
推送所有标签(含轻量标签) |
git tag -d <name> |
删除本地标签 |
git push origin --delete <name> |
删除远端标签 |
git checkout -b <branch> <name> |
从标签创建新分支 |
git describe --tags |
显示距最近 tag 的距离 |
版本发布的完整流程
以 v1.2.0 为例,典型流程:
1 | # 1. 确保代码在主分支,工作区干净 |
推送后,GitHub 会在 Releases 页面列出这个标签,可以在页面上补充 Release Notes,也可以附加编译好的二进制文件。
语义版本命名(semver)
tag 名字没有强制要求,但遵循 semver 是行业惯例:
1 | v<主版本>.<次版本>.<补丁版本> |
- 主版本:不兼容的 API 变更
- 次版本:向下兼容的新功能
- 补丁版本:向下兼容的 bug 修复
git describe:距上一个 tag 有多远
1 | git describe --tags |
输出格式:<最近tag>-<距离commit数>-g<当前commit hash>。在 CI 脚本里用来自动生成构建版本号很方便。如果当前 commit 刚好就是 tag,直接输出 v1.2.0。
常见问题
git tag 和分支有什么区别?
分支是移动的指针,随着新 commit 向前推进。tag 是固定的指针,一旦打上就不会移动,始终指向那个 commit。它们都是对 commit 的引用,但语义不同:分支表示”正在进行的工作线”,tag 表示”这个时刻的快照”。
为什么 git push 不会自动推送标签?
Git 把标签视为本地元数据,和分支一样不自动同步,需要显式推送。设计上是为了让临时的本地标签不污染远端。推送单个用 git push origin v1.0.0,推送全部附注标签用 git push origin --follow-tags。
如何修改已经推送的标签?
标签不能直接编辑。标准做法是删除旧标签再重新打:
1 | # 本地删除并重新打 |
对于已经公开发布的版本号,强烈不建议这样操作,因为其他人可能已经基于这个 tag 做了构建或发布。
轻量标签和附注标签哪个适合版本发布?
附注标签(git tag -a)。它存储了完整的元数据(打标签者、时间、message),可以用 GPG 签名验证,git describe 默认只识别附注标签,git show 也能看到详细信息。轻量标签只适合本地临时标记,不适合正式版本。