$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
现在,HEAD 应仍处于你在第 1 课中检出旧提交时的“分离”状态。要修复该问题,请运行命令 git checkout master
。本课后面将详细介绍此命令的功能。
现在,如果你在跟着 Caroline 做,可能已在 Asteroids 版本库中修复了此错误。如果没有,现在请更改代码,然后将更改添加到暂存区中。
在 game.js
中,找到语句
if (this.delayBeforeBullet <= 0) {
(应在第 423 行,在下一行插入)
this.delayBeforeBullet = 10;
然后,通过将 game.js
文件添加到暂存区中暂存更改。如果忘记了如何暂存更改,请观看此视频 复习。
现在,请提交此更改。请确保使用有意义的提交信息!如果忘记了如何提交,请观看此视频.
提交更改完成后,单击“下一项”。
虽然到此刻为止提交 ID 一直都是相同的,但你可能注意到我们的提交 ID 与你的不同(即使我们进行了相同的更改)。这是因为,如果两个提交之间有任何不同(包括创建提交的作者或时间),则提交将具有不同的 ID。如果你有兴趣详细了解 Git 的内部细节和如何生成提交 ID,请查看此页。
https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
Git是一个内容寻址文件系统。大。那是什么意思?这意味着Git的核心是一个简单的键值数据存储。这意味着你可以将任何类型的内容插入到Git仓库中,Git会为你提供一个可以稍后用来检索该内容的唯一键。
作为一个演示,我们来看看管道命令git hash-object
,该命令接收一些数据,将其存储在您的.git/objects
目录(对象数据库)中,并将您现在引用该数据对象的唯一密钥返回给您。
首先,初始化一个新的Git存储库,并确认objects
目录中没有(可预测)没有任何内容:
$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
Git已经初始化了它的objects
目录和创建的pack
以及info
子目录,但是没有常规的文件。现在,让我们git hash-object
用来创建一个新的数据对象并将其手动存储到新的Git数据库中:
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
以最简单的形式,git hash-object
将采取您交给它的内容,并仅返回将用于将其存储在Git数据库中的唯一密钥。-w
然后该选项告诉命令不要简单地返回键,而是将该对象写入数据库。最后,该--stdin
选项告诉git hash-object
从stdin获取内容以进行处理; 否则,该命令会在包含要使用的内容的命令结尾处指定文件名参数。
上述命令的输出是40个字符的校验和散列。这是SHA-1哈希值 - 您正在存储的内容的校验和加上一个头文件,您可以稍后了解它。现在您可以看到Git如何存储您的数据:
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
如果您再次检查objects
目录,则可以看到它现在包含该新内容的文件。这就是Git最初存储内容的方式 - 作为每个内容的单个文件,用内容及其标题的SHA-1校验和命名。子目录以SHA-1的前2个字符命名,文件名为剩余的38个字符。
一旦在对象数据库中有内容,就可以使用该git cat-file
命令检查该内容。这个命令是用来检查Git对象的瑞士军刀。传递-p
到cat-file
指示命令第一数字出的内容的类型,则适当地显示它:
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
现在,您可以将内容添加到Git并再次将其拉出。你也可以用文件中的内容来做到这一点。例如,您可以对文件执行一些简单的版本控制。首先,创建一个新文件并将其内容保存到数据库中:
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
然后,向该文件写入一些新内容,然后再保存它:
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
您的对象数据库现在包含这个新文件的两个版本(以及您在那里存储的第一个内容):
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
此时,您可以删除该test.txt
文件的本地副本,然后使用Git从对象数据库检索您保存的第一个版本:
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
或第二个版本:
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2
但记住每个文件版本的SHA-1密钥是不实际的; 另外,您不会将文件名存储在您的系统中 - 只是内容。这个对象类型被称为blob。你可以让Git告诉你Git中任何对象的对象类型,给定它的SHA-1关键字git cat-file -t
:
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob
我们将要研究的下一种Git对象是树,它解决了存储文件名的问题,并且允许您将一组文件存储在一起。Git以类似于UNIX文件系统的方式存储内容,但有点简化。所有内容都以树和blob对象的形式存储,其中的树与UNIX目录条目相对应,blob对应于inode或文件内容。一个树对象包含一个或多个树条目,每个树条目包含一个指向blob或子树的SHA-1指针及其关联的模式,类型和文件名。例如,项目中最近的树可能看起来像这样:
$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
该master^{tree}
语法指定master
分支上最后一次提交所指向的树对象。注意lib
子目录不是一个blob,而是一个指向另一个树的指针:
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
注意
|
根据您使用的shell,在使用 在Windows上的CMD中,该 如果你使用ZSH,那么这个 |
从概念上讲,Git存储的数据看起来像这样:
你可以很容易地创建自己的树。Git通常通过获取暂存区或索引的状态并从中编写一系列树对象来创建树。因此,要创建树对象,首先必须通过暂存一些文件来设置索引。要使用单个条目创建索引 - test.txt
文件的第一个版本- 您可以使用管道命令git update-index
。您可以使用此命令将test.txt
文件的早期版本人为添加到新的临时区域。您必须传递--add
选项,因为文件尚不存在于暂存区域中(您甚至没有设置临时区域),并且--cacheinfo
因为您添加的文件不在您的目录中,而是位于您的目录中数据库。然后,您指定模式,SHA-1和文件名:
$ git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
在这种情况下,你指定了一个模式100644
,这意味着它是一个普通的文件。其他选项是100755
,这意味着它是一个可执行文件; 并120000
指定了一个符号链接。该模式取自普通的UNIX模式,但灵活性要低得多 - 这三种模式是Git中唯一对文件(blob)有效的模式(尽管其他模式用于目录和子模块)。
现在,您可以使用git write-tree
将暂存区域写入树对象。不需要任何-w
选项 - 如果该树不存在,则调用此命令会自动从索引状态创建树对象:
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
您还可以使用git cat-file
您之前看到的相同命令来验证这是一个树对象:
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
现在,您将创建一个新的第二版test.txt
和新文件的树:
$ echo 'new file' > new.txt
$ git update-index --add --cacheinfo 100644 \
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index --add new.txt
您的临时区域现在具有新版本test.txt
以及新文件new.txt
。写出该树(记录暂存区域或索引到树对象的状态)并查看它的外观:
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
请注意,此树具有两个文件条目,并且test.txt
SHA-1是早期版本(1f7a7a
)中的“版本2”SHA-1 。只是为了好玩,你会将第一棵树作为子目录添加到这个子目录中。通过呼叫,您可以将树读入您的临时区域git read-tree
。在这种情况下,通过使用--prefix
此命令的选项,可以将现有树作为子树读入暂存区域:
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
如果您从刚写入的新树创建工作目录,则会在工作目录的顶层获得两个文件,并且将获取bak
包含该test.txt
文件的第一个版本的名为子目录的子目录。你可以将Git包含的这些结构的数据想象成这样:
如果您已经完成了上述所有工作,现在您有三棵树代表您想要跟踪的项目的不同快照,但前面的问题仍然存在:必须记住所有三个SHA-1值才能调出快照。您也没有关于谁保存快照,保存时间或保存时间的信息。这是提交对象为您存储的基本信息。
要创建一个提交对象,你可以调用commit-tree
并指定一棵树SHA-1,并且直接在它之前的那个提交对象(如果有的话)。从你写的第一棵树开始:
$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
由于创建时间和作者数据不同,您将获得不同的哈希值。本章进一步用你自己的校验和替换提交和标记哈希。现在你可以看到你的新的提交对象git cat-file
:
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon 1243040974 -0700
committer Scott Chacon 1243040974 -0700
first commit
提交对象的格式很简单:它指定该点项目快照的顶级树; 作者/提交者信息(使用您的user.name
和user.email
配置设置和时间戳); 一个空行,然后是提交消息。
接下来,您将编写另外两个提交对象,每个对象都引用直接位于它之前的提交:
$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'third commit' | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
三个提交对象中的每一个都指向您创建的三个快照树之一。奇怪的是git log
,如果您在最后一次提交SHA-1上运行它,现在可以使用该命令查看真实的Git历史记录:
$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon
Date: Fri May 22 18:15:24 2009 -0700
third commit
bak/test.txt | 1 +
1 file changed, 1 insertion(+)
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon
Date: Fri May 22 18:14:29 2009 -0700
second commit
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon
Date: Fri May 22 18:09:34 2009 -0700
first commit
test.txt | 1 +
1 file changed, 1 insertion(+)
惊人。您刚刚完成了低级操作,无需使用任何前端命令即可构建Git历史记录。这实际上是Git在运行git add
和git commit
命令时执行的操作 - 它为已更改的文件存储Blob,更新索引,写出树并写入引用顶层树的提交对象以及紧接在它们之前提交的提交。这三个主要的Git对象 - blob,树和提交 - 最初作为单独的文件存储在您的.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
如果你遵循所有的内部指针,你会得到一个像这样的对象图:
我们前面提到过,每一个对象都存储在您的Git对象数据库中。让我们花一分钟看看Git如何存储它的对象。您将看到如何在Ruby脚本语言中交互式地存储blob对象 - 在本例中为字符串“what is up,doc?”。
您可以使用以下irb
命令启动交互式Ruby模式:
$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"
Git首先构造一个标题,首先标识对象的类型 - 在本例中为blob。对于头部的第一部分,Git添加一个空格,后面跟着内容的字节大小,并添加一个最终的空字节:
>> header = "blob #{content.length}\0"
=> "blob 16\u0000"
Git连接标题和原始内容,然后计算新内容的SHA-1校验和。您可以通过在require
命令中包含SHA1摘要库并Digest::SHA1.hexdigest()
使用字符串调用来计算Ruby中字符串的SHA-1值:
>> store = header + content
=> "blob 16\u0000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
Git使用zlib压缩新内容,您可以使用zlib库在Ruby中执行此操作。首先,你需要需要图书馆,然后运行Zlib::Deflate.deflate()
内容:
>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D"
最后,你会将你的zlib压缩内容写入磁盘上的一个对象。您将确定要写入的对象的路径(SHA-1值的前两个字符是子目录名称,最后38个字符是该目录中的文件名)。在Ruby中,FileUtils.mkdir_p()
如果子目录不存在,则可以使用该函数创建子目录。然后,打开该文件,File.open()
并用write()
生成的文件句柄调用将之前zlib压缩的内容写出到文件中:
>> 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
就是这样 - 你已经创建了一个有效的Git blob对象。所有Git对象都以相同的方式存储,只是使用不同的类型 - 而不是字符串blob,标题将以commit或tree开头。另外,虽然blob内容几乎可以做任何事情,但提交和树内容都是非常具体的格式。