git如何跟踪文件的更改

git如何跟踪文件的更改,git,internals,Git,Internals,我认为git提交保存更改文件的差异而不是副本的时间最长。我能找到的任何信息都与此相反。我做了一个小实验: $ git init $ subl wtf 在这里,我创建了一个包含99999行的文件,每行都是foo-bar-baz#line 即使在不同分支上提交冲突,也无法改变这种情况 如果git在每次提交时确实保留了所有更改文件的副本,那么为什么使用的空间没有重大变化?git在逻辑上存储了历史记录中所有文件内容的一组独特的集合。这意味着,如果在一个10 MB的文件中更改了一个字符,则该文件的整个内

我认为git提交保存更改文件的差异而不是副本的时间最长。我能找到的任何信息都与此相反。我做了一个小实验:

$ git init
$ subl wtf
在这里,我创建了一个包含99999行的文件,每行都是foo-bar-baz#line

即使在不同分支上提交冲突,也无法改变这种情况


如果git在每次提交时确实保留了所有更改文件的副本,那么为什么使用的空间没有重大变化?

git在逻辑上存储了历史记录中所有文件内容的一组独特的集合。这意味着,如果在一个10 MB的文件中更改了一个字符,则该文件的整个内容都有两个不同的对象ID。然而,为了确保相似的对象用delta存储,有很多优化措施。

至少有两种机制可以减少Git对象数据库中所需的总存储量。首先,分别压缩每个对象。其次,将对象集中到对象“包”中,将对象与delta关联起来,为类似对象节省更多空间。有一个很有启发性的程序。

git有对象数据库。有一种类型的对象“blob”,由其内容的sha1标识。因此,这意味着,如果您在存储库中的任何位置(分支/历史点/目录等)有一个相同内容的文件,那么它将只存储在数据库中一次

数据库中有两个部分,
对象/??/*
文件是单独的对象。也就是说,如果一个大文件有两个版本,并且只有单行差异,那么它将被存储两次,存储在两个不同的文件中(使用简单的lzma?压缩)

然后,如果git认为
对象
目录增长过多,它将运行垃圾收集。此过程的一个步骤是重新打包。它在
objects/pack/
文件夹中创建大型文件包,使用巧妙的增量压缩算法,并且它不是在特定文件的历史上工作,而是在整个对象数据库的范围内工作,因此它意味着即使某些完全不相关的文件偶尔看起来相似,它们可以打包成彼此的三角洲

因此,考虑到历史上的最新变化,每个
git gc
命令之后,可以对增量进行不同的重新压缩

另外,
对象包
vs
松散对象
只是物理存储细节,当您每天使用git时,这些细节是完全透明的。例如,执行
log
cherry pick
merge
等操作时使用提交的完整快照。因此,如果您正在执行diff,它只会动态比较目录/文件的两个版本,生成一个补丁/diff


与其他风投相比,这种方法非常独特。例如,Mercurial分别为每个文件存储不可变的增量日志,Subversion则为整个存储库存储增量。它还影响系统的工作方式—物理存储并没有被抽象出来,并且会造成一些重大限制,而git允许非常灵活的工作流程和算法,同时使存储库的大小非常小

每当文件发生更改时,git都会在其数据库中存储该文件的新副本。提交存储对该提交跟踪的文件的最新版本的引用。这意味着在创建提交时,它使用其父级存储的对未更改文件的引用,以及对新添加版本的引用对已更改文件的引用


定期(或者根据需要,比如说,
git gc
),通过创建包文件来压缩数据库,包文件包含给定集中每个文件的最新版本,以及可用于根据需要重建旧版本的“反向差异”。

因此,它不会物理存储差异,而不会复制?如果这样可以节省空间的话。其他风险投资总是做差异,即使在做一个完整的文件重写。在Git中,它只会在内容足够相似的情况下进行Delta,这非常有趣。我真的很想更多地了解git是如何决定应该存储增量还是完整副本的。您能否在回答中对此进行详细阐述,或者提供一个详细的链接?最好是比实现本身更简单的东西。我想你可以看看源代码。我从来都不想知道它是如何决定空间节约的,我相信无论是谁编写了优化都知道他/她在做什么。我相信,当维护三角洲的成本不仅仅是从一个新的三角洲基地开始,它只是忽略了三角洲,从一个新的对象开始。请参阅的文档,了解如何在逐个对象的基础上确定节省的空间。关于第一点,我不认为将上述文件缩减到几乎为零是明智之举。关于第二个-所以它实际存储差异而不是副本?是的,它实际存储(一些)差异(可以使用增量),但它们与Mercurial、CVS或Subversion存储的差异不同。它们不是文本差异。它们与您从
git diff
命令中获得的任何内容都没有关系。这非常有趣。我真的很想更多地了解git是如何决定应该存储增量还是完整副本的。您能否在回答中对此进行详细阐述,或者提供一个详细的链接?最好是比实现本身更简单的东西。这不是这里发生的事情(至少我理解是这样),因为
objects/pack
目录现在是空的。Pro Git这本书有一个,但在某个时候,可能会决定用delta重新构造类似的对象吗?您能否在回答中对此进行详细阐述,或者提供一个详细的链接?最好是比实现本身更简单的东西。另外,这里可能不是这样(至少不是您描述它的方式),因为
对象/pack
$ ls -la
total 1760
drwxrwxr-x 3 __user__ __user__    4096 Aug 13 21:02 .
drwxr-xr-x 3 __user__ __user__    4096 Aug 13 19:57 ..
drwxrwxr-x 7 __user__ __user__    4096 Aug 13 21:02 .git
-rw-rw-rw- 1 __user__ __user__ 1788875 Aug 13 21:02 wtf
$ git add --all
$ git commit -m 'Initial commit'
[master (root-commit) 6ef5084] Initial commit
 1 file changed, 99999 insertions(+)
 create mode 100644 wtf
$ subl wtf
$ git diff
diff --git a/wtf b/wtf
index 7ba3acb..bf7a9ed 100644
--- a/wtf
+++ b/wtf
@@ -14156,7 +14156,7 @@ foo bar baz 14155
 foo bar baz 14156
 foo bar baz 14157
 foo bar baz 14158
-foo bar baz 14159
+foo qux baz 14159
 foo bar baz 14160
 foo bar baz 14161
 foo bar baz 14162
$ git add --all
$ git commit -m 'bar -> qux on #14159'
[master 1b5ab4b] bar -> qux on #14159
 1 file changed, 1 insertion(+), 1 deletion(-)
$ subl wtf
$ git diff
diff --git a/wtf b/wtf
index bf7a9ed..1aeeaa3 100644
--- a/wtf
+++ b/wtf
@@ -14156,7 +14156,7 @@ foo bar baz 14155
 foo bar baz 14156
 foo bar baz 14157
 foo bar baz 14158
-foo qux baz 14159
+xyz abc baz 14159
 foo bar baz 14160
 foo bar baz 14161
 foo bar baz 14162
$ git add --all
$ git commit -m 'foo qux -> xyz abc on #14159'
[master 85ccf97] foo qux -> xyz abc on #14159
 1 file changed, 1 insertion(+), 1 deletion(-)
$ ls -la
total 1760
drwxrwxr-x 3 __user__ __user__    4096 Aug 13 21:02 .
drwxr-xr-x 3 __user__ __user__    4096 Aug 13 19:57 ..
drwxrwxr-x 9 __user__ __user__    4096 Aug 13 21:05 .git
-rw-rw-rw- 1 __user__ __user__ 1788875 Aug 13 21:04 wtf