-
1. 起步
-
2. Git 基础
-
3. Git 分支
-
4. 服务器上的 Git
- 4.1 协议
- 4.2 在服务器上部署 Git
- 4.3 生成 SSH 公钥
- 4.4 架设服务器
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管服务
- 4.10 小结
-
5. 分布式 Git
-
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 中,这些简单的名字被称为“引用”或“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
现在,您可以使用刚刚创建的 head 引用,而不是在 Git 命令中使用 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 值。当您检出标签、提交或远程分支时,会发生这种情况,这会将您的仓库置于 “分离 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 对象与 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
请注意,对象条目指向您标记的 commit SHA-1 值。还要注意,它不一定指向 commit;您可以标记任何 Git 对象。例如,在 Git 源代码中,维护者已将其 GPG 公钥添加为 blob 对象,然后对其进行了标记。您可以通过在 Git 仓库的克隆中运行此命令来查看公钥:
$ git cat-file blob junio-gpg-pub
Linux 内核仓库还有一个指向非 commit 的 tag 对象 — 第一个创建的标签指向导入源代码的初始 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 将它们作为书签管理,记录了这些分支在服务器上的最后一个已知状态。