仓库与历史

对象浏览器

git 素有复杂之名。这名声几乎全部来自一件事:从来没人给你看过那个模型——四种对象,加上指针。一旦你能点开一个提交、落到它的树上、并发现你的文件就是一个 blob,只不过被某棵树赋予了一个名字,那些高层命令就不再是魔法了。

⌘K → 对象浏览器。这个页面上没有任何东西能改动哪怕一个字节——它背后的每一次调用都是读操作。

一个提交的各个字段,它的树和父提交都是链接,旁边是引用列表

四种对象

对象 是什么 知道什么
blob 一个文件的内容 什么都不知道。不知道自己的名字,不知道路径,也不知道历史
tree 一份目录清单 名字、模式,以及每个子 blob 或子树的 sha
commit 一张快照 它的树、它的父提交、作者、提交者、消息
tag 一个附注标签 它指向的对象、打标签的人、一条消息

让大多数人吃惊的是第一行。blob 没有名字。 你历史中任何位置上内容完全相同的两个文件,就是同一个 blob,只存了一份。名字住在指向它的那棵树里——这正是为什么 git 跟踪的是内容而不是文件,也是为什么重命名是被检测出来的,而不是被记录下来的。

一个引用——refs/heads/main、refs/tags/v1.0、HEAD——不过是一个装着 sha 的文件。「分支很便宜」这句话的全部内涵,就在这里。

走一走

左栏列出仓库里的每一个引用,按 git 自己的分组方式分组。点一个,就落到它所命名的那个对象上。

从那里开始,一切都是链接:

  • commit 会显示它的 tree 和每一个 parent——点进去看那张快照,或者一次一个提交地向历史深处倒退。
  • tree 会列出它的条目,带模式、类型、sha 和大小。点一个名字就能打开那个子对象。
  • blob 会显示它的文本(很大的话只显示开头一段),如果是二进制文件就直说。
  • 附注标签会显示它指向什么——点进去就到那个提交。

返回会沿着你走过的路退回去。

输入一个修订版本

这个输入框接受 git rev-parse 接受的一切,而这也正是它从一个浏览器变成一种学习方式的地方:

输入这个 得到
HEAD 当前提交
HEAD~3 往回数三个提交
HEAD^{tree} 那个提交的树,剥出来的
HEAD:src/app.ts 直接拿到该路径的 blob
v1.0^{} 一个附注标签所指向的东西,而不是标签对象本身
a1b2c3d 任何对象,按 sha 找——缩写也行

树列表里的模式数字值得认一认:100644 是文件,100755 是可执行文件,040000 是子树,120000 是符号链接,160000 是子模块的 gitlink——而最后这一个,就是一个子模块所存储的全部内容。

值得知道的限制

  • 只读,而且是故意的。 这里没有任何可以用来写的东西。手工造对象是一项 git hash-object 的练习,那属于终端。
  • 大的 blob 会被截断,只保留前 200 KB——足够看清它是什么,又不至于把窗口卡死。
  • 大小是对象的内容大小,也就是 git cat-file -s 报告的那个数,而不是它打包之后在磁盘上的开销。要看后者,见仓库维护。
  • 不可达的对象仍然是对象。 把 git fsck 悬垂报告里的一个 sha 粘进来,它就会打开,而这往往是「在决定要不要恢复之前,先看看那个丢失的提交里装了什么」的最快办法。

另请参阅:提交图 · 仓库维护 · 恢复

在 GitHub 上改进这份翻译