为什么GIT本身不支持UTF-16

时间:2018-09-24 03:47:20

标签: git utf-16

Git支持几种不同的编码方案:UTF-7UTF-8UTF-32以及非UTF编码方案。

鉴于此,为什么它不支持UTF-16

有很多问题询问如何获取git以支持UTF-16,但我认为尚未明确提出或回答。

5 个答案:

答案 0 :(得分:7)

我将book (see Chapter 3, which is in better shape than later chapters)(目前处于垂死状态)整整一整章中的很大一部分用于字符编码问题,因为这是一个历史混乱。不过,在这里值得一提的是,这个问题的前提的一部分(Git以某种方式支持UTF-7和UTF-32)是错误的:UTF-7 is a standard that never even came about,并且可能根本不应该使用(自然,较旧的Internet Explorer版本确实存在,这会导致链接的Wikipedia页面上提到的安全性问题。

也就是说,我们首先将字符编码代码页分开。 (也请参见下面的脚注部分。)这里的根本问题是,计算机-无论如何,还是现代的-还是可以处理一系列8位的 bytes ,每个字节代表[0..255]范围内的整数。较早的系统具有6、7、8甚至9位字节,尽管我认为将小于8位的任何内容称为“字节”都是令人误解的。 (BBN的“ C机器”有10位字节!)在任何情况下,如果一个字节代表一个字符符号,则我们可以得到256种符号的上限。在ASCII那些糟糕的过去,这已经足够了,因为ASCII只有128个符号,其中33个是非打印符号(控制代码0x000x1f,加上{{1 }}代表DEL或纸带上已删除的打孔器,在此处以十六进制形式书写。

当我们需要94个以上的可打印符号加空格(0x7f)时,我们我们我的意思是全世界使用计算机的人,不是特别的 me -说:好吧,看一下,我们有128种未使用的编码,0x200x80,让我们使用其中的一些!因此,法语使用了ç和é等,以及诸如«和»的标点符号。捷克人需要一个带卡纸的Z z。俄罗斯人需要很多东西,用于西里尔字母。希腊人需要很多东西,依此类推。结果是8位空间的上半部分爆炸成许多不兼容的集合,人们称之为code pages

从本质上讲,计算机存储了一些八位字节的值,例如235十进制(0xff十六进制),这取决于其他事情-另一个计算机程序,或者最终是人盯着屏幕来解释235,例如西里尔字母л或希腊字母λ等。如果我们使用的是一个代码页,它将告诉我们“ 235”是什么意思:我们应该对此施加什么样的语义。

这里的问题是我们可以支持多少个字符代码。如果我们想让西里尔字母L(л)与希腊字母L(lambda,λ)共存,我们不能同时使用CP-1251 CP-1253,因此我们需要一种更好的方式来编码 symbol 。一种明显的方法是停止使用一个字节的值编码符号:如果使用两个字节的值,则可以编码65536个值,0xEB0x0000;包括减去一些控制代码,仍然有很多字母的余地。但是,我们甚至迅速突破了这个限制,因此我们使用了Unicode,它有1,114,112个称为code points的空间,每个Unicode代表某种具有某种语义的符号。其中约有100,000种正在使用中,包括emoji like和。

将Unicode编码为字节或单词

这是UTF-8,UTF-16,UTF-32,UCS-2和UCS-4都加入的地方。这些都是用于 encoding Unicode代码点的方案-其中之一约100万个值—转换为字节流。我将完全跳过UCS编码,而仅关注UTF-8和UTF-16编码,因为这是目前最有趣的两种编码。 (另请参见What is Unicode, UTF-8, UTF-16?

UTF-8编码很简单:十进制值小于128的任何代码点都被编码为包含该值的字节。这意味着普通ASCII文本字符保留为普通ASCII文本字符。 0xffff(十进制128)至0x0080(十进制2047)中的代码点编码为两个字节,它们的值都在128-255范围内,因此可与一个字节的编码值区分开。 0x07ff0x0800范围内的代码点在相同的128-255范围内编码为三个字节,其余有效值编码为四个此类字节。 就Git本身而言,关键是没有编码值类似于ASCII NUL(0xffff)或斜杠(0x00)。

此UTF-8编码的作用是让Git假装文本字符串(尤其是文件名)是斜杠分隔的名称成分,其名称的末尾均是ASCII码或可以使用ASCII标记NUL字节。这是Git在 tree 对象中使用的编码,因此UTF-8编码的树对象正好适合,而无需摆弄。

UTF-16编码每个字符使用两个成对的字节。对于Git和路径名,这有两个问题。首先,一对中的一个字节可能会意外地类似于0x2f,并且所有ASCII值字符必须编码为一对字节,其中一个字节为/,类似于ASCII NUL。因此,Git需要知道:此路径名已使用UTF-16 进行编码,并且适用于字节对。树对象中没有空间可以容纳此信息,因此Git需要一个新的对象类型。其次,每当将16位值分成两个单独的8位字节时,我们都会以某种顺序执行此操作:我要么先给您更高的有效字节,然后再给您较低的字节;或者我先给您一个低位有效字节,然后给您高位有效字节。第二个问题导致UTF-16具有Byte Order Marks的原因。 UTF-8不需要字节顺序标记就足够了,那么为什么不在树中使用它呢? Git也是如此。

对树来说很好,但是我们也有提交,标签和斑点

Git对这四种对象中的三种进行自己的解释:

  1. 提交包含哈希ID。
  2. 树包含路径名,文件模式和哈希ID。
  3. 标记包含哈希ID。

此处未列出的是 blob ,并且在大多数情况下,Git不会对blob进行任何解释。

为了易于理解提交,树和标签,Git在很大程度上将所有三个约束都限制在UTF-8中。但是,Git 确实允许提交中的日志消息,或标签中的标签文本,在某种程度上(大部分)未被解释。它们位于Git解释的标头之后,因此,即使此时有些特别棘手或丑陋的地方,也很安全。 (由于PGP签名出现在标题下方,因此存在一些较小的风险, do 会得到解释。)特别是对于提交,现代Git将在其中包含 encoding 标题行解释的部分,然后Git可以尝试 decode 提交消息主体,并将其 re-encode 转换为任何解释Git所吐字节的程序所使用的编码出 1

相同的规则可用于带注释的标记对象。我不确定Git是否具有对标签执行此操作的代码(大多数情况下都可以重复使用提交代码,但是标签更常见的是具有PGP签名,并且在此处强制使用UTF-8可能更明智)。由于树是内部对象,因此它们的编码在任何情况下都是不可见的-您无需意识到这一点(我在书中指出的问题除外)。

这会留下斑点,它们是大猩猩。


1 这是计算世界中反复出现的主题:所有内容都经过反复编码和解码。考虑一下某些事物是如何通过WiFi或电缆网络连接到达的:它被编码为某种无线电波或类似的无线电波,然后一些硬件将其解码为比特流,然后其他一些硬件将其重新编码为字节流。硬件和/或软件剥离标题,以某种方式解释剩余的编码,适当地更改数据,并对位和字节重新编码,以供另一层硬件和软件处理。真奇怪,一切都做不到。


Blob编码

Git喜欢声称它完全不存储在文件中的实际 ,就像Git斑点一样。甚至大部分都是如此。或者,好吧,一半正确。或者其他的东西。只要Git所做的只是存储您的数据,那是完全正确的! Git只是存储字节。这些 mean 字节由您决定。

当您运行0x00git diff时,这个故事会崩溃,因为diff算法以及合并代码都是面向 line 的。行以换行符终止。 (如果您使用的是使用CRLF而不是换行符的系统,那么,CRLF对的第二个字符是换行符,因此这里没有问题-Git可以使用不终止的最后一行,尽管这会在这里和那里造成一些小小的烧心。)如果文件使用UTF-16编码,则很多字节似乎是ASCII NUL,因此Git只是将其视为二进制。

可修复的:Git可以将UTF-16数据解码为UTF-8,并通过其所有现有的面向行的算法(现在将看到换行符终止的行)提供该数据,然后将数据重新编码回UTF-16。这里有很多较小的技术问题。最大的决定是确定某些文件 UTF-16,如果是,则确定哪种字节序(UTF-16-LE或UTF-16-BE?)。如果文件具有字节顺序标记,则可以解决字节序问题,并且可以将UTF-16编码为git merge,就像您当前可以声明文件.gitattributesbinary一样,因此都可以解决。只是一团糟,还没有人完成这项工作。

脚注式:代码页可以被视为(粗俗的)编码形式

我在上面提到,我们使用Unicode所做的事情是将21位代码点值编码为一定数量的8位字节(UTF-8中为1到4个字节,UTF-16中为2个字节, UTF-16称为“代理”的错误小技巧,它将21位值压缩到16位容器中,有时使用成对的16位值。这种编码技巧意味着我们可以表示所有合法的21位代码点值,尽管这样做可能需要多个8位字节。

当我们使用代码页(CP- number )时,我们正在做的是,或者至少可以将其视为映射 256个值-装入一个8位字节,即插入到21位代码点空间。我们从不超过256个这样的代码点中选出一些子集,然后说:这些是我们允许的代码点。我们将第一个编码为text,将第二个编码为如0xa0,依此类推。我们总是留出至少一些控制代码的空间(通常是0xa10x00范围内的所有32个代码),通常我们会像Unicode本身一样保留整个7位ASCII子集(请参阅{{ 3}}),这就是为什么我们通常从0x1f开始。

当编写适当的Unicode支持库时,仅使用这种形式的索引,代码页就可以简单地变成转换表。困难的部分是为所有代码页创建准确的表。

关于代码页的好处是,每个字符又是一个字节。不好的是,您只说一次符号集,当您说:我使用此代码页。从那时起,您就被锁定在这小小的Unicode子集中。如果切换到另一个代码页,则您的部分或全部八位字节值表示不同符号。

答案 1 :(得分:5)

Git代码库中对UTF-8的首次提及可以追溯到d4a9ce7 (Aug. 2005, v0.99.6),这与邮箱补丁有关:

  

(可选)带有'-u'标志,.info和.msg的输出从其原始字符集转译为utf-8。这是为了鼓励人们在提交消息中使用utf8以实现互操作性。

这是由Junio C Hamano /滨野纯<junkio@cox.net>签名的。

字符编码已在commit 3a59e59 (July 2017, Git v2.6.0-rc0

中阐明
  

“ git编码不可知”仅对blob对象正确。
  例如。树和提交对象的“非NUL字节”要求不包括   UTF-16 / 32,以及索引文件中“ /”的特殊含义以及   提交对象中的空格和换行符消除了EBCDIC和其他非ASCII编码。

     

Git期望<0x80的字节为纯ASCII,因此与ASCII范围部分重叠的CJK编码也是有问题的。
  例如。 fmt_ident()假定它是ASCII'\',将从用户名中删除结尾的0x5C。
  但是,有超过200个GBK双字节代码以0x5C结尾。

     

UTF-8是Linux上的默认编码,并且在   Mac和Windows版本已将UTF-8 NFC确立为事实上的标准   路径名称。

有关最后一个补丁的更多信息,请参见“ git, msysgit, accents, utf-8, the definitive answers”。

Documentation/i18n.txt的最新版本包括:

  

Git在某种程度上与字符编码无关。

     
      
  • Blob对象的内容是未解释的序列   个字节。核心没有编码翻译   级别。

  •   
  • 路径名以UTF-8规范化形式C编码。

      这适用于:

         
        
    • 树对象,
    •   
    • 索引文件
    •   
    • 引用名称以及
    • 中的路径名称   
    • 命令行参数
    •   
    • 环境变量和
    •   
    • 配置文件(.git/configgitignoregitattributesgitmodules
    •   
  •   

您可以在commit 0217569 (Jan. 2012, Git v2.1.0-rc0中看到UTF-8路径转换的示例,其中添加了Win32 Unicode文件名支持。

  

更改opendir / readdir以使用Windows Unicode API并在UTF-8 / UTF-16之间进行转换。

关于命令行参数,请参见。 commit 3f04614 (Jan. 2011, Git v2.1.0-rc0),可在启动时将命令行参数从UTF-16转换为UTF-8。


注意:在Git 2.21(2019年2月)之前,代码和测试假定提供的iconv()系统在被要求编码为UTF-16(或UTF-32)时将始终在其输出中使用BOM,但是显然,某些实现输出big-endian 没有BOM表。
添加了编译时旋钮以帮助此类系统(例如NonStop)将BOM添加到输出中以提高可移植性。

  

utf8:处理不为UTF-16编写BOM的系统

     

序列化UTF-16(和UTF-32)时,有三种可能的方法   写流。可以使用大端字节序中的BOM写入数据   或低字节序格式,或者可以在没有BOM的情况下写入数据   大端格式。

     

大多数系统的iconv实现选择将BOM写入   一些字节序,因为这是最简单的方法,并且可以抵抗   在Windows上的误解,其中UTF-16和little-endian   序列化很常见。
  为了与Windows兼容并避免在此处意外使用,Git始终希望使用BOM编写UTF-16,并且拒绝在没有它的情况下读取UTF-16。

     

但是,musl的iconv实现编写的是没有BOM的UTF-16,   依靠用户将其解释为大端。这导致t0028和   相关的功能将失败,因为Git不会读取文件   BOM。

答案 2 :(得分:1)

简短的形式是增加了对宽字符的支持,这使一切变得更加困难。处理任何8位ISO代码页或UTF-8或任何其他MBCS的所有内容都可以轻松扫描/生成/复制字符串。尝试增加对其传输编码包含嵌入式null的字符串的支持,即使是琐碎的操作也会使您的所有代码变得肿。

我什至不知道UTF-16的什至获得要求的优点,但是当您开始实际使用它时,缺点并没有消灭。您可以使用相同的简单代码识别ASCII,UTF-8,所有16个ISO / IEC-8859集,所有EBCDIC中的任何一个字符串边界,以及大概十多个。仅需很小的限制(基于ascii,为多行终止符约定添加了几行),您就可以进行基本的标记化,并且对普通内部代码页的音译基本上是免费的。

添加UTF-16支持后,您刚刚付出了很多额外的努力和复杂性,但是所有工作都无济于事-在说“哦,但现在它可以处理UTF-16!”之后,现在,只要加上所有的膨胀和精力,其他的就可以实现了吗?没有。 UTF-16可以做的所有事情,UTF-8可以做的通常更好的事情。

答案 3 :(得分:1)

Git对UTF-16的支持即将到来...环境变量以及Git 2.20(Q4 2018)
(以及Git 2.21中的错误修复:请参见答案的第二部分)

请参见commit fe21c6bcommit 665177eJohannes Schindelin (dscho)(2018年10月30日)。
帮助者:Jeff Hostetler (jeffhostetler)
(由Junio C Hamano -- gitster --commit 0474cd1中合并,2018年11月13日)

  

mingw:即时重新编码环境变量(UTF-16 <-> UTF-8

     

在Windows上,权威环境使用UTF-16编码。
  在Windows的Git中,我们将其转换为UTF-8(因为UTF-16对Git来说是 such 的陌生想法,因为它没有为其准备源代码)。

     

以前,出于性能方面的考虑,我们在一开始就一举将整个环境转换为UTF-8,然后在putenv()run_command()转换回来。

     

拥有环境的私有副本有其自身的风险:当Git的源代码使用的库试图修改环境时,它实际上是行不通的(对于Git for Windows,libcurl,请参见git-for-windows/git/compare/bcad1e6d58^...bcad1e6d58^2来查看问题)。

     

因此,如果我们在getenv() / putenv()调用中切换到即时转换,那么它将使我们的环境处理变得更加健壮。
  基于Jeff Hostetler在MSVC上下文中的初始版本,此补丁可以实现。

     

令人惊讶的是,这对速度产生了的影响:在编写当前代码时,我们测试了性能,并且有很多 {{1 }}称一次转换所有内容似乎更好。
  不过,与此同时,Git显然已对getenv()调用进行了一些清理,因此测试套件产生的Git进程平均仅使用40 getenv() / getenv()在整个过程的生命周期中进行调用。

     

说到整个测试套件:当前代码中重新编码所花费的总时间大约为32.4秒(运行时间为113分钟),而此补丁中引入的代码总共仅花费8.2秒。
  数量不多,但这证明我们不必担心此修补程序对性能的影响。


在Git 2.21(2019年第一季度)中,上一条路径引入了一个影响putenv()命令的错误:字符串 从GIT_EXTERNAL_DIFF返回的值是非易失性的,这是不正确的,即 已得到纠正。

请参见commit 6776a84Kim Gybels (Jeff-G)(2019年1月11日)。
(由Junio C Hamano -- gitster --commit 6a015ce中合并,2019年1月29日)

该错误已在git-for-windows/git issue 2007中报告:
“无法在8个以上的文件上使用getenv()

difftool

因此:

  

$ yes n | git -c difftool.prompt=yes difftool fe21c6b285df fe21c6b285df~100 Viewing (1/404): '.gitignore' Launch 'bc3' [Y/n]? Viewing (2/404): 'Documentation/.gitignore' [...] Viewing (8/404): 'Documentation/RelNotes/2.18.1.txt' Launch 'bc3' [Y/n]? Viewing (9/404): 'Documentation/RelNotes/2.19.0.txt' Launch 'bc3' [Y/n]? error: cannot spawn ¦?: No such file or directory fatal: external diff died, stopping at Documentation/RelNotes/2.19.1.txt :确保diff正确的生命周期

     

根据getenv(3)的注释:

     
    

不需要重新实施external_diff_cmd
    返回值getenv()指向的字符串可以静态分配,并且可以通过随后对getenv()getenv()putenv(3)或{{1 }}。

  
     

由于允许setenv(3)返回的字符串在以后对unsetenv(3)的调用中发生变化,因此从环境中缓存getenv()时请确保重复。

     

fe21c6b起,此问题在Windows的Git上变得明显   (getenv():即时重新编码环境变量(UTF-16 <-> UTF-8)),   当external_diff_cmd中提供的mingw实现被更改时   保留一定数量的分配字符串并释放它们   随后的通话。

答案 4 :(得分:1)

git最近已经开始了解utf16等编码。请参阅gitattributes文档,搜索工作树编码

如果要在Windows计算机上将.txt文件设置为不带Bom的utf-16,则将其添加到gitattributes文件中

*.txt text working-tree-encoding=UTF-16LE eol=CRLF

已添加,以响应@jthill评论above

毫无疑问,UTF16是一团糟。但是请考虑

  • Java使用UTF16
  • Microsoft

    请注意行 UTF16…在Windows操作系统上用于本机Unicode编码的行

  • Javascript在UCS-2和UTF-16之间使用mess