gpt4 book ai didi

git - 如何通过压缩提交来节省git存储库中的空间?

转载 作者:行者123 更新时间:2023-12-02 11:04:45 26 4
gpt4 key购买 nike

如果我有一个git仓库,其中有一个初始提交,然后是100个小的提交,它们会更改100个文件,而每个小的提交只对一个文件进行一次更改,是否可以通过将这100个提交压缩为一个大的100个文件更改的提交来节省空间?例如:

$ git checkout master
Already on branch 'master'.
$ git reset --soft HEAD~100 && git commit -m 'squash last 100 commits'


将用与旧提交内容相同的新提交替换分支 master的尖端,但将其历史记录中的前100个提交保留在历史记录之外。这样可以节省多少空间?

最佳答案

也许(甚至“大概”)会节省一些空间,但不是马上。实际上,起初它会使事情变大一点。

让我们看一下git如何实际存储事物。它变得复杂,但是开始非常简单:git完整地存储了每个文件(使用“ zlib deflate”压缩,否则仅存储原始文件)。

git对象模型

在git存储库中,所有内容都存储为一个对象。每个对象都由其SHA-1命名,SHA-1是其实际内容(对象类型,大小和数据)的加密校验和。这使您可以执行以下两项操作之一:计算SHA-1并按对象名称存储对象(或发现对象已经在其中);或者,给定SHA-1名称,找到对象并访问其内容。

有四种类型的对象。一个在这里没有意思。1另外三个是:


“提交对象”,用于保存提交数据,包括提交消息本身以及“树”对象的SHA-1 ID;
“树”对象,用于存储内容列表:SHA-1,文件名和文件的模式; 2和
“ blob”(文件)对象,用于存储您的实际文件。 (偶然地,单词“ blob”可能源自数据库术语BLOB,它是"backronym" for "Binary Large OBject"。)


通过从提交的SHA-1 ID开始,git可以提取树,该树告诉它要提取的blob以及为其指定的文件名(例如,blob对象1234567...被称为file1.txt)。

实际对象存储在.git/objects的子目录中,例如,对象1234567...保留在.git/objects/12/34567...中。 (SHA-1的长度始终为40个字符,但我们通常将其缩写为7加3点,通常就足够了。)



1为了完整起见,最后一个对象类型是“带注释的标记”:它包含一个提交(如提交)(“ tagger”),另一个SHA-1(如提交)和一条消息(如提交),因此基本上就像一个承诺;但是它包含的SHA-1 ID通常是提交对象的ID,而不是树对象的ID,然后有一个轻量级标签指向带注释的标签。除其他外,这使您可以将经过密码签名的标记放入存储库中,其他人可以检查该标记以例如查看您是否已批准该特定提交。

2对于普通文件,该模式实际上只是一个位(执行或不执行),但是git还可以存储符号链接,子树和“子模块”,因此实际上还有一点点。出于我们的目的,我们可以忽略所有文件。



一个例子

假设我们创建了一个存储库,并对其进行了100个文件的初始提交,每个文件都与所有其他文件不同。为了简单起见,我们还将所有100个文件都放在顶层(没有子目录)。这样,存储库的初始状态为:


一个提交对象
一棵树对象
100个斑点


加上通常的git开销(一个分支文件,其中包含master的最尖端SHA-1 ID,HEAD文件,依此类推)。我们将这个仓库称为“ hundredfile.git”。这100个文件只是“ file1.txt”到“ file100.txt”。

如果我们在数百文件.git中计算对象,那么根据上面的列表,将有102个对象。

现在,我们将克隆此存储库,以便我们可以进行100次提交或1次提交,并比较结果。首先,让我们进行100次提交。以下内容实际上是伪代码,但足够接近,可以真正起作用(我认为/希望),只要您设置了make_change_即可对文件进行更改。另外,我们希望每次更改都产生一个新的唯一文件(以便所有100个文件始终互不相同),否则以下描述中的某些项目将变为错误。

$ git clone ssh://host.dom.ain/hundredfile.git method1
[clone messages]
$ cd method1
$ for i in $(jot 100); do # note: jot 100 => print list of values 1, 2, ... 100
> make_change_to file$i.txt; git add file$i.txt; git commit -m "change $i"
> done
[100 commit results come out here]


每次我们进行一次新提交时,git都会将索引(临时区域)转换为具有新blob的新树;但我们只修改了一个文件,因此100个Blob中的99个实际上与上次相同(具有相同的SHA-1 ID)。只有一个修改后的文件 file$i.txt具有新的SHA-1 ID。

因此,每次进行一次新提交时,都会得到一个新的提交对象( "change $i"加上作者和提交者的时间戳,再加上树),一个新的“ tree”对象(列出99个相同的Blob ID)加上一个新的,不同的Blob-ID)和一个新的“ blob”对象。

换句话说,每个提交将三个对象添加到存储库,并重新使用99个现有的Blob对象。我们重复此过程100次,因此添加了300个对象。 300 + 102 = 402,因此 method1中的此克隆具有402个对象。

现在,让我们回到原始的 hundredfile.git并进行新的克隆:

$ cd .. # up out of the "method1" repo
$ git clone ssh://host.dom.ain/hundredfile.git method2
[clone messages]
$ cd method2


这次,让我们一次更改(并添加)所有100个文件后进行一次提交:

