Git客户端中文Bug总结

2015/03/13

本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接

概述

工作中使用Git版本控制工具管理源代码,客户端使用的是Git for windows+TortoiseGit,使用过程中发现客户端的两个Bug:

  • 1)创建中文目录的Git仓库(如:”技术文档”),右键单击Git Gui,会提示打不开目录,英文版提示No working directory
  • no working directory
  • 2)在Git仓库下添加子模块,子模块如果命名为中文目录,则进入中文目录后会看到图标显示不正常,即使是已提交的文件,悬浮图标会是带问号的图标,本来应该是打勾的图标

好奇心驱使下,下载了Git for windows和TortoiseGit的源代码,通过不断跟踪源代码,定位问题原因,并和这些开源项目的维护者沟通,最终找到了这些Bug的原因,并成功解决了这些Bug。

Git for windows的Bug

第1个Bug是Git for windows的Bug,在Github上提交了该Bug:

https://github.com/msysgit/git/issues/302

导致该Bug的主要原因是字符编码问题,git-gui.tcl有如下脚本:

1
2
3
4
5
6
7
8
9
proc git {args} {
  ...
    set result [eval exec $opt $cmdp $args]
    if {[encoding system] != "utf-8"} {
        set result [encoding convertfrom utf-8 [encoding convertto $result]]
    }
    ...
    return $result
}

git-gui.tcl调用这个函数执行git命令,eval 和exec是tcl执行引擎的内置命令,exec开一个终端执行参数所指的命令,并认为命令的输出字符串是系统默认代码页编码的,然后将输出字符串按照系统默认代码页转unicode的规则进行转换保存在内存里。中文系统默认代码页一般是cp936,因此按照cp936转unicode的规则进行了转换。

git命令的所有输出都是utf8编码的,但是tcl执行引擎将输出字符串按照cp936转unicode的规则进行了转换再保存在内存里。

encoding convertto $result将result字符串按照unicode转默认代码页的规则转换成cp936字符串,也就是说将git命令输出的字符串还原,然后encoding convertfrom utf-8将输出字符串按照utf-8转unicode的规则转换。将Git命令的输出字符串按照utf-8转unicode的规则转换才是正确的转换。

但是这里存在两个转换,先将Git命令输出字符串当作cp936字符串转换成unicode字符串,然后再将unicode字符串转换成cp936字符传,但实际编码是utf8,这里存在问题,utf-8的字符串当作cp936字符串先转换为unicode,再转换回来,会出错,也就是说这种转换是不可逆的,utf8编码的一些字节在cp936编码中是不存在的,也就是说在utf-8字符串当作cp936字符串转unicode的过程中,某些字节会被丢掉,再从unicode转换到cp396时就找不回原来的字节了。故此转换会出错。

解决该问题的办法,修改git函数如下所示:

1
2
3
4
5
6
7
8
9
10
proc git {args} {
set fd [eval [list git_read] $args] 
fconfigure $fd -translation binary -encoding utf-8
set result [string trimright [read $fd] "\\n"]
close $fd
if {$::_trace} {
puts stderr "< $result"
}
return $result
}

eval [list git_read] $args会执行git命令,并将命令输出保存到一个文件里,然后返回该文件的指针,这里用fd变量接收文件指针返回值,fconfigure $fd -translation binary -encoding utf-8设置从fd读取时的编码为utf-8,这样调用read $fd时读取的字符串被直接转换为unicode编码。

TortoiseGit的Bug

第2个Bug是TortoiseGit的Bug,也是编码引起的问题。在googlecode上提交了该Bug:

https://code.google.com/p/tortoisegit/issues/detail?id=2453

GetAdminDir.cpp中有一个函数GetAdminDirPath,其中有几行代码:

1
2
3
4
5

CStringA gitPathA(buffer.get(), length);

CString gitPath = CUnicodeUtils::GetUnicode(gitPathA.Trim().Mid(8)); // 8 = len("gitdir: ")

buffer的内容是从子模块目录下的.git文件读取的字符串,示例: gitdir: ../../.git/modules/Documents/技术资料

读取到该字符串后,需要把该字符串转换成unicode字符串,并去掉前面的”gitdir:”, gitPathA.Trim()针对某些特定utf-8字符无法正常处理,导致转换的unicode字符串有问题。

解决方案:

1
2
3
CString gitPath = CUnicodeUtils::GetUnicode(gitPathA);
// trim after converting to UTF-16, because CStringA trim does not work when having UTF-8 chars
gitPath = gitPath.Trim().Mid(8); // 8 = len("gitdir: ")

最后编译生成安装包,重新安装即可。

目前TortoiseGit的维护者已经改好这个Bug,下个版本即可修复这个问题。

总结

不论是tcl脚本还是C++语言,处理字符编码都很容易出问题,相对来说用Java编程很少需要考虑字符编码问题。平常我们不管用什么语言编程,面对字符编码问题,可参考以下一些准则:

  • 1)程序内部用unicode编码,java,C++,tcl多是如此,从外部获得的字符串先了解它的编码,然后按照该编码转unicode的规则,将其转换为unicode保存在内存里
  • 2)如果是命令行形式的可执行程序,最好输出utf8编码的字符串,其它程序读取该命令的输出时可按照utf8转unicode的规则转换成unicode字符串
  • 3)windows系统显示字符串时,可获取系统默认编码,然后按照unicode转默认编码的规则转换后再交给GUI显示
  • 4)程序配置文件最好使用utf8编码保存,Git for windows的默认gitconfig配置文件是以系统默认编码保存的,导致我们保存中文配置后,在Git设置对话框里看到的是乱码
  • 5)网络通信时建议使用utf8编码传输,因为不用考虑字节序的问题。双字节编码的编码方式需要考虑字节序的问题,像gb2312,unicode,而utf8是多字节编码的方式,可能是1个字节,也可能是2个字节(希腊字母),也可能是3字节(汉字),无字节序。
  • 6)linux开发时推荐使用iconv进行编码转换,不需要依赖系统库,并且支持的编码方式非常多。
  • 7)交叉编译开发时,如git for windows项目,源码需用linux编译工具链编译,会生成windows上的可执行程序,建议使用iconv进行字符编码转换。git for windows源码正是使用iconv进行字符编码转换的。
  • 8)linux上使用mbstowcs将多字节字符串转换为宽字符串(unicode字符串)时,最好先确定系统是否能很好地支持该函数,Android的C库对该函数支持不好,故此Android上不能使用该函数。因此也推荐使用iconv进行编码转换。infozip便使用了mbstowcs函数进行字符编码转换,需关闭unicode支持。busybox在libbbb目录下的unicode.c里实现了mbstowcs字符串,可将utf8编码字符串转换为unicode字符串。但是busybox并不支持其它编码向utf8编码或者unicode编码转换。
  • 9)windows开发时推荐使用WideCharToMultiByte和MultiByteToWideChar进行转换。

¥打赏5毛

取消

感谢您的支持,我会继续努力的!

扫码支持
赏个5毛,支持我把

打开支付宝扫一扫,即可进行扫码打赏哦

本篇目录