【问题标题】:How does git track changes to filesgit如何跟踪文件的变化
【发布时间】:2015-08-13 18:29:13
【问题描述】:

在很长一段时间里,我认为 git 提交会保留更改文件的差异,而不是副本。我能找到的任何信息都相反。我做了一个小实验:

$ git init
$ subl wtf

这里我创建了一个99999行的文件,每一行都是foo bar baz #line

$ 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

即使在有冲突的不同分支上提交也没有改变这种情况。

如果 git 真的在每次提交时都保留所有更改文件的副本,为什么使用的空间没有显着变化?

【问题讨论】:

  • Pro Git 这本书有一个interesting chapter,它非常详细地解释了 Git 如何存储跟踪的数据。

标签: git internals


【解决方案1】:

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

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

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

因此,考虑到历史记录中的最新更改,每个 git gc 命令之后可以以不同方式重新压缩增量。

另外,object packsloose objects 只是物理存储细节,当你每天使用 git 时它们是完全透明的。例如。做logcherry-pickmerge 等正在使用提交的完整快照进行操作。所以,如果你在做差异,它只是比较一个目录/文件的两个版本,为你生成一个补丁/差异。

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

【讨论】:

  • 所以它存储物理副本,但可能在某些时候决定用deltas重构类似的对象?您能否在答案中对此进行扩展或提供包含详细信息的链接?最好是比实现本身更简单的东西。也可能这不是这里发生的事情(至少不是你描述它的方式),因为objects/pack/ 目录现在是空的。
  • @ndn 是的,它在垃圾回收期间进行重组。尝试运行git gc,所有对象都将被打包 - 来自objects/??/ 的文件将被删除,objects/packs/ 将被创建。
  • 那么此时不应该所有提交都有wtf物理 副本吗?为什么不占用更多空间?
  • @ndn ls -la 没有显示目录中的所有文件发生了多少。它显示文件系统目录条目列表占用了多少磁盘空间。 4k - 是文件系统块大小。根本与 git 无关。使用du -sh .git/objects 计算所有文件大小的总和。
  • 你是完全正确的。整件事对我来说只是一个愚蠢的错误。我现在很惭愧:~
【解决方案2】:

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

定期(或根据需要,例如git gc),通过创建包含给定集中每个文件的最新版本的包文件以及可用于根据需要重建旧版本。

【讨论】:

  • 这不是这里发生的事情(至少我的理解是这样),因为objects/pack 目录现在是空的。
  • 那你还没有打包任何文件。
  • 那么此时不应该所有提交都有wtf物理 副本吗?为什么不占用更多空间?
  • 只有在特定提交中发生更改时才会添加文件的新副本。如果文件没有从提交 A 更改为提交 B,则两个提交都将引用该文件的同一副本。因此,虽然提交是整个项目的完整快照,但单个 文件 的快照可以在提交之间共享。
  • 这就是问题的重点。 wtf 已更改,但占用的空间仍然没有真正的区别。怎么样?
【解决方案3】:

至少有两种机制可以减少 Git 对象数据库所需的总存储空间。首先,每个对象都被单独压缩。其次,将对象集中到将对象与增量相关联的对象“包”中,从而为类似对象节省更多空间。 ProGit 中有一个chapter on packfiles,非常有启发性。

【讨论】:

  • 关于第一点 - 我认为将上述文件减少到几乎没有任何东西是不够聪明的。关于第二个 - 所以它 physically 存储 diffs 而不是 copies?
  • 是的,它在物理上存储(一些)差异(可能存在差异),但它们与 Mercurial 或 CVS 或 Subversion 正在存储的差异不同。它们不是文本差异。它们与您从 git diff 命令中获得的任何内容都没有关系。
  • 这很有趣。我真的很想了解更多关于 git 如何决定它应该存储 delta 还是完整的 copy。您能否在答案中对此进行扩展或提供包含详细信息的链接?最好是比实现本身更简单的东西。
  • 这不是这里发生的事情(至少按照我的理解),因为objects/pack 目录现在是空的。
【解决方案4】:

Git 逻辑上在历史记录中存储了一组不同的所有文件内容。这意味着,如果在 10 MB 文件中更改了一个字符,则文件的全部内容具有两个不同的对象 ID。但是,在后台进行了很多优化,以确保类似的对象与增量一起存储。

【讨论】:

  • 那么它确实物理地存储差异而不是副本吗?
  • 如果这样可以节省空间。即使在完全重写文件时,其他 VCS 也总是会产生差异。在 Git 中,它只会在内容足够相似以保证它的情况下执行增量。
  • 这很有趣。我真的很想了解更多关于 git 如何决定它应该存储 delta 还是完整的 copy。您能否在答案中对此进行扩展或提供包含详细信息的链接?最好是比实现本身更简单的东西。
  • 我想你可以看看源代码。我从来没有想过确切地知道它是如何决定节省空间的,我相信编写优化的人都知道他/她在做什么。我相信当维护增量的成本不仅仅是从一个新的增量基础开始时,它只是忽略增量并从一个新对象开始。请参阅git cat-file 的文档,了解如何确定逐个对象的空间节省。
猜你喜欢
  • 1970-01-01
  • 2011-08-26
  • 2017-08-04
  • 1970-01-01
  • 2012-12-29
  • 1970-01-01
  • 2017-04-24
  • 1970-01-01
  • 2011-05-26
相关资源
最近更新 更多