3

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”具有相同的文本和相同的代码页 - 没问题,没有伪更改。

怎么修?

4

1 回答 1

3

您在评论中添加的这一点信息至关重要:

我只是用.gitattributes线:*.* text working-tree-encoding=Windows-1251

working-tree-encoding指令有很多副作用。有关更多详细信息,请参阅gitattributes 文档,但稍后我会从该页面引用更多内容。

来自您上述问题的此错误消息:

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

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

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

例如,Microsoft Visual Studio 资源文件 ( *.rc) 或 PowerShell 脚本文件 ( *.ps1) 有时以 UTF-16 编码。

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

任何状况之下:

我错了,工作树编码以某种方式编辑和重新保存我的文件吗?

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

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

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

但是,文件在存储中的形式很重要。

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

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

之后,您使用相同 README.md的. 然后你稍微修改了一下,用第二个版本的README.md.

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

为了快速做到这一点,并节省空间,Git 对 bag-of-bytes 文件所做的就是压缩它(使用 zlib deflate)并将其存储在 Git 所谓的blob object中。这个 blob 对象获得一个唯一的哈希 ID,就像每个提交都获得一个唯一的哈希 ID。第一个的哈希 IDREADME.md基于其中的数据字节。秒的哈希 ID 基于该秒README.md中的数据字节README.md。所以只有两个 blob 对象,在所有 1000 个提交中共享,每个提交都指向具有正确冻结、压缩README.md内容的对象。

所有这一切的结果是每个提交的文件存储都由这些冻结的、压缩的blob对象组成。我喜欢将这种形式的文件称为“冷冻干燥”:它们就像冷冻干燥的咖啡,必须加水。对冻干文件进行再水化可以让您恢复原始内容(原始字节包)。

因此,为了检查一个提交,Git 必须重新水化它所有的冻干文件。提交包含冻干(且不可修改!)的副本。工作树保存常规格式的文件。我们稍后再讨论这个问题。

Git 内部结构:索引,AKA 暂存区

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

当你第一次检查一些现有的提交时,Git 不只是重新水化它的文件。Git 还存储对现有冻干副本的引用。这份冻干副本中的文件列表在当前提交中是 Git 所称的,不同的是,索引暂存区域或(最近很少)缓存

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

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

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

这也是为什么你必须继续跑步的原因git add。更新工作树文件不会影响 index,并且git commit 会从 index 中的任何内容进行新的提交。如果它不在索引中,则它不在新提交中。无论索引中的内容是什么,这就是新提交中的内容。

请注意,git status通过以下方式工作:

  1. HEAD将提交与索引进行比较。无论文件有什么不同,Git 都会说staged for commit。当两个文件相同时——当它们是同一个 blob 对象时——Git 什么也不说。

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

所以索引,或暂存区,才是真正被提交的东西。您的工作树仅存在以便您可以使用您的文件。这些文件实际上从未提交:提交的是索引中的冻干文件。

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

请注意,每次Git 中输出文件时,都必须对其进行补水。请注意,每次文件进入索引/暂存区域时,都必须对其进行冻干。这些进程总是对字节包文件大惊小怪,通过使用 zlib deflate 压缩它们,或者根据需要使用 zlib inflate 重新生成它们。zlib deflate/inflate 是一种数据保存操作:最终,在往返(deflate + inflate)之后,它永远不会更改任何字节。

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

  • 重新水化文件时,更改\n\r\n(仅 LF 到 CRLF)。
  • 冷冻干燥文件时,更改\r\n\n(CRLF to 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 关于软件:每个软件开发人员绝对、肯定必须了解 Unicode 和字符集(没有借口!)的绝对最小值,然后,例如,这篇关于Unicode 结合字符和规范化,这表明两个看起来相同的字符串(“Zoë”)可能用不同的字节序列拼写(组合变音符号和字母 E,或使用小写字母 E 和变音符号 Unicode 字符)。

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

于 2019-10-12T19:51:10.170 回答