章节 ▾ 第二版

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 directory objects with branch head references included
图 176. 包含分支头引用的 Git 目录对象

当你运行像 git branch <branch> 这样的命令时,Git 基本上会运行 update-ref 命令,将你所在分支的最后一次提交的 SHA-1 添加到你想要创建的任何新引用中。

HEAD

现在的问题是,当你运行 git branch <branch> 时,Git 如何知道最后一次提交的 SHA-1? 答案是 HEAD 文件。

通常,HEAD 文件是指向你当前所在分支的符号引用。 所谓的符号引用,我们指的是,与普通引用不同,它包含指向另一个引用的指针。

然而,在一些罕见的情况下,HEAD 文件可能包含 Git 对象的 SHA-1 值。当您检出一个标签、提交或远程分支时,会发生这种情况,这会将您的仓库置于"分离 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 的三种主要对象类型(blobstreescommits),但还有第四种。tag 对象非常像 commit 对象——它包含一个标记者、一个日期、一条消息和一个指针。主要的区别是 tag 对象通常指向一个 commit 而不是一个 tree。它就像一个分支引用,但它永远不会移动——它总是指向同一个 commit,但给它一个更友好的名称。

Git 基础中所讨论的,有两种类型的标签:带注释的和轻量级的。您可以通过运行类似以下命令来创建一个轻量级标签

$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d

这就是轻量级标签的全部——一个永不移动的引用。但是,带注释的标签更复杂。如果您创建一个带注释的标签,Git 会创建一个 tag 对象,然后写入一个引用来指向它,而不是直接指向 commit。您可以通过创建一个带注释的标签(使用 -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 内核仓库也有一个非提交指向的标签对象——创建的第一个标签指向源代码导入的初始 tree。

远程仓库

您将看到的第三种引用类型是远程引用。如果您添加一个远程仓库并推送到它,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 将它们管理为指向这些分支在这些服务器上的最后已知状态的书签。

scroll-to-top