1. 简介
Git 是一个广泛使用的版本控制系统,它能够跟踪、合并和恢复文件和目录的更改。在进行任何修改后,我们会将这些更改“提交(commit)”到当前的 Git 树中。每个提交都有一个唯一的标识符(ID),这让我们可以恢复和合并不同提交之间的更改。然而,这个 ID 难以记忆,因此 Git 提供了其他方式来标识某个提交。
本教程将讨论 Git 提交的标识与标签(tag)机制。我们将简要回顾 Git 提交的基本概念,接着介绍查看提交详情的方法、提交的标识方式、引用(ref)以及标签的使用和实践。
本文中的命令在 Debian 12(Bookworm)系统上测试,使用 GNU Bash 5.2.15。除非特别说明,否则这些命令应在大多数 POSIX 兼容环境中正常运行。
2. Git 提交简介
Git 提交代表了某一时刻仓库变化的一个快照。
从本质上讲,提交构成了仓库的历史记录,是一系列修改的链条。
我们可以使用 git log
命令查看提交历史:
$ git log
commit e76fd96c911d0ab2d36156066604b691efa86aa1 (HEAD -> master)
Author: x <x@example.com>
Date: Tue Feb 13 11:27:34 2024 -0500
major modifications
commit dbe16c5120caa5a93d5fcbbdeadbeeff7e2b3b59
Author: x <x@example.com>
Date: Tue Feb 13 11:00:00 2024 -0500
minor modifications
commit 2dac0d5faf151885a0ed1432196674cacbe88168
Author: x <x@example.com>
Date: Sat Feb 10 10:00:00 2024 -0500
init commit
我们看到有三个提交:初始提交和两个后续修改。每个提交都有一个唯一的 40 位十六进制标识符。此外,还有作者、提交时间和提交信息。
3. Git 提交详情
我们可以通过 git show
查看某个提交的详细信息:
$ git show e76fd96c911d0ab2d36156066604b691efa86aa1
commit e76fd96c911d0ab2d36156066604b691efa86aa1 (HEAD -> master)
Author: x <x@example.com>
Date: Tue Feb 13 11:27:34 2024 -0500
major modifications
diff --git a/file b/file
index 8bcebf6..6436b28 100644
--- a/file
+++ b/file
@@ -1,3 +1,7 @@
-initial content
+Major Changes
-minor changes
+-deleted all other content
+-introduced new structure
+-added signature
+
+sig
通过 git show
,我们可以看到该提交引入的具体更改,以标准的 diff 格式呈现。注意,我们是通过提交 ID 来查看和操作提交的。
4. Git 提交标识符
正如我们看到的,Git 提交标识符是一个 SHA-1 哈希值,由 160 位(40 个十六进制字符)组成:
$ git show e76fd96c911d0ab2d36156066604b691efa86aa1
不过,Git 支持使用前缀来标识提交,只要前缀是唯一的即可:
$ git show e76fd96c
根据官方文档建议,使用 8 个字符通常就足够了。但有时可能需要更多字符,比如 Linux 内核这样的大型项目,可能需要超过 12 个字符才能唯一标识一个提交。
虽然也可以使用更少字符(如 4 个),但可能会导致歧义:
$ git show e76
fatal: ambiguous argument 'e76': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
5. Git 提交引用(Refs)
Git 提交可以有“引用(ref)”,ref 是对某个提交的描述性、更易读的名称。
5.1. 查看提交引用
在 git log
的输出中,我们已经见过一个 ref:
$ git log
commit e76fd96c911d0ab2d36156066604b691efa86aa1 (HEAD -> master)
Author: x <x@example.com>
Date: Tue Feb 13 11:27:34 2024 -0500
major modifications
[...]
这里有两个 ref:
HEAD
:指向当前分支的最新提交master
:默认分支名,也可能是main
或其他自定义名称
我们可以使用这些 ref 来操作提交:
$ git show HEAD
commit e76fd96c911d0ab2d36156066604b691efa86aa1 (HEAD -> master)
[...]
$ git show master
commit e76fd96c911d0ab2d36156066604b691efa86aa1 (HEAD -> master)
[...]
git rev-parse
可以将 ref 转换为对应的提交 ID:
$ git rev-parse master
e76fd96c911d0ab2d36156066604b691efa86aa1
HEAD
会随着每次提交而移动,master
则在我们继续在该分支工作时更新。
5.2. 特殊引用
Git 内置了一些特殊 ref:
HEAD
:当前提交和分支ORIG_HEAD
:HEAD
的备份FETCH_HEAD
:最近一次拉取的远程分支MERGE_HEAD
:正在合并的提交CHERRY_PICK_HEAD
:正在进行 cherry-pick 的提交
此外,还可以使用相对引用:
~
:表示向上第 N 个祖先提交(仅考虑第一个父提交)^
:表示向上第 N 个父提交(可指定多个父提交)
示例结构:
C1 C2 C3
\ | /
\ | /
\|/
C4 C5
\ /
\/
C6
Commit | ^ | ^ alt | ~ | ~ alt |
---|---|---|---|---|
C6 | C6^0 | |||
C4 | C6^1 | C6^ | C6~ | C6~1 |
C5 | C6^2 | |||
C1 | C6^1^1 | C6^^ | C6~~ | C6~2 |
C2 | C4^2 | C6^^2 | ||
C3 | C4^3 | C6^^3 |
5.3. 创建提交引用
创建 ref 最简单的方式是创建一个新分支:
$ git branch branch1
此时,branch1
指向当前 HEAD
所在的提交:
$ git show branch1
commit e76fd96c911d0ab2d36156066604b691efa86aa1 (HEAD -> master, branch1)
[...]
Git 的分支本质上就是一个指向某个提交的指针。
5.4. Reflog
Git 会记录 ref 的变更历史,这就是 reflog
:
$ git reflog
e76fd96 (HEAD -> master, branch1) HEAD@{0}: commit: major modifications
dbe16c5 HEAD@{1}: commit: minor modifications
2dac0d5 HEAD@{2}: commit (initial): init commit
reflog
记录了几乎所有仓库操作,包括未提交的更改。
5.5. 引用存储位置
ref 的信息存储在 .git/refs/
目录下:
$ tree .git/refs/
.git/refs/
├── heads
│ ├── branch1
│ └── master
└── tags
3 directories, 2 files
可以使用 git show-ref
查看:
$ git show-ref
e76fd96c911d0ab2d36156066604b691efa86aa1 refs/heads/branch1
e76fd96c911d0ab2d36156066604b691efa86aa1 refs/heads/master
6. Git 提交打标签(Tagging)
除了 ref,Git 提交还可以打标签(tag),tag 是对某个提交的特殊命名,可以作为其引用。
6.1. 标签类型
Git 支持两种类型的标签:
✅ 轻量标签(Lightweight):仅是对提交的引用
✅ 带注释标签(Annotated):完整的对象,包含以下信息:
- 标签名
- 校验和
- 标签作者
- 邮箱
- 日期
- 标签信息
带注释的标签可以签名和验证。其日期由 GIT_COMMITTER_DATE
环境变量决定:
$ export GIT_COMMITTER_DATE="$(git log -1 --format=%aI <COMMIT>)"
如果未设置或无效,则使用当前时间。
6.2. 创建与删除标签
创建轻量标签很简单:
$ git tag <LWTAG>
创建带注释标签:
$ git tag --annotate <TAG> --message=<TAG_MESSAGE>
可以将标签打在任意提交上:
$ git tag <TAG> <COMMIT>
其中 <COMMIT>
可以是 ID、ref 或其他标签。
删除标签:
$ git tag --delete <TAG>
⚠️ 注意:该操作仅删除本地标签。
6.3. 检出标签
可以检出标签,但会进入“分离头指针(detached HEAD)”状态:
$ git checkout <TAG>
此时会提示:
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
⚠️ 踩坑提醒:不要在此状态下直接提交新内容,除非你打算创建新分支保留这些更改。
6.4. 查看标签
使用 git log
可以查看标签:
$ git --all --decorate --oneline --graph
* e76fd96 (HEAD -> master, tag: antag) major modifications
* dbe16c5 (tag: minor) minor modifications
* 2dac0d5 init commit
列出所有标签:
$ git tag --list
antag
minor
查看标签存储位置:
$ tree .git/refs
.git/refs
├── heads
│ ├── branch1
│ └── master
└── tags
├── antag
└── minor
查看标签详细信息:
$ git show antag
tag antag
Tagger: x <x@example.com>
Date: Thu Feb 15 10:15:00 2024 -0500
annotated tag
commit e76fd96c911d0ab2d36156066604b691efa86aa1 (HEAD -> master, tag: antag, branch1)
[...]
可以看到带注释标签比轻量标签多了元数据信息。
6.5. 推送与删除远程标签
⚠️ 默认情况下,git push
不会推送标签到远程仓库:
$ git push origin --tags
删除远程标签:
$ git push origin --delete <TAG>
7. 总结
Git 提交是 Git 的核心对象之一,掌握其标识、引用和标签机制对于高效协作和版本管理至关重要。
- ✅ 每个提交都有唯一 ID,可使用前缀快速引用
- ✅ 使用 ref(如分支、HEAD)可更方便地操作提交
- ✅ 标签分为轻量和带注释两种,适合不同场景
- ✅ 推送标签需要显式指定
--tags
- ✅ 检出标签会进入分离头指针状态,需谨慎操作
掌握这些内容,能显著提升你在 Git 项目中的工作效率和稳定性。