在 git 拉动时将一个文件从 LF 更新到 CR/LF 的最简单方法?
Easiest way to update one file from LF to CR/LF on git pull?
有很多关于 EOL 转换的问题,但我找不到针对这种特殊情况的答案:我在 Unix 行尾有一个 readme.txt。此文本文件是部署在用户机器上的存储库的一部分,并使用简单的 git pull
.
进行更新
我们意识到这个文件应该总是在 CR/LF 中,因此想从 LF 更改它(其他文件都可以)。更新 .gitattributes
为
readme.txt eol=crlf
有效,但前提是存储库是 克隆的 。如果我想更新它,我必须做
git pull
git rm --cached readme.txt
git reset --hard
即我不能在每个用户的机器上做的事情。有办法解决这个问题吗? readme.txt 的更新对这里有帮助吗?
我一点都不清楚为什么你关心每个用户work-tree中出现的内容。使用 Git 时最重要的是每个 commit 中出现的内容。不过,让我们按要求回答问题:
Would an update to readme.txt help here?
是的,会的。 (这个答案的其余部分都是可选阅读,但可能是个好主意。)
为什么会这样
eol=crlf
属性告诉 Git 当文件被复制时 从 索引 到 用户的work-tree、Git 应该在 frozen 格式副本 中找到 \n
-only 行结尾,并用 \r\n
行结尾替换它们用户的 work-tree.
并不是说你说的错,而是你说的也不对。 :-) 事实上,它是不完整的。要真正理解这一点,需要理解提交、索引和用户的 work-tree 是如何相互作用的。
提交
请记住,Git 最基本的目的——它存在的根本原因——是存储提交。每个提交都包含每个文件的完整快照。更准确地说,提交包含该提交中每个文件的完整快照。这么说,这听起来有些多余——但我们的想法是,这相当于那些文件当时存在的 archive。每个提交 可能 有一组完全不同的文件,但这通常不是我们 使用 Git.
的方式
你可以天真地用 rar
或 tar
或 zip
之类的存档器构建这样的东西,每次你想提交时,只需一个新的完整档案。每个这样的档案都将完全独立于以前的每个档案。这使他们以后很容易取回。缺点是这些会占用很多space,而且容易忘记。
我们首先观察到每次提交都倾向于 re-use 来自 previous 存档的大部分文件。如果我们不制作 independent 存档,而是尽可能制作一个 re-used 以前的存档呢?事实上,Git 就是这样做的。
为了使这项工作 和 更快,Git 添加了更多技巧。最主要的是每个文件的数据——它的内容——都存储在压缩的 read-only、Git-only 格式中,这使得查看 Git 是否已经有该文件的副本变得非常快.因为它 是 read-only——事实上,every 提交的每一部分都是 read-only——所以 re-use 文件的旧副本,基于查找其内容。
我喜欢把这个叫做read-only,Git-only,压缩格式"freeze-dried"。它清楚地表明,您实际上无法 使用 此数据,直到您首先将其恢复为正常的日常格式, "rehydrating" 它。 (即时文件:加水即可!)
索引和你的work-tree
每个文件的提交副本都保存在数据库中。1当您签出或时切换到 一些提交,Git 将文件复制出数据库。这会使它们重新水化并发挥作用。
Git 可以到此为止,这两组实体:提交和 work-tree。提交是 read-only,work-tree 是您完成工作的地方。您将从 work-tree 构建 new 提交。其他版本控制系统就是这样做的……但 Git 不会。相反,Git 在当前(或 HEAD
)提交和 work-tree 副本之间插入每个文件的 third 副本。
第三个副本——实际上在中间,在另外两个之间,所以可能是第二个副本——在freeze-dried 格式,但与提交中的副本不同,您可以更改此副本。更准确地说,您可以替换它。这个中间副本存储在 Git 调用的地方,不同的是 index 或 staging area (或者,现在很少, 缓存).2
索引有多个角色——也许是它多个名字的来源——但它的主要角色可以描述为你构建下一个提交的地方制作。由于它开始与您签出的提交相匹配,因此它已经准备好每个文件以进入新的提交。但是假设您以某种方式更改了 work-tree 文件。 如何并不重要,重要的是你改变了它。此 work-tree 文件 尚未 在索引中。
您必须 运行 git add
更新 work-tree 文件。此 将文件 复制 回 索引,压缩它并将其转换为 freeze-dried 格式。这会将先前的副本从索引中引导出来。现在索引包含更新的文件,索引是准备好进行新的提交。
当您 运行 git commit
、Git 收集适当的元数据(您的姓名和电子邮件、日志消息、当前提交哈希 ID 等)并进行最终冻结索引中文件的快照版本。由于这些文件已经处于冻结 格式 ,这个过程非常快,特别是与其他没有令人讨厌的 "index" 的版本控制系统相比。
当您通过转到另一个分支或 "going back in time" 到历史提交来提取不同的提交时,Git 必须更新索引以匹配提交,并更新您的 work-tree以匹配索引。这意味着它必须将每个文件 从 索引复制到 到 work-tree,并沿途重新水化。同样,正如我们刚刚看到的,git add
必须从 复制一个文件 到 work-tree, 到 索引,脱水/freeze-drying 一路走来。这对我们的 crlf
行结尾有几个关键影响,或者更一般地说,对 smudge 和 clean 过滤器(您还设置了使用 .gitattributes
).
1这是Git的对象数据库。文件名存储在 Git 调用的 树对象 中,内容存储在 blob 对象 中,全部由 Git 的 提交对象 。这将各个部分统一在一个大 content-addressable 对象系统中,Git 以一系列提交的形式呈现给您。
2从技术上讲,索引不包含每个文件的实际副本,而是包含模式(+x
或 -x
呈现为 100755
或 100644
),一个文件名(完整的嵌入式斜杠:path/to/file.ext
),和一个 blob 哈希 。 blob 哈希用于冻结的压缩文件内容:文件数据的 freeze-dried 形式。当数据与任何现有提交中的任何文件的数据匹配时,blob 哈希与现有提交中现有文件的哈希相同。
不过,只要您不使用 git update-index
或 git ls-files --stage
了解索引的详细信息,您就可以将其视为额外的副本,在 freeze-dried 格式。其他一切都一样。
过滤,包括行尾
如果在 提取 freeze-dried 数据 过程中,我们 Git 将 newline-only 行结尾替换为 CRLF 行结尾怎么办?这是 "smudging" 过程的一部分:获取一个干净的文件,存储在提交中,现在在索引中,"dirtying it up" 将其放入 work-tree,作为 user-editable, user-usable 文件.
如果在 将常规文件压缩为 freeze-dried 格式 期间,我们 Git 将 CRLF 行结尾替换为 newline-only 行会怎样结局?这是"cleaning"流程的一部分:取一个脏文件,存放在用户的工作区,"clean it"将其放入索引,准备提交。
这就是 eol=
设置 所做的。它们不会,而且 不能 更改任何现有的已提交文件。这些已经在提交中并且一直被冻结。
这也是您的问题所在。
优化
当您从某个提交 a123456...
切换到某个不同的提交 b789abc...
、Git 可以:
- 从索引中删除索引中的每个文件并 work-tree
- re-populate 整个索引和 work-tree 来自新的提交
这会让你得到你想要签出的提交。但这会 非常 缓慢并且会对每个文件的 时间戳 产生恼人的副作用。
但是,由于 Git 在提交中存储文件的方式,非常容易 Git 判断某个文件是否名为 [=26] =] 或其他什么, 现在在索引中 因为提交 a1234567...
需要 不同 — 或者完全删除 — 因为path/to/file.ext
.
b789abc...
中的内容
如果文件不需要不同,Git就不管它了,在索引和[=244=中] work-tree。如果文件 必须不同,Git 不会让您从当前提交 a123456...
切换到另一个提交 b789abc...
除非文件的索引和 work-tree 副本是 "clean",即匹配当前提交。 (这里有很多棘手的角落案例。在 Checkout another branch when there are uncommitted changes on the current branch 上查看更多内容。)
这意味着所有三个副本(HEAD
提交、索引和 work-tree)是否匹配很重要。过滤器和 end-of-line 转换的引入使 匹配 这个词变得棘手。 Git 将查看保存在索引中的文件系统时间戳数据,3 以决定文件是否为 "clean",在某些情况下。
文件的真实 "clean-ness" 部分取决于您选择了哪种 EOL 转换(如果有)。但是,更改 .gitattributes
文件(或更改涂抹和清洁过滤器)并不是 Git 实际上 通知 ,因此如果您更改 EOL 设置,Git 可以认为文件是 "clean"不是,反之亦然。
在您的特定情况下,您向 .gitattributes
添加了一个新设置,表示 当文件从索引复制到 work-tree 时,更改 \n
至 \r\n
;当文件从 work-tree 复制到索引时,将 \r\n
更改为 \n
。 所以如果 Git 注意到,它会检查这些东西......但是Git没注意到。
当拥有现有存储库的用户在提交 H1(对于某些哈希)时,即 master
的提示,并且该用户运行s git pull
,他的 Git——我假设用户是男性——在 origin
联系另一个 Git 并获取新的提交。这带来了一个提交,其哈希是 H2(一些其他哈希),这是 origin
的 master
的尖端。他的 Git 然后 运行s git merge
在散列 ID H2 上将他已经完成的任何 work/commits 与其他工作结合起来。
假设自从 H1 和 H2 以来他没有做过任何工作,他的 H1父提交,他的 Git 做了一个 fast-forward 操作而不是合并,这相当于做了一个 git checkout
的提交 H2 拖拽他的分支名称master
转发指向提交 H2。所以现在 Git 采用了该优化。文件 .gitattributes
有一个不同的 blob 哈希 并且他的索引和 .gitattributes
的 work-tree 副本必须被替换。因为 Git 认为(正确地)这些是干净的,所以它们被替换了。然而,他的 Git 的 readme.txt
索引副本与新提交 H2 具有 相同的 blob 散列。所以他的 Git 不会 触及他的索引或 readme.txt
.
的 work-tree 副本
结果如您所见:work-tree 副本继续使用之前的行结尾。
如果两个提交H1和H2对文件[=55有不同的内容 =]——注意这意味着不同的 cleaned 内容——那么他的 Git 的 fast-forward 操作将看到他的 readme.txt
的副本,在他的Git的索引和他的work-tree,do需要替换。只要他的 Git 认为他们是 "clean",他的 Git 就会替换他们。这意味着将提交的 readme.txt
复制到索引中,然后将索引副本复制到他的 work-tree 中:此复制将服从新的 eol=crlf
操作并将替换 newline-only "clean frozen file" 数据与 CRLF-ending work-tree 数据。
如果用户随后编辑他的 work-tree readme.txt
,他——或者他的编辑,至少——将看到这些 CRLF 结尾。他的编辑如何处理它们取决于他的编辑。 (我强迫我的编辑给我看,然后我把它们去掉,因为我不喜欢它们,我不在乎你想让我拥有它们。:-))如果他更新了文件并且 运行s git add
,他的 git add
将去掉那些 CRLF 结尾,用 newline-only 结尾替换它们,文件应该这样;这就是将进入索引的内容,因此将是下一次提交的内容。
3因此索引的 rarely-used 名称 cache。在现代 Git 中,术语 cache 主要是指索引的 in-memory 副本,不过,从索引文件加载然后由任何 Git 命令你 运行ning.
有很多关于 EOL 转换的问题,但我找不到针对这种特殊情况的答案:我在 Unix 行尾有一个 readme.txt。此文本文件是部署在用户机器上的存储库的一部分,并使用简单的 git pull
.
我们意识到这个文件应该总是在 CR/LF 中,因此想从 LF 更改它(其他文件都可以)。更新 .gitattributes
为
readme.txt eol=crlf
有效,但前提是存储库是 克隆的 。如果我想更新它,我必须做
git pull
git rm --cached readme.txt
git reset --hard
即我不能在每个用户的机器上做的事情。有办法解决这个问题吗? readme.txt 的更新对这里有帮助吗?
我一点都不清楚为什么你关心每个用户work-tree中出现的内容。使用 Git 时最重要的是每个 commit 中出现的内容。不过,让我们按要求回答问题:
Would an update to readme.txt help here?
是的,会的。 (这个答案的其余部分都是可选阅读,但可能是个好主意。)
为什么会这样
eol=crlf
属性告诉 Git 当文件被复制时 从 索引 到 用户的work-tree、Git 应该在 frozen 格式副本 中找到 \n
-only 行结尾,并用 \r\n
行结尾替换它们用户的 work-tree.
并不是说你说的错,而是你说的也不对。 :-) 事实上,它是不完整的。要真正理解这一点,需要理解提交、索引和用户的 work-tree 是如何相互作用的。
提交
请记住,Git 最基本的目的——它存在的根本原因——是存储提交。每个提交都包含每个文件的完整快照。更准确地说,提交包含该提交中每个文件的完整快照。这么说,这听起来有些多余——但我们的想法是,这相当于那些文件当时存在的 archive。每个提交 可能 有一组完全不同的文件,但这通常不是我们 使用 Git.
的方式你可以天真地用 rar
或 tar
或 zip
之类的存档器构建这样的东西,每次你想提交时,只需一个新的完整档案。每个这样的档案都将完全独立于以前的每个档案。这使他们以后很容易取回。缺点是这些会占用很多space,而且容易忘记。
我们首先观察到每次提交都倾向于 re-use 来自 previous 存档的大部分文件。如果我们不制作 independent 存档,而是尽可能制作一个 re-used 以前的存档呢?事实上,Git 就是这样做的。
为了使这项工作 和 更快,Git 添加了更多技巧。最主要的是每个文件的数据——它的内容——都存储在压缩的 read-only、Git-only 格式中,这使得查看 Git 是否已经有该文件的副本变得非常快.因为它 是 read-only——事实上,every 提交的每一部分都是 read-only——所以 re-use 文件的旧副本,基于查找其内容。
我喜欢把这个叫做read-only,Git-only,压缩格式"freeze-dried"。它清楚地表明,您实际上无法 使用 此数据,直到您首先将其恢复为正常的日常格式, "rehydrating" 它。 (即时文件:加水即可!)
索引和你的work-tree
每个文件的提交副本都保存在数据库中。1当您签出或时切换到 一些提交,Git 将文件复制出数据库。这会使它们重新水化并发挥作用。
Git 可以到此为止,这两组实体:提交和 work-tree。提交是 read-only,work-tree 是您完成工作的地方。您将从 work-tree 构建 new 提交。其他版本控制系统就是这样做的……但 Git 不会。相反,Git 在当前(或 HEAD
)提交和 work-tree 副本之间插入每个文件的 third 副本。
第三个副本——实际上在中间,在另外两个之间,所以可能是第二个副本——在freeze-dried 格式,但与提交中的副本不同,您可以更改此副本。更准确地说,您可以替换它。这个中间副本存储在 Git 调用的地方,不同的是 index 或 staging area (或者,现在很少, 缓存).2
索引有多个角色——也许是它多个名字的来源——但它的主要角色可以描述为你构建下一个提交的地方制作。由于它开始与您签出的提交相匹配,因此它已经准备好每个文件以进入新的提交。但是假设您以某种方式更改了 work-tree 文件。 如何并不重要,重要的是你改变了它。此 work-tree 文件 尚未 在索引中。
您必须 运行 git add
更新 work-tree 文件。此 将文件 复制 回 索引,压缩它并将其转换为 freeze-dried 格式。这会将先前的副本从索引中引导出来。现在索引包含更新的文件,索引是准备好进行新的提交。
当您 运行 git commit
、Git 收集适当的元数据(您的姓名和电子邮件、日志消息、当前提交哈希 ID 等)并进行最终冻结索引中文件的快照版本。由于这些文件已经处于冻结 格式 ,这个过程非常快,特别是与其他没有令人讨厌的 "index" 的版本控制系统相比。
当您通过转到另一个分支或 "going back in time" 到历史提交来提取不同的提交时,Git 必须更新索引以匹配提交,并更新您的 work-tree以匹配索引。这意味着它必须将每个文件 从 索引复制到 到 work-tree,并沿途重新水化。同样,正如我们刚刚看到的,git add
必须从 复制一个文件 到 work-tree, 到 索引,脱水/freeze-drying 一路走来。这对我们的 crlf
行结尾有几个关键影响,或者更一般地说,对 smudge 和 clean 过滤器(您还设置了使用 .gitattributes
).
1这是Git的对象数据库。文件名存储在 Git 调用的 树对象 中,内容存储在 blob 对象 中,全部由 Git 的 提交对象 。这将各个部分统一在一个大 content-addressable 对象系统中,Git 以一系列提交的形式呈现给您。
2从技术上讲,索引不包含每个文件的实际副本,而是包含模式(+x
或 -x
呈现为 100755
或 100644
),一个文件名(完整的嵌入式斜杠:path/to/file.ext
),和一个 blob 哈希 。 blob 哈希用于冻结的压缩文件内容:文件数据的 freeze-dried 形式。当数据与任何现有提交中的任何文件的数据匹配时,blob 哈希与现有提交中现有文件的哈希相同。
不过,只要您不使用 git update-index
或 git ls-files --stage
了解索引的详细信息,您就可以将其视为额外的副本,在 freeze-dried 格式。其他一切都一样。
过滤,包括行尾
如果在 提取 freeze-dried 数据 过程中,我们 Git 将 newline-only 行结尾替换为 CRLF 行结尾怎么办?这是 "smudging" 过程的一部分:获取一个干净的文件,存储在提交中,现在在索引中,"dirtying it up" 将其放入 work-tree,作为 user-editable, user-usable 文件.
如果在 将常规文件压缩为 freeze-dried 格式 期间,我们 Git 将 CRLF 行结尾替换为 newline-only 行会怎样结局?这是"cleaning"流程的一部分:取一个脏文件,存放在用户的工作区,"clean it"将其放入索引,准备提交。
这就是 eol=
设置 所做的。它们不会,而且 不能 更改任何现有的已提交文件。这些已经在提交中并且一直被冻结。
这也是您的问题所在。
优化
当您从某个提交 a123456...
切换到某个不同的提交 b789abc...
、Git 可以:
- 从索引中删除索引中的每个文件并 work-tree
- re-populate 整个索引和 work-tree 来自新的提交
这会让你得到你想要签出的提交。但这会 非常 缓慢并且会对每个文件的 时间戳 产生恼人的副作用。
但是,由于 Git 在提交中存储文件的方式,非常容易 Git 判断某个文件是否名为 [=26] =] 或其他什么, 现在在索引中 因为提交 a1234567...
需要 不同 — 或者完全删除 — 因为path/to/file.ext
.
b789abc...
中的内容
如果文件不需要不同,Git就不管它了,在索引和[=244=中] work-tree。如果文件 必须不同,Git 不会让您从当前提交 a123456...
切换到另一个提交 b789abc...
除非文件的索引和 work-tree 副本是 "clean",即匹配当前提交。 (这里有很多棘手的角落案例。在 Checkout another branch when there are uncommitted changes on the current branch 上查看更多内容。)
这意味着所有三个副本(HEAD
提交、索引和 work-tree)是否匹配很重要。过滤器和 end-of-line 转换的引入使 匹配 这个词变得棘手。 Git 将查看保存在索引中的文件系统时间戳数据,3 以决定文件是否为 "clean",在某些情况下。
文件的真实 "clean-ness" 部分取决于您选择了哪种 EOL 转换(如果有)。但是,更改 .gitattributes
文件(或更改涂抹和清洁过滤器)并不是 Git 实际上 通知 ,因此如果您更改 EOL 设置,Git 可以认为文件是 "clean"不是,反之亦然。
在您的特定情况下,您向 .gitattributes
添加了一个新设置,表示 当文件从索引复制到 work-tree 时,更改 \n
至 \r\n
;当文件从 work-tree 复制到索引时,将 \r\n
更改为 \n
。 所以如果 Git 注意到,它会检查这些东西......但是Git没注意到。
当拥有现有存储库的用户在提交 H1(对于某些哈希)时,即 master
的提示,并且该用户运行s git pull
,他的 Git——我假设用户是男性——在 origin
联系另一个 Git 并获取新的提交。这带来了一个提交,其哈希是 H2(一些其他哈希),这是 origin
的 master
的尖端。他的 Git 然后 运行s git merge
在散列 ID H2 上将他已经完成的任何 work/commits 与其他工作结合起来。
假设自从 H1 和 H2 以来他没有做过任何工作,他的 H1父提交,他的 Git 做了一个 fast-forward 操作而不是合并,这相当于做了一个 git checkout
的提交 H2 拖拽他的分支名称master
转发指向提交 H2。所以现在 Git 采用了该优化。文件 .gitattributes
有一个不同的 blob 哈希 并且他的索引和 .gitattributes
的 work-tree 副本必须被替换。因为 Git 认为(正确地)这些是干净的,所以它们被替换了。然而,他的 Git 的 readme.txt
索引副本与新提交 H2 具有 相同的 blob 散列。所以他的 Git 不会 触及他的索引或 readme.txt
.
结果如您所见:work-tree 副本继续使用之前的行结尾。
如果两个提交H1和H2对文件[=55有不同的内容 =]——注意这意味着不同的 cleaned 内容——那么他的 Git 的 fast-forward 操作将看到他的 readme.txt
的副本,在他的Git的索引和他的work-tree,do需要替换。只要他的 Git 认为他们是 "clean",他的 Git 就会替换他们。这意味着将提交的 readme.txt
复制到索引中,然后将索引副本复制到他的 work-tree 中:此复制将服从新的 eol=crlf
操作并将替换 newline-only "clean frozen file" 数据与 CRLF-ending work-tree 数据。
如果用户随后编辑他的 work-tree readme.txt
,他——或者他的编辑,至少——将看到这些 CRLF 结尾。他的编辑如何处理它们取决于他的编辑。 (我强迫我的编辑给我看,然后我把它们去掉,因为我不喜欢它们,我不在乎你想让我拥有它们。:-))如果他更新了文件并且 运行s git add
,他的 git add
将去掉那些 CRLF 结尾,用 newline-only 结尾替换它们,文件应该这样;这就是将进入索引的内容,因此将是下一次提交的内容。
3因此索引的 rarely-used 名称 cache。在现代 Git 中,术语 cache 主要是指索引的 in-memory 副本,不过,从索引文件加载然后由任何 Git 命令你 运行ning.