git - git如何跟踪文件的变化

标签 git internals

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

$ git init
$ subl wtf

Here I create a file with 99 999 lines, each of which is 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 真的在每次提交时保留所有更改文件的副本,为什么使用的空间没有显着变化?

最佳答案

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

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

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

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

此外,object packsloose objects 只是物理存储细节,在你日常使用 git 时是完全透明的。例如。执行 log cherry-pickmerge 等操作时使用提交的完整快照。因此,如果您正在执行 diff,它只会即时比较一个目录/文件的两个版本,为您生成一个补丁/diff。

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

关于git - git如何跟踪文件的变化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31995829/

相关文章:

user-interface - 如何做 ncurses 等。人。工作?

git - 新 git 存储库的默认配置设置?

通过管道命令的 Git 分支状态

git - 不要向 git 提交特定更改,但也不要从我的本地工作区恢复它们

bash - 如何通过 sed 或 awk 替换文件中的两个代码块

memory-management - 独立内存 channel : What does it mean for a programmer

Git 没有使用 .gitignore 的内容

azure - 通过 RDP 连接到 Azure VM : An internal error has occurred

c# - 为什么 C# System.Decimal (decimal) "waste"位?

javascript - 为什么现代浏览器 JS 引擎是多线程的?