Git Tag 实战:从出包追溯到版本发布

前言

做 APP 开发给测试出包的时候,经常遇到一个尴尬的情况:测试反馈某个包有问题, 结果翻了半天 commit 记录,愣是没法确定那个包是从哪个提交打出来的。此刻,就轮到 Git Tag 登场了。

Git Tag 概念

tag 是 git 里面的一个标签,通俗来说,就是给某个 commit 起了一个别名。

Git 里的分支和 tag 名字都存放在 .git/refs/ 目录下,一个名字对应一个文件,文件里就一行 hash(引用多了可能被打包进 .git/packed-refs 单文件):

bash 复制代码
.git/refs/heads/master      → 8f3a2c1...   # 分支,新提交后会改写
.git/refs/tags/v1.0.0       → 4b7e9d2...   # tag,打下去就再也不动

所以 tag 最朴素的理解,就是给记不住的 commit hash 起个顾名思义的名字。8f69c21e8b7d 没人记得住,v2.0.0-正式版 一眼就懂。我们可以用 cat .git/refs/tags/v1.0.0 进行验证,里面就是一行 hash,别的什么都没有:轻量 tag 里这行 hash 就是 commit 本体, 附注 tag 里这行 hash 则指向一个独立的 tag 对象,由 tag 对象再指向 commit(下面会讲到)。

tag 和分支最本质的区别,就在于提交新的commit之后指针动不动。分支是链子末端会跟着跑的箭头,每次提交自动前移;tag 是往链子上钉的钉子,钉下去就固定了:

css 复制代码
          v1.0.0                      v1.1.0
             ↓                           ↓
  A ── B ── C ──── D ──── E ──── F ──── G ──── H ← master

哪怕 master 后来又跑了 100 个提交,v1.0.0 还指着当初那个 commit。简单说,分支回答「现在在做什么」,tag 回答「曾经做成过什么」。

tag 又分两种: 轻量 tag 和 附注 tag 。轻量 tag 就是上面说的指针文件,没有任何额外信息,git tag v1.0.0 一下就行;附注 tag 会创建一个独立的 Git 对象存进 .git/objects,记录打标签的人、时间、说明,还能 GPG 签名。一般建议用附注 tag (-a),因为半年后你需要知道「这个版本内容是什么、在什么时候、因为什么打的」,轻量 tag 更倾向于本地临时做个记号。

Tag 的使用方式

创建、查看、同步、删除的常用命令如下:

perl 复制代码
# 查看
git tag                        # 列出所有 tag
git tag -l "v1.*"              # 按通配符过滤
git show v1.0.0                # 看 tag 指向的 commit 和 tag 自身信息
git log --oneline --decorate   # 提交历史里直接标出哪些 commit 带 tag
​
# 创建
git tag v1.0.0                        # 轻量 tag,打在 HEAD
git tag -a v1.0.0 -m "说明"            # 附注 tag
git tag -a v1.0.0 <hash> -m "说明"     # 给历史 commit 补打,事后补 tag 很常用
​
# 同步远端
git push origin v1.0.0         # 推单个 tag
git push origin --tags         # 推全部
git fetch --tags               # 拉远端全部 tag
​
# 删除
git tag -d v1.0.0                       # 删本地
git push origin --delete v1.0.0         # 删远端

这里要特别注意的是: tag 不随 git push 走。你 push 了分支,tag 还留在本地,别人看不到、CI 也不会触发,必须单独 push tag,很多人第一次都栽在这上面。

还有一个很好用的命令 git describe --tags,输出形如 v1.0.0-14-g2414721,意思是从最近的 tag 之后又提交了 14 次。很多项目的构建脚本用它自动生成版本号,因为任何一个测试包都能反查出来: 它是从哪个 tag 之后的第几个提交打出来的。

另外, 很多人想回看某个历史版本时,第一反应是:

git 复制代码
git checkout v1.0.0

但这并不是"切到一个叫 v1.0.0 的分支",而是让 Git 的 HEAD 直接停在某个 commit 上,也就是进入 detached HEAD 状态(没有处在任何分支上)。

在这个状态下,你只是看看代码没问题;但如果你改了代码还提交了,这些提交不属于任何分支。等你切回 master,就没有分支再引用这些提交了,它们会变成"悬空提交 "。等过一段时间,大约 90 天后 reflog 记录过期,再触发 git gc,这些提交就可能被清理掉,代码也就丢了。

正确做法是:不要直接站在 tag 上改,而是基于 tag 新建一个分支再改:

git 复制代码
git checkout -b hotfix/v1.0.1 v1.0.0

这样你的修改就提交在 hotfix/v1.0.1 这个分支上,不会被当成垃圾清掉。

Tag 的实际应用

  1. 出包版本和源码一一对应,这是 tag 对客户端 / APP 项目最实在的价值。每给测试或客户发一个安装包,就打个 tag,比如 v1.2.0、apk-20260904-test。后续如果 APK 出问题了,直接 git checkout -b fix v1.2.0 就能精确还原出打出这个包的源码,不用手足无措地翻找 commit message 。

  2. 很多团队的 CI/CD 会配成「只有 push tag 才构建生产包」,这样可以避免每次提交都跑一遍打包,发布节点也能留痕。

Tips

几条踩坑记录,分享给大家:

  1. 别和分支重名。同时存在 master 分支和 master tag 会产生歧义,Git 会 warning。

  2. 推出去的 tag 不可更改。别人可能已经拉走了这个tag, 你删了重打同名 tag 会造成两边不一致, 真要撤回, 宁可编辑一个不一样的名称。

  3. 打 tag 的顺序: 先提交再打 tag,不然 tag 会打在上一个 commit 上,完全不含本次改动。

命名的话,正式发布用语义化版本,如 v1.2.0, 内测出包建议带日期或来源 apk-20260904-test、v1.2.0-rc1,避免和分支同名,避免中文和空格。

总结

总结来说, git tag 就是把一个难以记忆的 commit hash 变成一个有语义的名字,让「这个包是从哪份代码打出来的」这类问题有据可查。核心就三条:对外发布用 附注 tag (-a)、push 分支后记得单独 push tag、回看历史版本从 tag 拉分支而不是直接 checkout。

希望这篇文章能帮到有同样困扰的同学,也欢迎在评论区交流。

相关推荐
linweidong几秒前
RTC 走偏、时间跳变?嵌入式时间管理的工程避坑
嵌入式硬件·面试·实时音视频·rtc·嵌入式面经·嵌入式ntp·嵌入式对时
数据掘金7 分钟前
小程序埋点方案的埋点文档怎么自动生成?
前端
deli0078 分钟前
cron 表达式为什么总跑错?10 个场景实测,下次执行时间一眼看懂
前端
代码猎人11 分钟前
前端怎么优化页面性能
前端
代码猎人14 分钟前
前端怎么配合后端完成数据对接
前端
lexiangqicheng20 分钟前
Docker的沙箱机制是怎样的?
前端·javascript
guohuang44 分钟前
让机器人安全失败:点云中断、TF 超时和控制器崩溃怎么测
git·安全·机器人
燕落南枝1 小时前
HTML 表格标签与 AI 合并单元格实战指南
前端·人工智能·html
用户84913717547161 小时前
订单超时未支付自动取消实现方案 | 三种方案详解
redis·面试·rabbitmq
考研保研资料分享1 小时前
国际商务保研复盘:贸大自贸港题、央财投资研究追问与西财华为案例
面试