怎么了? GIT 见变无变

What going on? GIT see changes without changes

Git 看到干净克隆后的变化。

我只是从服务器克隆项目,我的一个文件已标记为已更改。

nick@DESKTOP-NUMBER MINGW64 /d
$ git clone http://nick@host/nick/test.git
Cloning into 'test'...
remote: Enumerating objects: 27, done.
remote: Counting objects: 100% (27/27), done.
remote: Compressing objects: 100% (22/22), done.
remote: Total 27 (delta 8), reused 0 (delta 0)
Unpacking objects: 100% (27/27), done.
error: failed to encode 'Var.not' from UTF-8 to Windows-1251

nick@DESKTOP-NUMBER MINGW64 /d
$ cd test/

nick@DESKTOP-NUMBER MINGW64 /d/test (master)
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   Var.not

no changes added to commit (use "git add" and/or "git commit -a")

error: failed to encode 'Var.not' from UTF-8 to Windows-1251 文件可以在 VSC 中以 UTF-8 代码打开,带有一些不可读的符号——通常是 Windows-1251。但是有什么问题呢?相邻文件 "Var.yes" 具有相同的文本和相同的代码页 - 没问题,没有伪更改。

如何解决?

您在 中添加的这条信息至关重要:

I just use .gitattributes with line: *.* text working-tree-encoding=Windows-1251

working-tree-encoding 指令有很多副作用。有关更多详细信息,请参阅 the gitattributes documentation,但稍后我会从该页面再引用一点。

以上问题的错误信息:

error: failed to encode 'Var.not' from UTF-8 to Windows-1251

表明此文件的内容实际上并未存储为 UTF-8 数据。

gitattributes 文档中列出的陷阱之一是:

For example, Microsoft Visual Studio resources files (*.rc) or PowerShell script files (*.ps1) are sometimes encoded in UTF-16.

也许您的 Var.not 文件就是这种情况。

无论如何:

Am I wrong and working-tree-encoding edit and resave my files somehow?

是的,这就是工作树编码的作用。完全准确地说,我们需要讨论 Git 如何在内部存储文件,然后将它们提取到您的工作树以便您可以使用它们,或者将它们从您的工作树复制为内部格式。

Git 内部结构:blob 对象,或者文件如何永久冻结

Git 并不是关于 文件 ,而是关于 提交 。每次提交一旦完成,(大部分)都是永久性的,并且(完全)是只读的/不可更改的。但是,一个提交 持有 个文件——或者更准确地说,引用了 个文件——因此通过存储提交,Git 有效地存储了文件。

文件的 格式 在存储中很重要。

通常,Git 只是承诺一个文件是一个字节包。无论您在文件中存储什么字节,Git 都会为您取回它们。原始数据文件就是这种情况——对于您在 .gitattributes 中说 -text 的文件。 所有 文件都是这种情况,如果您不要求 Git 处理它们,即您不将它们标记为 text 并设置选项,例如CRLF 行结尾或 working-tree-encoding。但是,如果你这样做......好吧,首先,让我们继续了解字节袋文件的工作原理。

每个 提交都会存储每个 文件的副本——但是有重复数据删除!假设您有一千次提交,每个提交都有一千个文件。这意味着您 Git 存储了各种文件的一百万个版本。但是 大多数 这些版本的文件是相同的。也就是说,在您第一次提交时,您可能已经创建了一个名为 README.md 的文件。您将一些文本放入文件中,并将文件放入您的第一次提交中。

之后,您使用 相同 README.md 进行了另外 99 次提交。然后你稍微改变了它,并用 README.md.

的第二个版本进行了剩余的 900 次提交。

提交中的文件,就像提交本身一样,一直被冻结。所以没有必要制作 1000 个单独的 README.md 版本。我们只需要 两个 版本:第一个和第二个。前 100 个提交所有 share 第一个 README.md。最后 900 个提交都共享第二个。

为了快速完成此操作并节省 space,Git 对字节袋文件所做的是 压缩 它(使用 zlib deflate)并将其存储在 Git 所谓的 blob 对象 中。这个 blob 对象获得一个唯一的哈希 ID,就像每个提交都获得一个唯一的哈希 ID 一样。第一个 README.md 的哈希 ID 是基于其中的数据字节。第二个 README.md 的哈希 ID 基于第二个 README.md 中的数据字节。因此只有两个 blob 对象,在所有 1000 次提交中共享,每个提交引用具有正确冻结、压缩 README.md 内容的对象。

所有这一切的结果是每次提交的文件存储都由这些冻结、压缩的 blob 对象组成。我喜欢这样称呼文件"freeze-dried":它们就像冻干咖啡,你必须加水。对冻干文件进行再水化可以让您恢复原来的内容——原来的字节包。

因此,要签出 提交,Git 必须重新水化其所有冻干文件。提交包含冻干(且不可修改!)的副本。 工作树 包含常规格式文件。我们稍后会回到这个问题。

