首页 > 解决方案 > git checkout 分支最终修改文件

问题描述

我有一台 Mac,我在其上克隆了一个 repo。当我签出任何分支时,.json我最终显示为“已修改”的文件。

这是我尝试过的事情的清单。

  1. git clone <blah>.git
  2. git checkout release
  3. git checkout -- $(git ls-files -m)

文件仍显示为已修改:

me@box> cat ~/.gitattributes
text=auto
-crlf

me@box> cat .gitconfig
[core]
    autocrlf = true

我也尝试autocrlfautoand false

有没有什么办法解决这一问题?

标签: git

解决方案


TL;博士

可能希望.gitattributes(not ~/.gitattributes) 包含该行* -text*.json -text. 您可能希望从或或或中删除任何autocrlf = true设置。您可能希望更好地控制其他文件,具体取决于您和其他人在哪些系统上使用这些存储库的方式。.git/config$XDG_CONFIG_HOME/git/config$HOME/.config/git/config$HOME/.gitconfig

一方面,这些行是错误的:

text=auto
-crlf

中的条目.gitattributes应采用文件名或模式的形式,后跟空格(通常是制表符,但空格也可以),后跟一个或多个属性。在这里,文件名/模式部分丢失了,尽管 Git 不这么看:这告诉 Git 要做的是不对所有名为 的文件text=auto应用属性,并且不对所有名为-crlf. 由于可能没有具有这些有趣名称的文件,因此这不会将属性应用于任何文件 - 因此它没有效果。

(您可能想要* text=auto和/或* -crlf,但见下文。)

同样(但也许不是问题),这可能是错误的地方:

cat ~/.gitattributes

Git 在几个地方寻找属性文件。最重要的是.gitattributes在存储库工作树的顶层,即.gitattributes, 不是~/.gitattributes。您可以让 Git 咨询core.attributesFile,您可以将其设置为~/.gitattributes. 现代 Git 会查找$XDG_CONFIG_HOME/git/attributesor $HOME/.config/git/attributesifcore.attributesFile未设置,因此除非您配置了全局core.attributesFile设置,否则它不会查找$HOME/.gitattributes.

意见:core.autocrlf不是一个好主意

cat .gitconfig
[core]
    autocrlf = true

如果这是来自您的主目录,这可能是问题的根源。

如文档中所述,设置core.autocrlf为以true这种方式运行:git config

将此变量设置为“true”与将text 所有文件的属性设置为“auto”和core.eol“crlf”相同。如果您想在工作目录中使用 CRLF 行结尾并且存储库具有 LF 行结尾,请设置为 true。该变量可以设置为input,在这种情况下不执行输出转换。

一般来说,我不喜欢text=auto,因为这意味着 Git 必须猜测您的文件是否为文本。Git会猜错吗?有时,是的,它会的。如果你在 Linux 上,行尾转换默认为什么都不,这是无害的,但在 Windows 和 MacOS 上,它就不是那么无害了。在这里,一个.gitattributes文件特别列出了哪些文件的行尾是有用的。

由于您提到core.eol设置core.autocrlffalse

为设置了 text 属性的文件设置在工作目录中使用的行结束类型(当core.autocrlf为 false 时)。替代方案是lfcrlfnative,它们使用平台的本机行尾。默认值为原生。有关行尾转换的更多信息,请参阅 gitattributes(5)

我也尝试autocrlfautoand false

唯一有效的设置是truefalseinput。好吧,从技术上讲,将其分别设置为10-1也可以,因为如果它与这些单词之一不匹配,Git 会尝试将其转换为整数。将其设置为文字字符串auto使 Git 将其视为一个整数,它转换为 value 0,因此意味着false. 这是默认设置,在这种情况下,只要文件中没有覆盖,core.eol就会发挥作用。.gitattributes

除了一些没有在任何地方正确描述的关键项目外,其余大部分要了解的内容都隐藏在gitattributes文档中。这些需要明确定义 Git 的indexwork-tree

提交、索引和工作树

首先要意识到的是,任何已提交的文件都被冻结在该文件的已提交形式中的任何数据中,在该特定提交中永远如此。也就是说,每个提交都包含每个文件的快照——嗯,那个提交中的每个文件,但这似乎有点多余——以当谁制作快照时它在索引中的形式,制作快照(通过运行git commit)。

如果提交的文件有一行以 CR-then-LF 结尾,然后是第二行以 CR-without-LF 结尾,然后是第三行以 LF-without-CR 结尾,则该文件的提交版本总是有那种形式。对此,任何人都无能为力。如果你有那个提交,你就有那个文件,这三个行结尾的顺序是这样的。您可以选择是否查看;但无论您做什么或不做什么,该文件都以该形式存在于该提交中。

可是等等!签出某个文件的行为——或者一个充满文件的提交——并不意味着将该文件按原样放入工作树中。让我们考虑一个文件,例如x.json. 首先,该文件必须通过您的index。Git 的索引是一种特殊的数据结构(通常存储在一个大的平面文件中,尽管有时有一些优化会使用多个文件:平面文件有各种索引槽,部分通过名称找到x.json,以及与内部的链接数据,实际上存储在其他地方:索引副本实际上只是指向真实数据的指针)。该索引还有两个名称,也许反映了它在 Git 中的重要性,或者也许是名称索引留下一些不足之处:它也称为staging area,有时也称为cache,具体取决于 Git 的谁/哪个部分正在执行调用。

