话虽如此,让我们首先将字符编码与代码页区分开来。(也请参见下面类似脚注的部分。)这里的根本问题是计算机(至少是现代计算机)使用一系列8位字节,每个字节表示[0..255]范围内的一个整数。旧的系统有6、7、8甚至9位字节,但我认为把任何小于8位的东西都叫做“字节”是误导性的。(BBN's 的“C机器”有10位字节!)无论如何,如果一个字节表示一个字符符号,那么这就给我们提供了256种符号的上限。在那些不好的ASCII时代,这是足够的,因为ASCII只有128个符号,其中33个是非打印符号(控制代码0x00
到0x1f
,以及0x7f
代表DEL或已删除的纸带上的打孔,在此以十六进制书写)。
当我们需要超过94个可打印符号加上空格(0x20)时,我们——我指的是全世界使用计算机的人们,而不是特定的我——说:“看这个,我们有128个未使用的编码,从0x80到0xff,让我们使用其中一些!”于是法国人用了一些ç和é等字符,以及像«和»这样的标点符号。捷克人需要一个带抑扬符的Z,ž。俄罗斯人需要很多,用于Cyrillic。希腊人需要很多,等等。结果是8位空间的上半部分分裂成许多不兼容的集合,人们称之为代码页。
0x0000
到0xffff
;减去一些控制码仍然有足够的空间容纳许多字母表。然而,我们很快就超过了这个限制,所以我们转向了Unicode,它有1114112个被称为代码点的位置,每个位置代表某种具有语义意义的符号。其中有超过10万个正在使用,包括表情符号如和。
未在此列出的是{{blob}},在大多数情况下,Git不会对{{blob}}进行任何解释。
为了方便理解提交、树和标签,Git通常将三者限制为UTF-8。但是,Git允许在提交中的{{日志消息}}或标签中的{{标签文本}}中进行一些(大多数)未解释的内容。这些内容位于Git解释的头部之后,因此即使此时存在特别棘手或丑陋的内容,也是相当安全的。(由于PGP签名出现在标题下面,有一些微小的风险,因为它们会被解释。)对于提交而言,现代Git将在解释部分中包括一个编码标头行,然后Git可以尝试对提交消息正文进行解码,并将其重新编码为Git输出的字节使用的任何程序所使用的编码方式。1
对于注释型标签对象,同样的规则也适用。我不确定Git是否有代码来处理标签(提交代码可能可以重复使用,但标签通常具有PGP签名,并且强制UTF-8可能更明智)。由于树是{{内部}}对象,它们的编码基本上是不可见的——除了我在书中指出的问题,您不需要意识到这一点。
这会留下{{blobs}},它们是最重要的部分。
1这是计算机世界中反复出现的主题:一切都被重复编码和解码。考虑一下通过Wi-Fi或有线网络连接传输的内容:它已被编码成某种无线电波或类似物,然后某个硬件将其解码成位流,另一些硬件将其重新编码成字节流。硬件和/或软件会剥离头部,在某种方式下解释剩余的编码,适当地更改数据,并重新编码比特和字节,以供另一层硬件和软件处理。真奇妙,任何事情竟然都能完成。
Git喜欢声称它完全不关心存储在文件中的实际数据,即Git blob。这基本上是真的。或者说,半真。只要Git所做的只是存储您的数据,那就完全正确!Git只存储字节。这些字节的含义由您决定。
当您运行git diff
或git merge
时,故事就会破裂,因为差异算法和合并代码是面向行的。行以换行符终止。 (如果您使用的是使用CRLF而不是换行符的系统,则CRLF对的第二个字符是换行符,因此这里没有问题-尽管Git可以处理未终止的最终行,但这会在某些地方引起一些小问题。)如果文件以UTF-16编码,则许多字节往往似乎是ASCII NULs,因此Git将其视为二进制。
0xa0
,第二个编码为0xa1
,以此类推。我们总是留出至少一些控制代码的空间——通常是在0x00
到0x1f
范围内的所有32个控制代码——通常我们还留下整个7位ASCII子集,就像Unicode本身一样(参见Unicode字符列表),这就是为什么我们最常从0xa0
开始的原因。此内容由Junio C Hamano / 濱野純可选地,使用“-u”标志,将
.info
和.msg
的输出从其原始字符集[sic]转换为utf-8。这是为了鼓励人们在提交消息中使用utf8以实现互操作性。
<junkio@cox.net>
签署。/
" 的特殊含义以及 commit 对象中的空格和换行符也排除了 EBCDIC 和其他非 ASCII 编码。Git 期望 bytes < 0x80
是纯 ASCII,因此与 ASCII 范围部分重叠的 CJK 编码也存在问题。例如,fmt_ident()
假定用户名的尾随 0x5C 是 ASCII '\
',但是有超过 200 个 GBK 双字节代码以 0x5C 结尾。Linux 上的默认编码为 UTF-8,并在 Mac 和 Windows 版本中进行相应的路径转换,已经建立了 UTF-8 NFC 作为路径名的事实标准。"Documentation/i18n.txt
的最新版本包括: opendir
/ readdir
以使用Windows Unicode API并在UTF-8 / UTF-16之间进行转换。iconv()
在被要求将编码转换为 UTF-16(或 UTF-32)时,输出总是带有 BOM,但显然有些实现输出大端序而没有 BOM。查看提交 79444c9(2019年2月12日)由brian m. carlson (bk2204
)完成。
(由Junio C Hamano -- gitster
--在提交18f9fb6中合并,2019年2月13日)
当序列化UTF-16(和UTF-32)时,有三种可能的写入流的方式。可以以大端或小端格式使用BOM编写数据,也可以在大端格式下不使用BOM编写数据。
utf8
: 处理不为UTF-16写BOM的系统
Makefile
中:# Define ICONV_OMITS_BOM if your iconv implementation does not write a
# byte-order mark (BOM) when writing UTF-16 or UTF-32 and always writes in
# big-endian format.
#
ifdef ICONV_OMITS_BOM
BASIC_CFLAGS += -DICONV_OMITS_BOM
endif
由于NonStop OS及其关联的NonStop SQL产品始终使用UTF-16BE(16位)编码Unicode(UCS2)字符集, 在该环境中您可以使用ICONV_OMITS_BOM
。
argv
中提供无效的UTF-8字节序列,您可以在启动时使程序崩溃。 - torek最近,Git开始支持像UTF-16这样的编码格式。请参阅gitattributes文档中的working-tree-encoding。
如果您想在Windows机器上将.txt文件设置为UTF-16而没有BOM,则需将以下内容添加到您的gitattributes文件中:
*.txt text working-tree-encoding=UTF-16LE eol=CRLF
针对 jthill的评论:
毫无疑问,UTF-16是一团糟。但请考虑以下几点:
查看 提交fe21c6b, 提交665177e (2018年10月30日) 由 Johannes Schindelin (dscho
) 完成。
协助者: Jeff Hostetler (jeffhostetler
)。
(由Junio C Hamano -- gitster
--合并于提交0474cd1, 2018年11月13日)
mingw
: 在运行时动态重新编码环境变量(UTF-16 <-> UTF-8
)在 Windows 上,权威的环境编码是 UTF-16。
在 Git for Windows 中,我们将其转换为 UTF-8(因为 UTF-16 对于 Git 来说是一个“陌生”的概念,其源代码没有为此做好准备)。
以前,出于性能考虑,我们一开始就将整个环境转换为 UTF-8,并在 putenv()
和 run_command()
时将其转换回来。
拥有一个私有副本的环境也有其自身的危险:当 Git 的源代码使用的库尝试修改环境时,它实际上并不起作用(在 Git for Windows 的情况下,libcurl
,请参见 git-for-windows/git/compare/bcad1e6d58^...bcad1e6d58^2
了解问题的一瞥)。
因此,如果我们在 getenv()
/putenv()
调用中切换到动态转换,则可以使我们的环境处理更加健壮。基于 Jeff Hostetler 在 MSVC 上的初始版本,此补丁使之成为可能。
令人惊讶的是,这对速度有一个积极的影响:在编写当前代码时,我们测试了性能,并且有如此多的getenv()
调用,以至于似乎最好一次性转换所有内容。然而,与此同时,Git 显然已经在 getenv()
调用方面进行了一些清理,因此测试套件生成的 Git 进程在其生命周期内平均只使用 40 次 getenv()
/putenv()
调用。
说到整个测试套件:在当前代码中,重新编码所花费的总时间约为 32.4 秒(运行时间为 113 分钟),而此补丁引入的代码总共只需约 8.2 秒。
不算太多,但它证明了我们不必担心此补丁引入的性能影响。
在 Git 2.21(2019年第一季度)中,之前的路径引入了一个错误,影响了 GIT_EXTERNAL_DIFF
命令:从 getenv()
返回的字符串是非易失性的,这是不正确的,已经得到了纠正。
请参见 提交 6776a84(2019年1月11日),作者为 Kim Gybels (Jeff-G
)。
(由 Junio C Hamano -- gitster
-- 合并于 提交 6a015ce,2019年1月29日)
git-for-windows/git
问题2007中被报告: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
: 确保external_diff_cmd
的正确生命周期根据getenv(3)的注释:
getenv()
的实现不需要是可重入的。
由getenv()
返回的字符串可能是静态分配的,并且可以通过后续调用getenv()
、putenv(3)
、setenv(3)
或unsetenv(3)
进行修改。
由于getenv()
返回的字符串允许在后续调用getenv()
时更改,因此在从环境中缓存external_diff_cmd
时,请务必复制。
这个问题在Git for Windows上变得明显,因为fe21c6b(mingw
:动态重新编码环境变量(UTF-16 <-> UTF-8))之后,compat/mingw.c
中提供的getenv()
实现已更改以保留一定数量的分配字符串并在后续调用中释放它们。
Git 2.24(2019年第四季度)修复了之前引入的一个黑客攻击。
请参见提交2049b8d,提交97fff61(2019年9月30日),作者为Johannes Schindelin(dscho
)。
(由Junio C Hamano -- gitster
--在提交772cad0中合并,2019年10月9日)
sort
函数git_sort()
移入libgit.a
qsort()
函数不能保证是稳定的,即它不能保证维护被告知视为相等的项的顺序。
相比之下,我们在compat/qsort.c
中实现了一个归并排序算法的git_sort()
函数是稳定的。
为了准备在Git的重命名检测中使用稳定的排序,将稳定的排序移入libgit.a
,以便无条件地编译它,并将其重命名为git_stable_qsort()
。
注意:这也使得我们引入的hack过时了,该hack是在fe21c6b(mingw
:在飞行中重新编码环境变量(UTF-16 <-> UTF-8
),2018-10-30,Git v2.20.0-rc0)中直接包含compat/qsort.c
到compat/mingw.c
中使用稳定的排序。