我完全不清楚为什么你关心每个用户的工作树中出现的内容。使用 Git 时最重要的是每个 commit 中出现的内容。不过,让我们按要求回答这个问题:
更新 readme.txt 会有帮助吗?
是的,会的。 (这个答案的其余部分都是可选的阅读,但可能是一个好主意。)
为什么会这样
eol=crlf 属性告诉 Git,当文件从 索引 复制到用户的工作树时,Git 应该找到 \n-only 行结尾在冻结格式副本中,并将它们替换为用户工作树中的\r\n 行结尾。
这并不是说你说的是错误,但你说的也不是很正确。 :-) 事实上,它是不完整的。要真正理解这一点,需要了解提交、索引和用户的工作树如何交互。
提交
请记住,Git 最基本的目的——它存在的根本原因——是存储提交。每个提交都包含每个文件的完整快照。更准确地说,提交包含该提交中每个文件的完整快照。这么说来,这听起来是多余的——但想法是,这相当于当时存在的这些文件的存档。每个提交可能有一组完全不同的文件,但这通常不是我们使用Git的方式。
您可以天真地从 rar 或 tar 或 zip 之类的存档器中构建这样的东西,每次您想要提交时,只需制作一个新的完整存档即可。每个这样的档案都将完全独立于每个以前的档案。这使他们以后很容易回来。缺点是这些会占用大量空间,并且很容易忘记。
我们首先观察到每次提交都倾向于重用上一个存档中的大部分文件。如果我们不制作独立档案,而是制作一个尽可能重用前一个档案的档案,该怎么办?事实上,Git 就是这样做的。
为了使这项工作和更快,Git 添加了更多技巧。最主要的是每个文件的数据——它的内容——都以一种压缩的、只读的、仅限 Git 的格式存储,这使得它可以非常快速地查看 Git 是否已经拥有该文件的副本。因为它是只读的——事实上,每个提交的每一部分都是只读的——重用文件的旧副本是非常安全的,基于查找其内容。
我喜欢将这种只读、仅限 Git 的压缩格式称为“冻干”。它清楚地表明,您实际上无法使用这些数据,直到您首先将其恢复为正常的日常格式,“重新水化”它。 (即时文件:只需加水!)
索引和你的工作树
每个文件的已提交副本都保存在数据库中。1 当您签出或切换到某个提交时,Git将文件复制出数据库。这会使它们重新水化并使它们变得有用。
Git 可以在这里停下来,有这两组实体:提交和工作树。提交是只读的,工作树是您完成工作的地方。您将从工作树构建 new 提交。其他版本控制系统就是这样做的……但 Git 没有。相反,Git 在当前(或HEAD)提交和工作树副本之间插入每个文件的第三个副本。
第三个副本——实际上是在中间,在另外两个副本之间,所以可能是 第二个副本——是冻干的格式,但与提交中的副本不同,您可以更改此副本。更准确地说,您可以替换它。这个中间副本存储在 Git 所称的不同地方,即 index 或 staging area(或者,现在很少使用 cache)。2
索引有多个角色——也许是它多个名称的来源——但它的主要角色可以描述为您将在其中构建下一个提交。由于它开始匹配您签出的提交,因此它已经准备好每个文件以进入新的提交。但是假设您以某种方式更改了工作树文件。 如何并不重要,重要的是你改变了它。这个工作树文件还没有在索引中。
您必须在更新的工作树文件上运行git add。这复制文件回到索引,压缩它并把它变成冻干格式。这会将前一个副本从索引中引导出来。现在索引包含更新的文件,并且索引再次准备好进入新的提交。
当您运行 git commit 时,Git 会收集适当的元数据(您的姓名和电子邮件、日志消息、当前提交哈希 ID 等)并制作其索引中文件的最终冻结快照版本。由于这些文件已经是冻结的格式,这个过程非常快,尤其是与其他没有讨厌的“索引”的版本控制系统相比。
当您通过转到另一个分支或“回到过去”到历史提交来提取不同的提交时,Git 必须更新索引以匹配该提交,并更新您的工作树以匹配该索引。这意味着它必须将每个文件从索引复制到工作树到,并在此过程中对其进行补水。同样,正如我们刚刚看到的,git add 必须将文件从工作树复制到到索引,并在此过程中对其进行脱水/冷冻干燥。这对我们的 crlf 行结尾有几个关键的影响,或者更普遍地,对于 smudge 和 clean 过滤器(您也可以使用 .gitattributes 设置)。
1这是 Git 的对象数据库。文件名存储在 Git 所称的 树对象 中,内容在 blob 对象 中,所有这些都由 Git 的 提交对象 绑定在一起。这将各个部分统一在一个大型内容可寻址对象系统中,Git 将其作为一系列提交呈现给您。
2从技术上讲,索引包含的不是每个文件的实际副本,而是一个模式(+x 或-x 呈现为100755 或100644),一个文件名(完整的嵌入斜线:path/to/file.ext)和一个 blob 哈希。 Blob 哈希用于冻结的压缩文件内容:文件数据的冻干形式。当数据与任何现有提交中的任何文件匹配时,blob 哈希与现有提交中的现有文件相同。
不过,只要您不使用git update-index 或git ls-files --stage 了解索引的详细信息,您就可以将其视为冻干格式的额外副本。其他一切都一样。
过滤,包括行尾
如果在提取冻干数据过程中,我们让 Git 用 CRLF 换行符替换仅换行符的行尾,该怎么办?这是“涂抹”过程的一部分:获取一个干净的文件,存储在提交中,现在存储在索引中,然后“弄脏”它以将其作为用户可编辑、用户可用的文件放入工作树中.
如果在将常规文件压缩为冻干格式期间,我们让 Git 将 CRLF 行尾替换为仅换行符行尾,该怎么办?这是“清理”过程的一部分:获取一个存储在用户工作区中的脏文件,然后“清理”它以将其放入索引中,准备好提交。
这就是eol= 设置所做的。他们不会,而且不能不会更改任何现有的已提交文件。这些已经在提交中并且一直被冻结。
这也是你的问题所在。
优化
当您从某个提交 a123456... 切换到某个不同的提交 b789abc... 时,Git 可以:
- 从索引和工作树中删除索引中的每个文件
- 从新提交重新填充整个索引和工作树
这会让你得到你想要签出的提交。但这会非常缓慢,并且会对每个文件的时间戳产生烦人的副作用。
然而,由于 Git 在提交中存储文件的方式,Git 真的很容易判断是否某个名为 path/to/file.ext 或其他名称的文件 在由于提交a1234567...,现在的索引需要不同 - 或完全删除 - 因为b789abc... 中的内容为path/to/file.ext。
如果文件 不需要 必须不同,Git 只会在索引 和 工作树中保留它。如果文件确实必须不同,Git 不会让你从当前提交 a123456... 切换到另一个提交 b789abc...,除非文件的索引和工作树副本是“干净的”,即匹配当前提交。 (这里有很多棘手的极端案例。在Checkout another branch when there are uncommitted changes on the current branch 上查看更多信息。)
这意味着所有三个副本(HEAD 提交、索引和工作树)是否匹配很重要。但是,过滤器和行尾转换的引入使 match 这个词变得棘手。在某些情况下,Git 会查看缓存在索引中的保存的文件系统时间戳数据,3 以确定文件是否“干净”。
文件的真正“清洁度”部分取决于您选择的 EOL 转换类型(如果有)。但是,更改 .gitattributes 文件(或更改涂抹和清洁过滤器)实际上并不是 Git notices,因此如果您更改 EOL 设置,Git 可以认为文件是“干净的”,而实际上它不是,反之亦然。
在您的特定情况下,您已向.gitattributes 添加了一个新设置,该设置表示当文件从索引复制到工作树时,将\n 更改为\r\n;当文件从工作树复制到索引时,将\r\n 更改为\n。 因此,如果 Git 注意到,它会检查这些东西……但 Git 不会注意到。
当一个用户拥有现有存储库时,在提交 H1(对于一些哈希)是master 的提示,并且该用户运行git pull,他的 Git ——我假设用户是男性——通过origin 联系另一个 Git 并获取新的提交。这带来了一个提交,其哈希是 H2(一些其他哈希),它是 origin 的 master 的尖端。然后他的 Git 在哈希 ID H2 上运行 git merge 以将他所做的任何工作/提交与其他工作结合起来。
假设自从 H1 并且 H2 将 H1 作为其父提交后他没有做任何工作,他的 Git 执行快进操作而不是合并,这相当于执行提交 H2 的 git checkout,将他的分支名称 master 向前拖动以指向提交 H2。所以现在 Git 采用了这种优化。文件.gitattributes 有一个不同的blob 散列,他的.gitattributes 的索引和工作树副本必须被替换。由于 Git (正确地)认为这些是干净的,因此它们被替换了。然而,他的 Git 的 readme.txt 索引副本与新提交 H2 具有 same blob 散列。所以他的 Git 不会 触及他的 readme.txt 的索引或工作树副本。
结果就是您所看到的:工作树副本继续具有之前的行结尾。
如果两个提交H1和H2对于文件readme.txt有不同的内容——注意这意味着不同的cleaned 内容——然后他的 Git 的快进操作将看到他在 Git 的索引和工作树中的 readme.txt 副本,do 需要被替换。只要他的 Git 认为它们是“干净的”,他的 Git 就会替换它们。这意味着将提交的readme.txt 复制到索引中,然后将索引副本复制到他的工作树:此复制将遵循新的eol=crlf 操作,并将用 CRLF 结尾替换仅换行的“干净冻结文件”数据工作树数据。
如果用户随后编辑他的工作树readme.txt,他(或至少他的编辑)将看到这些 CRLF 结尾。他的编辑对他们做什么取决于他的编辑。 (我强迫我的编辑给我看,然后我把它们去掉,因为我不喜欢它们,我不在乎你想让我拥有它们。:-))如果他更新文件并运行@ 987654374@,他的git add 将去掉那些CRLF 结尾,用仅换行符结尾替换它们,文件应该是这样的;这就是索引中的内容,因此下一次提交中的内容。
3因此很少使用索引名称cache。然而,在现代 Git 中,术语 cache 主要是指索引的内存副本,从索引文件加载,然后通过您正在运行的任何 Git 命令进行操作。