无论如何x.json,正如我们刚刚提到的,提交的内部是冻结的,但也是一种特殊的、压缩的(有时是高度压缩的)仅 Git 格式。Git 首先将其复制x.json到您的索引中,解冻文件,但将其保留为特殊的仅 Git 格式。由于索引中的副本当前与提交中的副本匹配,因此它具有相同的行尾。但由于它没有被冻结,如果你愿意,你可以改变行尾。在我们到达那里之前,让我们看一下结帐的最后一步x.json

仅 Git 文件对 Git 以外的任何东西都无用。因此,Git 将解冻的、仅限 Git 的文件从索引复制到您的work-tree,创建一个实际的普通格式x.json文件。正是这最后一步完成了行尾操作的第一遍,基于:

  • 该文件是否已被检测为“文本”——非文本文件从未以这种方式进行操作——以及
  • 文件中的eol设置(如果有)或由or.gitattributes暗示的设置,或多或少按该顺序。core.eolcore.autocrlf

当然,您也可以从工作树复制到索引。此时 Git 将再次进行行尾操作,基于这两个相同的项目,加上input设置,它告诉 Git 进行行尾转换,即使输出传递没有。

转换

实际上有两种可能的转换。第一个也是最常见的,与回车和换行有关。第二个是自提交 107642fe2661(首次出现在 Git 2.18.0 中)以来的新内容,与 Unicode 编码有关。

CRLF 转换

从索引复制到工作树时,如果您告诉 Git 这样做,Git 会将仅 LF 行结尾替换为 CRLF 行结尾。它不会用 LF 代替 CR-LF;它在这里只做 LF-to-CRLF。

从工作树复制到索引时,如果您告诉 Git 这样做,Git 会将 CRLF 行结尾替换为仅 LF 行结尾。它不会用 CRLF 代替 LF;它在这里只做 CRLF-to-LF。现代 Git 有一些额外的魔力:如果文件的索引副本有任何回车(\r^M字符),Git 会禁止转换,除非它正在执行“重新规范化”操作(git add --renormalize,加上带有重新规范化标志的合并或樱桃选择打开)。

Unicode 转换

正如我所提到的,这些在 Git 2.18 中是新的(对我来说仍然是新的)。.gitattributesGit 现在将通过条目将 UTF-16 文件重新编码为 UTF-8,反之亦然,如果您告诉它这样做的话。与行尾转换一样,这些是定向的:git add,它从工作树复制到索引,准备文件以存储在 Git 中,并将转换UTF-8,而git checkoutor git checkout-index(或各种其他情况,如git reset --hard),从工作树的索引,将从UTF-8 转换。

根据任何iconv --list印刷品,可能会提供额外的重新编码。这也在gitattributes 文档中有所描述。

更改text=和转换绕过问题

Git 的默认设置是猜测某个文件是否为文本。如果文件文本,它会被转换;如果不是,Git 将其视为神圣不可侵犯:索引中的任何内容都进入工作树,工作树中的任何内容都进入索引。所以* -text告诉 Git:永远不要碰任何文件,并且没有问题。但是如果 Git 之前在想* text=auto怎么办?

这个问题的答案是:这行不通。坏事发生!除了git add --renormalize,Git 将相信索引和工作树匹配——因此如果文件是最近提取或添加的,则不会费心复制一种或另一种方式。

也就是说,特别要注意这一点:

$ git checkout branch
$ echo '* -text' > .gitattributes   # avoid conversions
$ git add file.ext                  # use whatever's in the work-tree

和这个:

$ git checkout branch
$ echo '* -text' > .gitattributes   # avoid conversions
$ git checkout -- file.ext          # see what's actually in the commit

假设工作树和索引已经匹配,这里的git addorgit checkout步骤可能不会做任何事情,即使它们仅用于基于先前.gitattributes设置(或缺少设置)进行匹配。您可以通过修改(或什至删除,如果您要重新提取)文件来强制提取或添加,以便工作树副本现在明显不同。但总的来说,让.gitattributes更改生效既烦人又棘手,因为 Git 没有意识到修改.gitattributes可以更改 Git 将执行的转换

就此而言,core.eoland也是如此core.autocrlf:更改这些配置项会更改 Git 可能执行的转换,但 Git 不知道索引和工作树现在可能彼此不同步。索引的缓存方面在这里对您不利。

最后,请注意,如果 Git 看到git add-ing 文件更改行尾,Git 会告诉您某个文件正在或将被修改。有时 Git 会猜错,尤其是在更改.gitattributes条目之后,将分类为文本的文件现在分类为二进制文件,反之亦然。在这些情况下,您git add无论如何都必须访问该文件,以便 Git 更新索引副本,之后 Git 意识到更新的索引副本仍然与HEAD提交匹配,并且文件根本没有被修改。

换句话说,有时修复.gitattributes文件是不够的,即使文件不会改变。 另请注意,有时,如果条目丢失或不正确,文件的提交版本的行尾错误。.gitattributes工作树副本很可能是正确的,但是在您进行新的提交之前,该提交具有从正确的索引副本构建的冻结副本,Git 将——这一次正确地——告诉你文件已被修改!


推荐阅读