对象浏览器
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 粘进来,它就会打开,而这往往是「在决定要不要恢复之前,先看看那个丢失的提交里装了什么」的最快办法。