-
A1. 附录 A: Git 在其他环境
- A1.1 图形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小结
-
A2. 附录 B: 在应用程序中嵌入 Git
-
A3. 附录 C: Git 命令
10.3 Git 内部原理 - Git 引用
Git 引用
如果你对查看从某个提交(比如 1a410e
)可达的仓库历史感兴趣,你可以运行类似 git log 1a410e
的命令来显示该历史,但是你仍然需要记住 1a410e
是你想用作历史起点提交。如果能有一个文件,你可以用一个简单的名称存储这个 SHA-1 值,然后使用这个简单的名称而不是原始的 SHA-1 值,那会更容易。
在 Git 中,这些简单的名称被称为“引用(references)”或“refs”;你可以在 .git/refs
目录中找到包含这些 SHA-1 值的文件。在当前项目中,这个目录不包含文件,但它确实包含一个简单的结构
$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f
要创建一个新的引用来帮助你记住最新的提交在哪里,你可以在技术上做一些像这样简单的事情
$ echo 1a410efbd13591db07496601ebc7a059dd55cfe9 > .git/refs/heads/master
现在,你可以在 Git 命令中使用刚刚创建的 head
引用,而不是 SHA-1 值
$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit
不鼓励你直接编辑引用文件;相反,Git 提供了更安全的命令 git update-ref
来更新引用
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
这基本上就是 Git 中分支的含义:一个指向工作线头部的简单指针或引用。要在第二个提交处创建一个分支,你可以这样做
$ git update-ref refs/heads/test cac0ca
你的分支将只包含从该提交往下的工作
$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit
现在,你的 Git 数据库在概念上看起来是这样的

当你运行诸如 git branch <branch>
的命令时,Git 基本上是运行 update-ref
命令,将你所在分支的最新提交的 SHA-1 值添加到你想要创建的任何新引用中。
HEAD
现在的问题是,当你运行 git branch <branch>
时,Git 如何知道最新提交的 SHA-1 值?答案是 HEAD 文件。
通常 HEAD 文件是你当前所在分支的符号引用。通过符号引用,我们指的是与普通引用不同,它包含一个指向另一个引用的指针。
然而,在某些罕见情况下,HEAD 文件可能包含 Git 对象的 SHA-1 值。这发生在你检出标签、提交或远程分支时,这会将你的仓库置于 “分离头指针(detached HEAD)” 状态。
如果你查看该文件,通常会看到类似这样的内容
$ cat .git/HEAD
ref: refs/heads/master
如果你运行 git checkout test
,Git 会更新文件,使其看起来像这样
$ cat .git/HEAD
ref: refs/heads/test
当你运行 git commit
时,它会创建提交对象,并将该提交对象的父级指定为 HEAD 中引用指向的任何 SHA-1 值。
你也可以手动编辑这个文件,但同样存在一个更安全的命令:git symbolic-ref
。你可以通过此命令读取 HEAD 的值
$ git symbolic-ref HEAD
refs/heads/master
你也可以使用相同的命令设置 HEAD 的值
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test
你不能设置 refs 样式之外的符号引用
$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/
标签
我们刚刚讨论了 Git 的三种主要对象类型(blob、tree 和 commit),但还有第四种。tag 对象非常像一个提交对象——它包含一个标签创建者、日期、消息和一个指针。主要区别在于标签对象通常指向一个提交而不是一个树。它类似于分支引用,但它从不移动——它总是指向相同的提交,但给它一个更友好的名称。
正如 Git 基础 中所讨论的,标签有两种类型:附注标签(annotated tag)和轻量标签(lightweight tag)。你可以通过运行类似这样的命令来创建轻量标签
$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d
轻量标签就是这样——一个永不移动的引用。然而,附注标签更为复杂。如果你创建一个附注标签,Git 会创建一个标签对象,然后写入一个引用来指向它,而不是直接指向提交。你可以通过创建一个附注标签(使用 -a
选项)来看到这一点
$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'Test tag'
这是它创建的对象 SHA-1 值
$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2
现在,对那个 SHA-1 值运行 git cat-file -p
$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700
Test tag
请注意,对象条目指向你打标签的提交 SHA-1 值。还要注意,它不需要指向提交;你可以标记任何 Git 对象。例如,在 Git 源代码中,维护者已将其 GPG 公钥添加为 blob 对象,然后对其进行了标记。你可以在 Git 仓库的克隆中运行此命令来查看公钥
$ git cat-file blob junio-gpg-pub
Linux 内核仓库也有一个非提交指向的标签对象——创建的第一个标签指向源代码导入的初始树。
远程仓库
你将看到的第三种引用是远程引用。如果你添加了一个远程仓库并推送到它,Git 会在 refs/remotes
目录中存储你上次推送到该远程仓库的每个分支的值。例如,你可以添加一个名为 origin
的远程仓库并将其 master
分支推送到它
$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
a11bef0..ca82a6d master -> master
然后,你可以通过检查 refs/remotes/origin/master
文件,查看你上次与服务器通信时 origin
远程仓库上的 master
分支是什么样子
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
远程引用与分支(refs/heads
引用)的主要区别在于它们被认为是只读的。你可以 git checkout
到一个远程引用,但 Git 不会符号引用 HEAD 到它,因此你永远不会用 commit
命令更新它。Git 将它们作为对那些分支在服务器上最新已知状态的书签进行管理。