Git内部存储换行符吗



Git是否在内部存储特定的行结束?

我的猜测是,Git只存储行数据本身(中性行结尾),根据操作系统的不同,它将使用特定于平台的行结尾。

或者换句话说:每一行的结尾都可以被存储吗;混合的";在同一个文件中?

我不是在说.gitattributes文件中的设置。

Git将整个文件存储为"斑点";并且只在读取(签出)或写入(索引)时进行转换。Git不存储";中和线";。

所以,是的,Git可以用混合行结尾保存文件,但这通常是一种需要避免的糟糕做法。

正如iBug所说,Git只是存储一个原始数据块。Linus最初在Git中没有任何CRLF换行技巧:2005年的首次发布根本没有。第一个行尾转换代码于2007年2月在commit6c510bee2013022fbce52f4b0ec0cc593fc0cc48中引入Git。.gitattributes文件本身是在2007年4月的commitd0bfd026a8241d544c339944976927b388d61a5e中引入的。

然而,理解这些的真正关键是要注意每个文件的索引副本和每个文件的工作树副本之间的差异。请记住,索引实际上包含建议的下一次提交,或者至少包含其快照。(下一次提交的元数据是在运行git commit或任何其他命令时生成的。)任何现有提交的内容都是神圣不可侵犯的:任何东西,甚至Git本身,都无法更改它们。

提取侧

当您第一次签出某个提交时——例如,使用git checkoutbranchgit switchbranch,或者使用原始哈希ID(尽管git switch在这种情况下需要--detach标志)——Git将填充该提交的Git索引,则填充您的工作树。(Git索引和工作树中的任何以前的提交都会首先从这两个位置删除,当当前分支上有未提交的更改时,请在Checkout另一个分支中列出一些奇特的注意事项。)

索引获取提交中的内容。这意味着,如果一个提交的文件有一个奇怪的仅LF行和CRLF行的混合,或者是一个像JPG这样的二进制文件,带有随机的二进制数据,天真的程序会认为是行尾,那么,这也是索引中的内容。更确切地说;复制";索引中的文件实际上只是Git数据库中某个blob原始哈希ID。保存一些现有的已提交文件的blob对象是只读的,因此很容易共享。因此,为了进行初始签出,Git只允许索引共享该副本。blob散列ID进入索引,存储在提交中列出的文件名下的slot zero条目中1这个blob存储在Git的对象数据库中,要么以松散对象的形式压缩,要么以压缩对象的形式压缩。Git可以读取任意一个;尽管Git可以很容易地创建新的(不同的)松散对象,但没有人也没有什么能重写一个。

然而,工作树副本是另一回事。Git必须解压缩blob对象。这意味着读取压缩的blob字节,并在此基础上运行zlib解压缩代码,以获得表示文件内容的不同字节,正如您希望看到的那样。因为Git已经在做这项工作,所以这是Git做更多工作的理想场所:Git可以用CRLF行尾取代仅LF的行尾2

因此,当Git索引副本提取到工作树中时,Git可以将仅LF行转换为CRLF行。如果某个文件(通过.gitattributes或任何其他方式)被标记为需要此转换,Git会执行此操作;如果文件的LF前面没有CR,Git会确保它将写入的工作树文件首先有CR,然后是LF。

这是git checkout的一面3让我们暂停一下脚注,然后看看事物的git add方面。


1从技术上讲,提交列出了代表快照的对象的树哈希ID。该树对象包含名称组件块和哈希ID。散列ID可以是子树的散列ID,其中包含更多的名称组件片段,也可以是表示应该签出的文件的Blob的散列ID。散列ID可能是符号链接或gitmodule的散列ID,但它们相对罕见:常见的树条目用于子树和文件Blob。

2这种转换——仅LF到CRLF——是Git文件提取代码中内置的仅行结束转换。您可以使用Git的污迹过滤器添加自己的任意附加转换,但这些由您自己编写。(请注意,Git LFS使用了一个预先编写的污点过滤器,可以从网络上其他地方的单独存储区域提取"大"文件系统。这是Git的一个附加组件,而不是Git本身的一部分。)

