通过 cat-file
查看这个新 commit 对象:
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon schacon@gmail.com 1243040974 -0700
committer Scott Chacon schacon@gmail.com 1243040974 -0700
first commit
commit 对象有格式很简单:指明了该时间点项目快照的顶层树对象、作者/提交者信息(从 Git 设置的 user.name
和 user.email
中获得)以及当前时间戳、一个空行,以及提交注释信息。
接着再写入另外两个 commit 对象,每一个都指定其之前的那个 commit 对象:
$ echo ‘second commit’ | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo ‘third commit’ | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
每一个 commit 对象都指向了你创建的树对象快照。出乎意料的是,现在已经有了真实的 Git 历史了,所以如果运行 git log
命令并指定最后那个 commit 对象的 SHA-1 便可以查看历史:
$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon schacon@gmail.com
Date: Fri May 22 18:15:24 2009 -0700
third commit
bak/test.txt | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon schacon@gmail.com
Date: Fri May 22 18:14:29 2009 -0700
second commit
new.txt | 1 +
test.txt | 2 ±
2 files changed, 2 insertions(+), 1 deletions(-)
commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon schacon@gmail.com
Date: Fri May 22 18:09:34 2009 -0700
first commit
test.txt | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
真棒。你刚刚通过使用低级操作而不是那些普通命令创建了一个 Git 历史。这基本上就是运行 git add
和 git commit
命令时 Git 进行的工作 ──保存修改了的文件的 blob,更新索引,创建 tree 对象,最后创建 commit 对象,这些 commit 对象指向了顶层 tree 对象以及先前的 commit 对象。这三类 Git 对象 ── blob,tree 以及 tree ── 都各自以文件的方式保存在 .git/objects
目录下。以下所列是目前为止样例中的所有对象,每个对象后面的注释里标明了它们保存的内容:
$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # ‘test content’
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1
如果你按照以上描述进行了操作,可以得到如图 9-3 所示的对象图。
对象存储
之前我提到当存储数据内容时,同时会有一个文件头被存储起来。我们花些时间来看看 Git 是如何存储对象的。你将看来如何通过 Ruby 脚本语言存储一个 blob 对象 (这里以字符串 “what is up, doc?” 为例) 。使用 irb
命令进入 Ruby 交互式模式:
$ irb
content = “what is up, doc?”
=> “what is up, doc?”
Git 以对象类型为起始内容构造一个文件头,本例中是一个 blob。然后添加一个空格,接着是数据内容的长度,最后是一个空字节 (null byte):
header = “blob #{content.length}\0”
=> “blob 16\000”
Git 将文件头与原始数据内容拼接起来,并计算拼接后的新内容的 SHA-1 校验和。可以在 Ruby 中使用 require
语句导入 SHA1 digest 库,然后调用 Digest::SHA1.hexdigest()
方法计算字符串的 SHA-1 值:
store = header + content
=> “blob 16\000what is up, doc?”
require ‘digest/sha1’
=> true
sha1 = Digest::SHA1.hexdigest(store)
=> “bd9dbf5aae1a3862dd1526723246b20206e5fc37”
Git 用 zlib 对数据内容进行压缩,在 Ruby 中可以用 zlib 库来实现。首先需要导入该库,然后用 Zlib::Deflate.deflate()
对数据进行压缩:
require ‘zlib’
=> true
zlib_content = Zlib::Deflate.deflate(store)
=> “x\234K\312\311OR04c(\317H,Q\310,V(-\320QH\311O\266\a\000_\034\a\235”
最后将用 zlib 压缩后的内容写入磁盘。需要指定保存对象的路径 (SHA-1 值的头两个字符作为子目录名称,剩余 38 个字符作为文件名保存至该子目录中)。在 Ruby 中,如果子目录不存在可以用 FileUtils.mkdir_p()
函数创建它。接着用 File.open
方法打开文件,并用 write()
方法将之前压缩的内容写入该文件:
path = ‘.git/objects/’ + sha1[0,2] + ‘/’ + sha1[2,38]
=> “.git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37”
require ‘fileutils’
=> true
FileUtils.mkdir_p(File.dirname(path))
=> “.git/objects/bd”
File.open(path, ‘w’) { |f| f.write zlib_content }
=> 32
这就行了 ── 你已经创建了一个正确的 blob 对象。所有的 Git 对象都以这种方式存储,惟一的区别是类型不同 ── 除了字符串 blob,文件头起始内容还可以是 commit 或 tree 。不过虽然 blob 几乎可以是任意内容,commit 和 tree 的数据却是有固定格式的。
9.3 Git References
你可以执行像 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 命令中使用你刚才创建的引用而不是 SHA-1 值:
$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
当然&#x