Git 内部结构:索引,A.K.A。暂存区

当您进行 new 提交时,Git 必须将所有文件打包为新的或重新使用的冻结 blob 对象。其他版本控制系统已经做到了这一点,例如,重新冻结每个文件。这太慢了! Git,相反,做了一些聪明的事情。

当您第一次检查一些现有的提交时,Git 不只是重新水合它的文件。 Git 还存储对现有冻干副本的引用。 current 提交中的这些文件的冻干副本列表在 Git 不同的调用中 index暂存区,或(现在很少)缓存

换句话说,索引列出了将此提交提取到工作树中的所有 blob 哈希 ID。

当您修改工作树中的内容时,索引不会发生任何变化。您必须对每个修改后的文件 运行 git add <file>。此 git add 步骤 工作树 复制 文件。它将字节重新压缩为内部冻干形式。如有必要,这会在现场创建一个新的 blob 对象。现在 Git 在索引中有一个冻结格式、准备提交文件的哈希 ID。

换句话说,在任何时候,索引都包含 下一个 提交,准备就绪。如果您希望在下一次提交 中更新更新的文件,您必须运行 git add 对其进行更新。这会将文件复制到索引中,通过查找或创建一个内部 blob 对象,索引再次包含 next 提交,准备就绪。

这也是为什么你要保持运行宁git add。更新工作树文件 不会影响索引 ,并且 git commit 从索引 中的任何内容进行新提交。如果它不在索引中,则它不在新提交中。无论 是什么 在索引中, 那就是 新提交中的内容。

请注意 git status 的工作人员:

  1. 比较 HEAD 提交到索引。无论文件 不同 ,Git 都表示 暂存用于提交 。当两个文件相同时——当它们是同一个 blob 对象时——Git 什么都不说。

  2. 正在将索引与工作树进行比较。每当工作树文件不同时,Git 表示 not staged for commit。当两个文件相同时(经过适当的再水化或冷冻干燥后),Git 什么都不说。 (请注意,有两种比较方法:冷冻和比较冷冻,或再水化和比较再水化。我 think Git 做了其中的第二种,出于各种原因,但文档没有做出任何承诺,因此它可能会在没有警告的情况下更改。)

因此,索引或临时区域才是真正要提交的内容。您的工作树仅供您 使用 您的文件。这些文件从未真正提交:提交的是索引中的冻干内容。

.gitattributes影响冷冻干燥和复水过程

请注意,每次 out of Git 的文件都必须重新水化。请注意,每次文件 进入 索引/暂存区时,它都必须被冻干。这些进程总是 处理字节袋文件,通过使用 zlib deflate 压缩它们,或使用 zlib inflate 重新生成它们,视情况而定。 zlib deflate/inflate 是一种数据保存操作:它永远不会 更改 任何字节,最后,在往返(放气 + 膨胀)之后。

但是因为 Git 已经在处理每个文件的每个字节,所以这里也是 更改 字节的理想位置。例如,假设我们希望冻干文件始终使用换行结尾,但 Windows 上的工作树文件使用 CRLF 换行结尾。我们可以告诉 Git:

  • 再水化文件时,将 \n 更改为 \r\n(仅 LF 到 CRLF)。
  • 冷冻干燥文件时,将 \r\n 更改为 \n(CRLF 到 LF-only)。

因为 Git 从索引(冻干)提交,而不是从工作树(再水化)提交,这正是我们想要的。为此,我们所做的就是写:

*.txt  text eol=crlf

但我们可以让它做的不仅仅是 LF/CRLF 翻译。事实上,使用 Git 调用的 cleansmudge 过滤器,我们可以插入我们自己的任意操作。 (这就是 Git-LFS 的工作方式。)或者,在这种特殊情况下,我们可以设置 working-tree-encoding.

工作树编码影响冻干和复水

工作树编码设置告诉 Git:

  • 再水合时,假定原始文件为 UTF-8,并重新编码为工作树编码。
  • 冷冻干燥时,假定原始文件采用工作树编码,并在执行通常的 zlib deflate 之前转换为 UTF-8。

为此,blob 对象 必须 实际上 UTF-8。此外,这个操作——UTF-8 到任何东西,任何东西到 UTF-8——需要保持一致:如果不是,每个提交都可以随机重新编码成 UTF8。这与 deflate/inflate 的往返思路相同。但并不是所有的编码都能在这里做出很好的保证。

有关陷阱的(更多)信息——比 gitattributes 文档提到的更多——请参阅 Joel on Software: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) and then, e.g., this article on Unicode combining characters and normalization,它表明 看起来 相同的两个字符串("Zoë") 可以用不同的字节序列拼写(组合元音变音和字母 E,或使用小写 E-with-umlaut Unicode 字符)。

对于您的情况,最有可能的问题是输入文件不是以 UTF-8 开头的(但这可能是某种重新编码错误)。