首页 > 解决方案 > 如何动态更改以 Git 存储库结尾的行

问题描述

我有一个在 Windows 上签出的 Git 存储库。当我安装 Git 时,我选择了“使用 CRLF 作为本地行尾”,因此克隆了 repo 并检查了所有以 CRLF 作为行尾的文件。这给了我一些问题,因为我有很多 bash 脚本并且我在 WSL 中使用 Ubuntu bash。我已经编辑了全局.gitconfig文件以将行尾更改为与远程仓库一致。但是我的文件仍然以 CRLF 行结尾。在不删除 repo 目录并重新克隆的情况下将所有这些更改回其原始行结尾的最佳方法是什么?

标签: git

解决方案


但是我的文件仍然以 CRLF 行结尾。

如果存储库内的提交中的文件具有 CRLF 行结尾,则该文件的该版本将永远保持这种状态。任何现有提交的任何部分都不能更改。

如果存储库内的提交中的文件具有仅 LF 行结尾,则该文件的该版本将永远保持这种状态。但是,当您提取该文件时,您可以选择您希望 Git 在您的工作树中放置的结尾。

如果您已经提取了文件,则 Git 已经完成了转换。Git 现在认为一切都很好,即使您刚刚更改了转换设置。

因此,如果您更改转换设置,您必须强制 Git 重新提取文件。在所有版本的 Git 中一致地执行此操作的最简单方法是从工作树中删除文件,然后运行​​. 因为文件从工作树中消失了,Git 将被迫再次提取它。这次将应用更新的 EOL 转换。git checkout -- path/to/file

(另一种方法是更改​​文件,然后运行相同的git checkout,或在 Git 2.23 或更高版本中使用git restore。通过告诉 Git Git 应该丢弃您的文件版本,并且 Git 看到您的文件版本是确实“错误”,因为它与索引副本不匹配,因为您更改了它,Git 将被迫重新提取索引副本。)

对于您的情况,这可能就足够了,也可能不会。如果没有,请继续阅读。

关于 Git 的行尾转换的知识

我自己坚信“永远不要使用 Windows,这样你就永远不需要让你的版本控制系统用行结尾弄得一团糟”的哲学,但如果你在其他阵营并一些事情,有一些事情需要知道想让 Git 弄脏行尾。其中最重要的是:Git 中存储的内容,以及您在处理Git 获得的文件时使用的内容,不一定是同一回事。

要了解它是如何工作的,请记住 Git 存储提交而不是直接存储文件。这些提交中的文件来自Git 的index,而不是来自您的工作树。文件的索引副本的格式与 Git 用于永久冻结提交的内部格式相同:数据是预压缩的。因此,索引中每个文件的副本已经与您在工作树中使用的副本有很大不同,因为工作树中的副本不是 Git blob 对象,通常不是 zlib 压缩的。

Git 在将提交复制到您的工作树之前将其读入索引。在文件上运行git add会压缩和对文件进行 blob 化,以便将其存储在 Git 的索引中。 就在这个转换点,当 Git 压缩和 Git 化文件(git add)或解压和解压缩文件(git checkout-index或等效文件)时,Git 插入额外的转换操作是微不足道的。

因此,Git 在这一点上做了它的事情。Git 可以做的事情——唯一直接内置的事情——是,在离开索引时,Git 可以将\n-only 行尾替换为\r\n行尾,在进入索引的路上,Git 可以将\r\n行尾替换为\n- 仅行尾。

换句话说,您可以安排 Git在存储文件之前丢弃一些回车,并在提取文件时添加一些回车。如果您同时执行这两项操作,您将在工作树中获得 CRLF 行结尾,并在提交中获得仅换行符的行结尾。

如果愿意,您可以让 Git 只执行其中一项:特别是,通过crlf=input设置,您可以告诉 Git:在工作树到索引的复制操作中只执行一次转换。

如果您在提取文件时选择让 Git 进行转换,则此处唯一可用的转换是将 LF-only 转换为 CRLF。您不能将 CRLF 结尾转换为 LF-only 结尾。如果 in-Git提交的文件具有 CRLF 结尾,则工作树中提取的文件将具有 CRLF 结尾。

同样,这些转换中的每一个都只发生在一个方向上:

  • 索引 → 工作树:可选,替换\n\r\n
  • 工作树→索引:可选,替换\r\n\n

您选择的core.autocrlf.gitattributes指令是:

  • text, -text, 和/或core.autocrlf: 哪些文件
  • eol=...和/或core.eol:得到哪些治疗
  • crlf=input: 在哪个操作上

一旦文件被处理和转换——通过将其复制到索引或从索引中复制——Git 通过从操作系统获取关键数据(文件的大小和其他lstat系统调用值)将索引的副本标记为“匹配工作树的副本”。由于不同的操作系统以不同的粒度存储不同的数据,因此此处的精确细节会有所不同。

强制进行新转换的简单方法是删除文件的一个或另一个副本:rm filegit rm --cached file分别破坏工作树或索引副本,因此现在 a git checkout -- fileorgit add file将创建一个新副本。

当你运行时,文件索引git commit副本中的任何字节都会进入 Git 所做的新提交。这个新的提交现在一直被冻结:索引中的字节现在永远在提交中(或者只要提交本身继续存在)。没有任何人可以改变它们。

上述后果

上面的意思是,如果您确实计划让您的版本控制系统(即 Git)乱用行尾,那么行尾您可以——因此可能应该——<em>总是用于每个索引副本,因此每个文本文件的每个提交的副本都是 LF-only 行结尾。这些总是可以通过适当的.gitattributes设置转换为工作树文件中的 CRLF 结尾core.*。如果您已经完成了这样的转换,那么该工作树文件可以在git add操作中转换回仅 LF 行尾。

如果您确实提交了一个带有 CRLF 行结尾的文件,那么该提交将一直以这种方式卡住,并且每次提取该提交都会为您提供一个具有 CRLF 行结尾的工作树副本,因为 Git 没有内置索引→ 将改变这一点的工作树操作。Git 唯一内置的 CRLF 到 LF 操作只能在另一个方向上工作,即 index ← work-tree。

如果您想进行新的改进提交,其中该文件的已提交副本具有仅 LF 行结尾,您有以下两个选项:

  1. 确保您的索引←工作树设置这样做,然后强制Git添加文件(例如,在工作树中更改它或git rm --cached在索引副本上使用git add它);或者
  2. 使用任何将工作树副本更改为仅具有 LF 行尾的命令,例如,dos2unix在其上运行或类似的,然后git add是它。

方法 2 的优点是您可以立即看到效果(在您的工作树文件中)并且很难出错。方法 1 的问题在于您不到它,而且很容易完全弄错:例如,您可能不小心使用git rm了而不是git rm --cached,它会同时删除索引工作树副本。


推荐阅读