我已经阅读,搜索和阅读,冲洗,重复,但是对Git中树木的基本理解仍然无法避免(除了它们与文件系统目录非常类似)。它们似乎与索引有着内在联系,但我无法通过厚厚的头骨获得如何。
当然,Blob很容易,因为它们很简单。树木,至少在概念上,对我来说更加模糊。是否有某种解释方式 - 以接近补救方式的方式:
可能还有其他一些我甚至不知道要问的问题,所以请随时以任何必要的方式详细说明,以促进对对象类型及其背景的一致理解。
非常感谢。
答案 0 :(得分:8)
这可以是第一个描述:
alt text http://eagain.net/articles/git-for-computer-scientists/git-storage.2.png
(来自Git for Computer Scientists)
但Git From the Bottom Up会有最详细的描述。
指数
与您可能使用的其他类似工具不同,Git不会直接从工作树提交更改到存储库中。相反,更改首先在称为索引的内容中注册
在提交之前,将其视为一种“逐一确认”您的更改的方式
(它会立即记录您所有批准的更改)
有些人认为将其称为“临时区域”而不是索引是有帮助的。
工作树
工作树是文件系统上具有与之关联的存储库的任何目录(通常由其中存在名为.git
的子目录表示。)。
它包括该目录中的所有文件和子目录。
Git blob和文件系统文件之间的区别在于blob不存储有关其内容的元数据。所有这些信息都保存在包含blob的树中。
一棵树可能将这些内容知道为2004年8月创建的名为“foo”的文件,而另一棵树可能知道与五年后创建的名为“bar”的文件相同的内容。 在普通文件系统中,具有相同内容但具有不同元数据的两个文件将始终表示为两个独立文件。
为什么会出现这种差异?主要是因为文件系统设计用于支持更改的文件,而Git则不支持 数据在Git存储库中是不可变的这一事实使得所有这些都工作,因此需要不同的设计。
简而言之,引用Git Internal(非常简短的摘录)
树是树包含的树和blob的简单列表,以及这些树和blob的名称和模式。
更具体地说,树的内容是:
一个非常简单的
text文件,其中列出了:
- 模式,
- 型,
- sha1和
- 名称
每个实体。
(评论中的Jakub Narębski详细信息:
实际上树对象不是文本文件:由于某种原因,它以二进制格式存储SHA-1。
可是:
提交对象使用父级和顶级树的SHA-1的文本格式。
)
OP在评论中添加:
我认为我很难理解每个提交都有一棵树。
肯定有。 **提交是指向**顶级树****的指针,由其SHA1引用。
是什么触发Git最初创建一棵树?
您的第一次提交(git init不会创建树,只是一个空的Git存储库)
根据Pro Git的说法,索引有一个搭配,但没有提供更多信息。
您必须提及internal objects chapter:
Git通常通过获取暂存区域或索引的状态并从中写入树对象来创建树。
因此,只要您'git add
'某些文件(即“暂存它们”或“将它们添加到索引中”),就允许Git在下次提交时从索引创建树。
alt text http://progit.org/figures/ch9/18333fig0901-tn.png
这实际上是Git在您运行
时所做的事情git add
和git commit commands
- 它为已更改的文件存储blob,
- 更新索引
- 写出树木,
- 并写入引用顶级树和紧接在它们之前的提交的提交对象。
这三个主要的Git对象 - blob,树和提交 - 最初存储在
.git/objects
目录中的单独文件中。
答案 1 :(得分:3)
提交时,git为索引的内容构建树层次结构,然后构建引用该树层次结构的根的提交。在git-add操作之后,存储库包含添加的所有文件的blob对象,并且索引包含对与路径名称配对的blob的引用。还没有树对象。
当你提交(技术上,在写树操作期间)时,git使用索引信息递归地构造一组树。它从仅包含blob的树开始,确定它们的标识符,并写入树对象。然后它上升到每个级别并构造下一组树,因为在子树标识符已知之前不会发生这种情况。然后它存储根级树。
提交操作分为写树和提交树步骤。写树步骤使用索引的当前状态来识别并(如果需要)存储所有树。 commit-tree步骤创建一个新的提交,引用所有父提交和刚刚创建的根树。
当你学习如何使用git时,主要关注提交的有向无环图(DAG):每次提交都包含一个指向前一次提交的指针,你可以通过跟随这些链接回过头来。这是有道理的,因为用户界面是关于提交的,而树实际上是次要的。
树也形成DAG,但区别在于它们不代表提交的历史。就像一个blob一样,一旦创建了一个树,它的标识符将永远指向那些包含这些内容的树。如果树中列出的任何blob或树被修改或删除,它将具有新的标识符,并且树本身将在下一次提交中具有新名称。
好吧,假设您的存储库看起来像这样:
foo/
a.txt
b.txt
bar/
a.txt
b.txt
并且所有文件都是空的。然后存储库中有三个对象,不包括提交:
顶级树:
$ git cat-file -p ebf247ec5ebc97b12cd7a56db330141568edb946
040000 tree 2bdf04adb23d2b40b6085efb230856e5e2a775b7 bar
040000 tree 2bdf04adb23d2b40b6085efb230856e5e2a775b7 foo
有两个blob的树:
$ git cat-file -p 2bdf04adb23d2b40b6085efb230856e5e2a775b7
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 b.txt
空blob:
$ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
首先,我将解释为什么树foo
和bar
由同一个对象存储,然后我会做出改变,看看会发生什么。
树的SHA1标识符完全由其内容决定,就像blob一样。请注意,不涉及其名称,这意味着重命名树将重新创建其父级,但不需要还原树本身。如果将上面的输出粘贴到git mktree
,git将使用结果树的对象名称进行响应。在引擎盖下,mktree
生成SHA1,就像这个ruby代码一样:
>> require 'digest/sha1'
>> sha1 = ['e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'].pack 'H*'
>> contents = "100644 a.txt\0#{sha1}100644 b.txt\0#{sha1}"
>> data = "tree #{contents.length}\0#{contents}"
>> Digest::SHA1.hexdigest(data)
"2bdf04adb23d2b40b6085efb230856e5e2a775b7"
现在我要修改'bar / b.txt'并检查新的树集:
$ echo hello > bar/b.txt
$ git add bar/b.txt
$ git write-tree
5fa578acc6695bf2af2975ed0ffa7ab448b52c22
$ git cat-file -p 5fa578acc6695bf2af2975ed0ffa7ab448b52c22
040000 tree 9a514e08691a9f636665a43a1c89dc1920dab0fa bar
040000 tree 2bdf04adb23d2b40b6085efb230856e5e2a775b7 foo
由于'foo'下面没有任何内容发生变化,因此它会被存储为完全相同的树。对于大型结构,这是一个巨大的空间胜利。 “bar”有一个新的树,因为我修改了它:
$ git cat-file -p 9a514e08691a9f636665a43a1c89dc1920dab0fa
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.txt
100644 blob ce013625030ba8dba906f756967f9e9ca394464a b.txt
$ git cat-file -p ce013625030ba8dba906f756967f9e9ca394464a
hello
同样,树对象中没有任何关于修订或提交的内容。如果树及其子项从一个提交到下一个提交保持不变,则它们将由同一个对象表示。如果同一个提交中有两个相同的树,它们也将由同一个对象表示。
关于索引,它与树之间只有一个最小的链接。一个重要的区别是索引存储blob名称和路径,使用平面列表,并且根本没有提到树:
$ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 bar/a.txt
100644 ce013625030ba8dba906f756967f9e9ca394464a 0 bar/b.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 foo/a.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 foo/b.txt
将数据从树复制到索引时,树结构会变平。当数据从索引复制到树时,它将被重建。
答案 2 :(得分:1)
树表示磁盘上的文件状态。这是一种永恒的,不可改变的事物。
提交不代表磁盘上的文件状态。提交的工作是表示状态的历史 - 即,按时间顺序提交链接树(状态)。单个提交表示某人将磁盘上的文件状态提交到永久存储的时刻。它通过保持指向树的指针(“这是作者提交的状态”),指向先前提交的指针(“这是作者提交它之前的历史记录”)以及获取一个所需的各种元数据来实现。良好的历史(时间戳,提交信息,作者身份)。
编辑: 在回复评论时,“那么每次提交都是如此,基本上是完整代码库的快照(使用内容未改变的指针)?”:每个提交都有一个指向树的指针(是整个代码库的快照),但实际上,因为我们在这里要求精确,答案是否定的:提交不代表代码库的状态。它们代表了在永久历史中输入代码库状态的时刻。但是,提交指向的树绝对 表示整个代码库的状态(因为它是顶级树 - 以repo根目录为基础的树)。
但是,出于实际目的,您可以将提交视为特定时刻和代码库的特定状态。如果你曾经看过一个在文档中带有“树状”的命令,那么这就是他们所说的:你可以给它一个树或一个提交,如果你给它一个提交,它只会遵循到它指向的树。所以,是的,git文档,当我们只是在不考虑实现的情况下使用它时,你可以将提交视为了解repo的整个状态(而不仅仅是改变了)。
与你从Joel Spoelsky的不正确的博客文章中读到的内容相反,git不存储差异。它在每次提交时存储代码库的整个状态。它只是使用带有散列的巧妙技巧来确保对象存储中的数据冗余非常少。