在 Git 中,标签(Tag)就像为项目历史中一个特定的提交点拍下的快照,是一个指向该提交的静态指针。与随着新提交而移动的分支(Branch)不同,标签一旦创建就固定不变,是标记版本发布(如 v1.0.0)、重要里程碑等关键节点的最佳方式。
在深入了解所有命令之前,理解两种标签类型的区别至关重要。
🤔 标签类型:轻量标签 vs. 附注标签
- 轻量标签 (Lightweight Tag):一个特定提交的引用,像是一个不会移动的分支指针。它不包含作者信息、日期和标签信息。
- 附注标签 (Annotated Tag):一个独立的 Git 对象,存储在对象数据库中。它包含了标签名、作者信息、日期、一个可选的标签信息(Message)以及可选的 GPG 签名。
| 特性 | 轻量标签 (Lightweight Tag) | 附注标签 (Annotated Tag) |
|---|---|---|
| 本质 | 一个指向提交的指针 | 一个独立的 Git 对象 |
| 元数据 | ❌ 无 | ✅ 包含(作者、日期、信息、签名) |
| 推荐用途 | 临时、私有或个人标记 | 正式发布、公共里程碑 |
| 示例命令 | git tag v1.0.0 | git tag -a v1.0.0 -m "信息" |
核心原则:用于发布的正式版本,应始终使用附注标签。
📝 核心操作:创建、查看与删除
创建标签
1. 创建轻量标签
# 为当前提交创建
git tag <tag-name>
# 示例:为当前提交创建轻量标签 v1.0.0
git tag v1.0.0
# 为历史提交创建
git tag <tag-name> <commit-hash>
# 示例:为指定提交创建轻量标签
git tag v0.9.0 9fceb02
轻量标签适合创建临时或私有的标签。
2. 创建附注标签
# 创建附注标签
git tag -a <tag-name> -m "<tag-message>"
# 示例:为当前提交创建附注标签
git tag -a v1.0.0 -m "正式发布 1.0.0 版本"
# 创建包含多行信息的附注标签
git tag -a v1.0.0 -m "正式发布 1.0.0 版本" -m "新增功能: 用户认证模块" -m "修复问题: 登录超时问题"
# 为历史提交创建附注标签
git tag -a <tag-name> <commit-hash> -m "<tag-message>"
# 示例:为指定提交创建附注标签
git tag -a v0.9.0 9fceb02 -m "预发布 0.9.0 版本"
-a (--annotate) 参数用于创建附注标签。 -m (--message) 参数用于提供标签信息。如果省略 -m,Git 会打开一个文本编辑器让你编写。
3. 创建 GPG 签名标签
# 使用默认 GPG 密钥签名
git tag -s <tag-name> -m "<tag-message>"
# 示例:使用默认密钥创建签名标签
git tag -s v1.0.0 -m "签名发布版本"
# 指定 GPG 密钥签名
git tag -u <key-id> <tag-name> -m "<tag-message>"
# 示例:使用指定密钥创建签名标签
git tag -u 0A46826A v1.0.0 -m "签名发布版本"
# 配置始终使用签名
git config --global tag.gpgSign true
-s (--sign) 参数使用默认邮箱地址的 GPG 密钥创建签名标签。 -u <key-id> (--local-user=) 参数使用指定的 GPG 密钥。 tag.gpgSign 配置为 true 后,git tag -a 命令会自动创建签名标签,可被 --no-sign 参数覆盖。
4. 从文件读取标签信息
# 创建附注标签,信息来自文件
git tag -a <tag-name> -F <file>
# 示例:从文件 version-note.txt 读取信息创建标签
git tag -a v1.0.0 -F version-note.txt
-F <file> (--file=) 参数从指定文件中读取标签信息。
5. 强制创建标签
# 强制替换同名标签
git tag -f <tag-name>
# 示例:强制将 v1.0.0 标签指向当前提交
git tag -f v1.0.0
# 强制创建附注标签
git tag -a -f <tag-name> -m "<message>"
# 示例:强制将附注标签 v1.0.0 指向当前提交
git tag -a -f v1.0.0 -m "重新发布的 1.0.0 版本"
-f (--force) 参数允许替换一个已存在的同名标签。通常不建议更改已公开的标签。
查看标签
1. 列出所有标签
# 列出所有标签
git tag
# 或使用 --list 参数
git tag --list
git tag 命令不加参数,会列出所有标签。
2. 按模式列出标签
# 列出匹配模式的标签
git tag --list "<pattern>"
# 示例:列出所有 v1 开头的标签
git tag --list "v1.*"
--list 配合模式参数(如 "v1.*")可以列出所有匹配的标签。模式是通配符,支持 fnmatch 规则。
3. 查看标签详细信息
# 查看标签的详细信息
git show <tag-name>
# 示例:查看 v1.0.0 标签的详细信息
git show v1.0.0
git show 命令会显示标签的元数据(对于附注标签)和它所指向的提交的差异信息。
4. 在日志中查看标签
# 在日志中显示标签
git log --oneline --decorate
# 显示所有分支和标签的图形化日志
git log --oneline --graph --all --decorate
# 查看两个标签之间的提交
git log <tag1>..<tag2>
# 示例:查看 v1.0.0 和 v2.0.0 之间的提交
git log v1.0.0..v2.0.0
--decorate 参数会让 git log 显示指向每个提交的引用(分支、标签),是查看标签位置最直观的方式。
5. 查看远程标签
# 查看远程仓库的所有标签
git ls-remote --tags origin
# 查看远程仓库的特定标签
git ls-remote --tags origin <tag-name>
# 示例:查看远程仓库的 v1.0.0 标签
git ls-remote --tags origin v1.0.0
git ls-remote --tags 命令可以列出远程仓库的所有标签,无需先拉取到本地。
删除标签
1. 删除本地标签
# 删除本地标签
git tag -d <tag-name>
# 示例:删除本地标签 v1.0.0
git tag -d v1.0.0
-d (--delete) 参数用于删除本地标签。
2. 删除远程标签
# 方法一:推送空引用
git push origin :refs/tags/<tag-name>
# 示例:推送空引用以删除远程 v1.0.0 标签
git push origin :refs/tags/v1.0.0
# 方法二:使用 --delete 参数
git push --delete origin <tag-name>
# 示例:使用 --delete 删除远程 v1.0.0 标签
git push --delete origin v1.0.0
删除远程标签需要两步:先删除本地标签,再推送到远程。推荐使用更直观的 --delete 方式。
🔄 与远程仓库交互
推送标签
# 推送单个标签到远程
git push origin <tag-name>
# 示例:推送 v1.0.0 到远程
git push origin v1.0.0
# 推送所有本地标签到远程
git push origin --tags
# 推送所有本地标签,但自动检查上游
git push --follow-tags
# 推送所有引用和标签
git push --all --tags
默认情况下,git push 不会推送标签。--tags 参数会推送所有本地标签,但也可能推送不需要的标签,建议谨慎使用。--follow-tags 是一个更安全的选择,它只推送附注标签,以及被推送的提交所指向的标签。
拉取标签
# 克隆仓库时会自动拉取所有标签
git clone <repository-url>
# 拉取远程仓库的所有标签
git fetch --tags
# 拉取特定标签
git fetch origin tag <tag-name>
# 示例:拉取远程仓库的 v1.0.0 标签
git fetch origin tag v1.0.0
# 拉取远程仓库的所有内容(包括标签)
git fetch --all --tags
git fetch --tags 命令会从远程仓库拉取所有标签。git fetch <remote> tag <tag-name> 可以拉取特定的标签。
同步标签
# 同步远程标签(删除本地不存在的远程标签)
git fetch --prune --tags
# 删除本地不存在的远程标签引用
git remote prune origin
# 强制同步远程标签
git fetch origin --prune --tags
git fetch --prune --tags 会在拉取前,先删除本地已经不存在的远程标签引用。
🚀 高级操作与实用技巧
检出标签
# 检出标签(进入分离头指针状态)
git checkout <tag-name>
# 示例:检出 v1.0.0 标签
git checkout v1.0.0
# 基于标签创建新分支
git checkout -b <branch-name> <tag-name>
# 示例:基于 v1.0.0 标签创建 hotfix 分支
git checkout -b hotfix v1.0.0
检出标签会使仓库进入"分离头指针"(detached HEAD)状态,意味着你不在任何分支上,所做的任何新提交都不会属于任何分支,除非你基于此创建一个新分支。
比较标签
# 比较两个标签的差异
git diff <tag1> <tag2>
# 示例:比较 v1.0.0 和 v2.0.0 的差异
git diff v1.0.0 v2.0.0
# 查看两个标签之间的提交列表
git log <tag1>..<tag2> --oneline
# 查看标签相对于当前分支的差异
git diff <tag-name>
# 示例:查看 v1.0.0 标签与当前分支的差异
git diff v1.0.0
git diff 命令可以比较两个标签之间的文件差异,git log 可以查看两个标签之间的提交历史。
编辑/重命名标签
# 重命名本地标签(先创建新标签,再删除旧标签)
git tag <new-tag-name> <old-tag-name>
git tag -d <old-tag-name>
# 示例:将 v1.0.0 重命名为 v1.0.1
git tag v1.0.1 v1.0.0
git tag -d v1.0.0
# 更新远程标签(需要强制推送)
git push origin :<old-tag-name> <new-tag-name>
git push origin <new-tag-name>
# 示例:更新远程仓库的标签名称
git push origin :v1.0.0 v1.0.1
git push origin v1.0.1
Git 没有直接重命名标签的命令,需要组合使用。
验证签名标签
# 验证标签的 GPG 签名
git tag -v <tag-name>
# 示例:验证 v1.0.0 标签的签名
git tag -v v1.0.0
# 验证多个标签
git tag -v <tag1> <tag2>
# 验证并显示格式
git tag -v --format="%H %(tagger)" <tag-name>
-v (--verify) 参数用于验证标签的 GPG 签名。
高级筛选与格式化
# 按包含的提交筛选标签
git tag --contains <commit>
# 示例:列出包含指定提交的标签
git tag --contains 9fceb02
# 按指向的对象筛选标签
git tag --points-at <object>
# 示例:列出指向当前提交的标签
git tag --points-at HEAD
# 按合并状态筛选标签
git tag --merged <commit>
git tag --no-merged <commit>
# 排序标签
git tag --sort=<key>
# 示例:按版本号排序
git tag --sort=-version:refname
# 示例:按标签名排序
git tag --sort=refname
# 自定义输出格式
git tag --format="%(refname:short) %(taggerdate:short)"
# 示例:显示标签名和创建日期
git tag --format="%(refname:short) was tagged on %(taggerdate:short)"
--contains 参数可以查找包含特定提交的标签。 --points-at 参数可以列出指向特定对象的标签。 --merged 和 --no-merged 用于筛选已合并或未合并的标签。 --sort 参数用于排序,支持 refname、taggerdate、version:refname 等键。 --format 参数提供了强大的自定义输出功能。
⚙️ 自动化与集成
Git 钩子集成
# 示例:post-update 钩子(服务器端)
#!/bin/bash
# 当 master 分支更新时,自动创建发布标签
if [ "$1" = "refs/heads/master" ]; then
RELEASE_TAG="release-$(date +'%Y-%m-%d')"
git tag -a "$RELEASE_TAG" -m "自动发布标签 $RELEASE_TAG"
echo "自动创建了标签:$RELEASE_TAG"
fi
# 示例:pre-push 钩子(客户端)
#!/bin/bash
# 在推送前检查标签是否存在
protected_tags=("v*")
for tag in "${protected_tags[@]}"; do
if git tag -l "$tag" | grep -q .; then
echo "错误:不能删除保护标签 $tag"
exit 1
fi
done
Git 钩子可以用于在特定事件发生时自动执行标签操作。 post-update 钩子可以在服务器端接收推送后自动创建标签。 pre-push 钩子可以在客户端推送前进行检查,例如禁止删除特定标签。
CI/CD 集成
# GitHub Actions 示例
name: Create Release Tag
on:
push:
branches: [ main ]
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get version from package.json
id: get_version
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
- name: Create and push tag
run: |
git tag -a "v${{ env.VERSION }}" -m "Release v${{ env.VERSION }}"
git push origin "v${{ env.VERSION }}"
CI/CD 系统可以自动化标签创建过程。在 CI 流程中,可以从 package.json 或 VERSION 文件读取版本号,然后创建并推送标签。
📊 最佳实践
- 使用语义化版本控制 (SemVer):格式如
v<major>.<minor>.<patch>(例如 v1.2.3),清晰表达版本变更程度。 - 优先使用附注标签:始终为正式发布创建附注标签,以便在审计或排查问题时提供完整的元数据。
- 为历史提交打标签:如果忘记为某个版本打标签,可以事后补充,只需指定该版本的提交哈希即可。
- 定期同步标签:团队协作时,确保在
pull或fetch时使用--tags参数,保持本地与远程标签同步。 - 避免修改已推送的标签:不应修改或删除已公开的标签。如确有错误,应创建新标签,并使用
-f等操作时需格外小心。
💎 总结
Git 标签是管理软件版本的核心工具。通过掌握其创建、查看、管理以及与远程仓库交互的全套操作,你可以为项目建立清晰、可靠的版本发布流程。始终遵循最佳实践,尤其是为正式发布使用附注标签,将使你的版本管理更加专业和高效。