3在新的git restore命令进入Git 2.23之前,只有索引到工作树的转换才能进行这种转换。现在git restore可以直接从提交到工作树中提取文件,还有一个地方可以做到这一点。请注意,git checkout --path/to/filegit checkoutcommit--path to file首先写入Git的索引,然后只写入到工作树,所以这个特定的代码路径通过索引到工作树函数。这就是为什么这个新的git restore功能值得一个脚注:在Git2.23之前,你必须先让Git在Git的索引上乱写;现在你可以避免了。


git add

当您运行git add(例如,包括来自git commit -a的隐含添加)时,Git会在此时完成实际的艰苦工作,而不是等待稍后的git commit。如果您使用git commit -a,那么提交可能会在几毫秒内发生,但逻辑仍然相同:首先,Git执行git add

git add的目的是更新Git的索引。我们必须首先更新索引——建议的下一次提交(或其快照)。只有当提议的提交与我们希望提交的内容相匹配时,我们才能提交。

由于索引包含路径名和blob哈希ID(以及文件模式),Git此时必须将工作树文件转换为blob。要做到这一点,Git必须首先将blob作为一个新的松散对象来生成——或者至少,如果它这样做了,就要弄清楚它的哈希ID是什么。事实证明,找出哈希ID是什么的最快方法是继续写对象,用zlib的压缩器压缩,同时计算哈希。由于我们还不知道对象的名称(hash ID),我们只使用一个临时名称:.git/objects/tmpXXXXXXX,其中Xes填充了一些唯一的东西。(准确的临时名称在这里无关紧要。)

然而,要将数据馈送到压缩和哈希函数,Git必须读取文件的工作树副本。如果工作树副本被标记(通过.gitattributes或任何其他机制)为需要转换,那么,这是检查CR和LF的最佳时机,删除CR部分,这样我们就可以获得LF ony行结尾。这样,散列函数zlib压缩器都将只获得LF行4

一旦整个文件通过哈希函数和压缩器馈送,Git现在就有了正确的blob哈希ID。Git检查对象是否已经作为哈希对象存在。如果是这样,Git只需重新使用现有对象,删除临时文件5否则,Git会重命名临时文件,使其成为.git/objects/中的有效松散对象。现在该文件有了一个对象,这就是Git索引中的内容。请注意,在这一点上,整个文件都已转换为仅LF行结尾,而不管以前的提交是什么。


4如前所述;删除紧跟着LF"的CR;filter是Git中唯一内置的,在这些代码路径中。您可以使用干净过滤器自己进行任意过滤,但必须自己编写。(不出所料——如果你已经阅读并理解了脚注2——这也是Git LFS自动作为插件提供的:如果一个文件是"大"的,并且要存储在其他地方,Git LFS的"干净"过滤器会将文件存储在其他位置,并生成LFS数据,这样以后的签出就可以检索该文件,作为"干净"数据。)

5Git可能应该检查——也许是可选的,因为这可能很慢——这不是哈希冲突的结果。我认为目前还没有。意外哈希冲突的可能性很小,可以忽略不计,但考虑到破坏SHA-1的存在证据,在我看来,可选检查是个好主意。


这一切只有在启用时才会发生

为了让Git对文件进行更改,该文件必须标记为";应修改";。将core.autocrlf设置为true可以标记一些文件:Git现在将尝试猜测某个文件是文本还是二进制。在.gitattributes中列出文件可以将某些文件标记为特定的文本或二进制文件,也可以将它们标记为特定转换。

不过,同样,中唯一内置的转换是:

  • 提取:仅将LF转为CRLF
  • 添加:仅将CRLF转为LF

有些设置同时启用两种转换,有些设置仅启用添加端转换(旧版本的Git中为旧的crlf=input,现代Git中的eol=lf)。

请注意,由于blob到工作树的提取从未在LF之前删除CR,因此以内部blob形式(一致或混合)具有CRLF结尾的现有提交文件始终作为工作树中具有CRLF末尾的文件检出。如果你没有触摸处于这种状态的签出文件,Git会注意到你没有触摸它,并且不会添加文件,因此在下一次提交时,它仍然具有混合或一致的CRLF结尾。

git add --renormalize标志旨在强制Git重新添加文件,即使这些文件看起来未被更改。这样,如果设置好了,他们就可以通过CRLF到LF的转换。

最新更新