$ for i in $(jot 100); do
> make_change_to file$i.txt; git add file$i.txt
> done
$ git commit -m 'change all'
[one commit result comes out here]


在这里,所有100个文件都是不同的,因此git会存储一个新提交以及一棵新树,其中包含100个新的blob-ID。现在,此回购包含102 + 102 = 204个对象,而不是 method1中的402个对象。

几乎可以肯定这会占用少得多的磁盘空间。每个系统的详细信息各不相同,但是通常任何文件都至少需要存储512或4096(一个“磁盘块值”)字节。由于每个git对象都是一个磁盘文件,因此存储更多对象会占用更多空间。

但是有一些皱纹。

Git就像博格:它试图增加其集体

Git非常喜欢挂在物品上。当您将100个提交(在 method1中)压缩为一个时,git所做的就是将一个新提交添加到其存储库中。这一新提交包含您的提交消息(无论它是什么)以及通常的日期和树ID等。它有一棵树,该树与上一次提交的最终树完全相同,因为该树存储每个Blob的名称和SHA-1,也与文件中带有该Blob的文件的前一个Blob完全相同。一样的名字。 (也就是说,新提交中的树的“ file1.txt是1234567 ...”与原始分支提示提交中的树相同,并且对于每个文件都是如此,因此树是相同的,因此其校验和相同,因此其SHA-1 ID也相同。)

因此,您在 method1中得到的是402个对象变为403个对象:原始的402,再加上一个新的提交,该提交重新使用了先前的树及其所有先前的blob。存储库会稍微大一点(一个文件可能只有一个磁盘块)。

最终,“未引用”的对象被垃圾收集

如果git从不丢弃任何内容,则存储库将变得seriously肿,因此有一种删除对象的方法。这是基于“引用”的,它是“查找事物的方式”的花哨词。分支是最明显的引用形式:分支引用文件包含分支尖端的SHA-1 ID。标签也会计数,“远程分支”,以及在这种情况下的键,是“重新引用”。

当您将100个提交压缩为一个时,分支的上一个尖端(存储在上述问题中的 master中的SHA-1)将保存在两个引用日志中,一个保存为 HEAD,另一个保存为分支。 (当然,新的南瓜提交的ID照常进入 master。)

这些reflog保留旧的提交,但是直到reflog条目到期为止。默认情况下,到期时间设置为30天(某些情况下为90天,而这种情况下为30天)。一旦它们过期, git reflog expire将删除它们(或者您可以手动删除它们,但这有点棘手)。

在这一点上,旧的提交真正成为未引用的:无法找到上一次提交的SHA-1 ID。现在,git的垃圾收集器(属于 git gc的一部分,并且请注意 git gc也会为您运行 git reflog expire)可以删除提交,一旦删除,还可以删除先前的提交,以此类推,返回100提交。这些使除最后一棵树外的树对象未被引用;而那些反过来使blob未被引用,最终的blob除外。 (最后一棵树和最后一滴斑点仍然可以通过您进行的南瓜提交找到。)

因此,现在存储库实际上缩小到与repo method2中相同的204个对象。 (如果所有提交时间戳都相同,则它们只是完全相同的对象,但是对象的数量将减少到204。)

但是还有另外一种皱纹,这使得以前所有的皱纹都变得无关紧要。

Git打包对象

除了对象的“宽松”格式( .git/objects/12/34567...)之外,git还具有“打包”格式。打包的对象将针对同一打包中的其他对象进行压缩。

当您对某个文件进行更改时,您将获得两个不同的git blob对象。3每个对象都是zlib压缩的,但是git此时并未将其与任何其他blob进行比较:它是“独立压缩的”。但是,一旦两个对象存储在一个包文件中,它们就可以彼此“增量压缩”。 delta格式的细节相当晦涩(并不是那么重要-git取决于4号打包文件格式,大多数人在上次更改时从未注意到),但关键是现在git实际上存储“更改”。不一定是“ file1.txt中发生了什么变化:例如,git对 file39.txt压缩了 file75.txt。这完全取决于文件中的实际内容以及git选择压缩的对象。甚至可以压缩其他种类的物体

与reflog和垃圾回收一样,git的打包(或重新打包)是通过 git gc自动完成的,并且git在认为合适时会自动为您调用 gc(请参见 setting for gc.auto)。

如果愿意,您可以进行手动重新打包,到期和对象收集,并且可以调整某些参数以获得更好的打包效果,但这超出了此答案的范围。通常,自动结果就很好,并且压缩得如此之好,以致 .git目录小于任何单个检出的提交并不罕见。



3更准确地说,新文件存储为松散对象;存储在包中的现有对象仅保留在包中。



底线

为了节省大量空间,您必须将所有引用都丢弃到压缩效果不好的大型文件(巨型图像或gzip压缩的tar-ball或其他文件)上,即使打包文件中存在增量压缩。您可以使用 git filter-branch进行此操作,尽管它相当复杂。或者您可以使用 BFG cleaner。有关几种方法,请参见 How to remove/delete a large file from commit history in Git repository?

总的来说,我认为,针对个人提交这样做并不值得。如果结果是更明智的历史记录,则压入一堆提交;不要只是为了节省磁盘空间。它可能节省了一点,但不足以值得失去有用的历史。 (另一方面,丢失无用的历史记录,即使使存储库变大,它的历史记录也会使以后的调试更加困难而不是更容易了!)

关于git - 如何通过压缩提交来节省git存储库中的空间?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26080278/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com