作者:小猪
11390字节
点击:61853
回复:3290
所属分类:挨踢的人
2月份将手上的项目从svn转移到git,总的来说是非常成功的,甚至可以说,这是一次极具先见之明的神来之笔。当时转移到git主要基于两点理由:
1 项目组分散在两个办公地点,而且其中一个地点无法进行物理网络连接,只能通过邮件交换文件。
2 在可以预见到的进展中,项目极有可能需要进行多分支发布,这在后来的确成为现实。
之所以称之为神来之笔,是因为在3月份的大地震后,我们项目组集体跑路到大阪去了,后来又流浪到沈阳,在这个过程中,因为git的分布式特性,给我们带来了极大的方便,随时在任何地方,随便找个开发成员的机器开个共享目录,把本地git push过去,就可以开始开发了。
我因为曾经使用过hg,网上的资料都说hg简单易用好理解,但很不幸,我曾经使用了两个月hg,除了最简单的pull,push之外,我完全没有理解hg的工作模式,甚至于rebase都让我一头雾水,至于更复杂的queue,那是什么?鉴于使用hg的非良好体验,我决定试试git。网上的资料都说git学习困难,难于理解,然而,我却很容易的理解了git的工作流程和模型,两三天后,我就可以自如的处理一些常见问题了。这个时候,我认为,的确git比hg更适合我的需求,在多分支的管理上,git的确更为方便。
就目前来讲,git主要存在以下几个问题:
1 多语言支持的问题,我不知道在linux平台上git的表现如何,看代码基本上是基于utf-8在处理,估计问题不大,然而不幸的是windows并不支持utf-8的文件路径,因此会遇到很多问题。最常见的是多字节文件路径无法正常处理,因此,选用git,必须很小心的保证,资料库中只使用英文文件名。
2 仍然是源于第一点的问题,git基本上是一堆可执行文件和shell的混合物,他们会通过文件管道,临时文件等交换数据,组合起来完成各种功能,因此经常会在各种命令中看到乱码输出,如果使用tortoisegit,这个现象会稍好一点,但仍然很频繁,不过还好,基本不影响使用,除了一个比较严重的问题:patch的导入。我的使用体验是,如果不使用patch的导入导出功能,再注意不要使用非英文文件路径的话,windows上的git+tortoisgit,已经非常完善了,可以平滑的完成各项工作而没有任何问题。
3 详细说明一下patch的问题,目前git在处理patch上,带多字节语言注释的patch不能正常导入,这里其实是两个分别的bug,其中一个bug是windows上特有的,如第2点所述,git是一堆可执行文件和shell的混合物,git的一个执行程序在通过标准输出将某些信息传递给shell,shell在存到临时文件中,然后再传递给另一个可执行程序,这时候出现了utf-8编码不能被windows正常处理的问题,我自己打了个补丁解决了这个问题,就是在shell中不通过临时文件而是直接通过管道将数据传递过去,我把这个补丁提交到了msysgit,但是后来好像被他们给删掉了,原因不明,看那帮人的提交log,他们好像是打算给git追加unicode支持来完美的解决这个问题,如果是这样,我只能期待他们好运了。。。。下个版本如果还有问题,我把那个修改提交到git去,懒得跟msys那帮德国人叽歪。另外一个bug,是git本身的bug,git的patch文件其实是一个mail文件,rfc多少来着,懒得查了,但git本身没有正确的实现rfc,所以如果有多行的多字节语言注释,导出的patch文件是没法正确导入的。我提交了一个补丁到git,很不幸,又被干掉了,我的补丁完全是针对问题给出的一个临时解决方案,维护者认为他们需要一个完整的符合rfc的解决方案,然后,讨论中的另外一个哥们儿提交了一个他认为完整的解决方案,我没看懂他的代码,没发表意见,然后,地震了。。。我跑路了。。。就不知道下文了。。。。这个问题倒是很容易绕开,写注释的时候,一定记住,第二行一定要是空行,这样就可以避开这个bug,产生一个可以正常导入导出的patch文件。如果万一忘记了,手工修改导出的patch也不算是件麻烦事。要么简单的把所有注释作为邮件的subject,或者手工把其中一部分作为subject,其他的部分作为body,导出一个多行的英文注释的patch,很容易参照修改。如前所述,我迁移到git的重要原因就是希望通过邮件交换patch,这两个bug花费了我两到三天的时间才得到解决。
4 换行符,git不能正确的处理换行符,我不知道是我的使用上的问题还是git的确就有这个问题。一开始使用git的时候,因为不太明白,犯了一个错误,设置了git的换行符自动转换功能,它将提交到服务器上的代码换行符全部替换成unix,取到本地的文件全部替换为windows,然后我们意识到这个错误,修改了这个设置,然后,经过一系列混乱的提交后,我们的代码的换行符完全的乱掉了。。。这个不小心给我们带来了长期的困扰。我可以手工把一个文件的换行符改为unix,然后重新提交到服务器上,但是很不幸,无论是通过merge或者cherry pick,还是导出patch文件,git都无法正常的将换行符的修改反映到别的分支上去,因此,我们不得不在多个分支上一个文件一个文件的修改换行符,然后提交,然后打tag来标记各个分支的状态一致。这个教训的结论就是,不要使用git的换行符自动转换功能,并且,尽可能保证代码的换行符都是unix。
5 资料库的一致性维护。我们一直都维护着两个独立的资料库,如果没有意外,每个资料库上的commit id应该是一致的,但是很不幸,因为很多意外,比如第4点换行符的影响,它们的提交历史有一些微妙的差异,然后,麻烦的事情产生了,就是两边的commit id总是不一致,这个问题,倒也不能完全怪git,我希望,tortoisgit的下一个版本,能够更好的支持gerrit,主要是自动产生gerrit id,我还没有使用gerrit的打算,但是至少我可以用gerrit id在两个资料库上标识同一个提交。另外一个额外的麻烦是因为两边的commit id总是不一致,因此我们需要经常确定两个资料库是不是完全一致。我们通常的做法是用ls-tree命令产生一个hash列表,然后对比两边的hash列表是否一致,这个方法很有效,但是我希望能够有一个更简单的办法。。。
6 tortoisegit一个谈不上严重的严重bug。使用tortoisegit,cherry pick有时不能正常工作,这个本身不算严重,失败了再pick一次就是,也就是麻烦点,也还算好。严重的是,rebase实际上是reset + cherry pick + reset,cherry pick失败的后果就是所有打算rebase的修改全部丢失。可以简单的手工找回来,但是多少是个麻烦事儿。
http://code.google.com/p/tortoisegit/issues/detail?id=798,这里,可以参考一下。
接下来,谈谈git使用中的一些体会和经验:
1 开发分支。通常来说,git的教材都推荐一个标准模式,即针对origin上的master,在本地建一个同步的分支master,再建一个开发分支local。我个人也非常赞同这个做法,目前,我在项目中推广的标准流程是,将所有本地开发所需要的环境设置等全部提交到本地local分支,这些提交不需要反映到master,因为这是开发人员本地开发的配置相关的修改。然后,比如bug修正,在本地local分支提交后,切换到master分支,将local上的修改cherry pick到master,然后push到服务器,如果有冲突,在master上做rebase,然后再重新push。然后切回local分支进行下一次开发任务。如果master有新的修正,简单的把master merge到local分支即可。git教材上会提到stash功能,我认为在建立本地开发分支的情况下,是完全不需要stash功能的。只有在master上直接开发的情况,才会有可能需要用到stash,而实际上,stash功能是有危险的,在stash pop的时候,git不会发现冲突而是简单的把本地文件覆盖掉,开发人员如果不加小心,很有可能会将基于旧版本的文件覆盖本地文件,然后提交到服务器,在我们的实际使用中,也的确出现过这样的事情。另外一个问题是,本地开发分支命名的问题,参照后面关于备份资料库的说明。
2 保持工作区的整洁。使用开发分支的一个好处是,可以随时保持工作区和本地资料库是同步的状态,而不会在工作区留下未提交的修改,因为我们可以很自由的将未提交的修改提交到开发分支后再切换到别的分支去做别的事情。切回开发分支后,可以简单的mix reset就回到切换前的状态,比stash要安全得多。在工作区不整洁的情况下,通常会遇到两个意外,第一,是未提交的修改被带到其他分支状态下,然后产生意外的运行结果,第二,未提交的修改与试图切换的目标分支 有冲突,从而导致分支切换失败。第二点无所谓,第一点是会产生非常严重的后果,可能导致修正被提交到错误的分支。因此,一定要在项目组中培养良好的习惯:切换分支前检查工作区一定是干净整洁的,没有任何未提交修改。
3 一个提交只做一件事情。这个一般不用特别强调,所有的git教材都会强调这一点。为了完整,我就在这里列举一下。使用git的时候,如果不养成良好的提交习惯,基本上会没法进行正常的开发工作。 一个提交中,应该只有跟该次开发任务相关的提交,如果一次完成了多件事情,那么应该分成多个提交。在开发初期,这个的确无所谓,但一旦进入稳定期,如果在一个提交中包含多个bug修正或者特性追加,哦,好吧,我不会拿刀 捅他,我会告诉他,其中某个修改了20个文件的bug修正需要提前反映到另一个分支。我觉得git比svn好的是,我在svn中混搭着提交多个文件,一次提交多个开发任务的成果,除了会让履历变得混乱一点之外,通常不会带来更严重的后果,而git的时候,因为我们维持多个分支是如此平常的事情,所以,我们随时会试图把某个修正单独的反映到某个分支上去。这会强迫我养成良好的习惯。
4 尽可能保持各个分支的单线提交历史。大多数开发人员的大脑内存是有限,他们通常不能很好的理解多个版本历史线的意义和相互关系,会被如同蛛网一般的合并履历搞得晕头转向。如果你不希望通过复杂的提交履历图来检验开发人员的智商并作为下一次调薪的依据的话,我强烈建议尽可能的使用cherry pick在各个分支间同步代码而不是merge。我现在的做法是,在master稳定前,一个长开发周期的新的特性分支会通过merge得到master的最新修正,但一旦master进入稳定状态而只剩下bug修正之后,其他的分支不会再从master做merge,master的bug修正会通过cherry pick同步到各个分支,而对于前面已经做过数次merge的长周期新特性分支,我们会将它backup起来以便查找履历,然后从master的稳定点开一个新分支,然后将特性分支squash merge到新分支上,做一个新的提交,然后将这个新的分支作为特性分支继续下去,从这个点开始,就不会有merge了,只有cherry pick。
5 随时关注履历。使用git,开发人员应该尽可能的关注git各个分支的变动情况,既然我们尽可能的保证master的单线提交历史,那么当意外的在本地的master分支上发现merge后,通常是由于服务器进行了forece update造成的(这应该尽量避免,但也难免会出现)或者其他可能的原因,应该分析并及时报告情况。另外,虽然开发分支local总是从master分支merge过来的,但是我们无法保证因为意外的原因,local上有未提交到master的修正,为避免这种意外,开发人员应该定期比较local和master的差异,保证只有配置文件之类的开发配置文件的不同而没有其他任何实质性差异。
6 release分支。release分支是绝对必须的,master上的修正并不意味着一定会进入release流程,对于一个维护期的项目来说,release分支是必不可少的,而且,master上的bug修正,由于测试出新的bug等原因,有时会出现好几次提交,将其重新整理成一个完整的commit再反映到release分支上我认为是有好处的。
7 提交的整理。如前所述,master上的修正,并不一定是需要release的,我以前的做法是做一个backup的分支,把这个修正备份,然后reset本地的master,然后再force update服务器上的资料库,另外,当发现有错误的提交之类的,我也都采用这样的手段来修正。后来发现这样做是有严重的问题的,因为我强行reset了服务器上的master head,其他开发人员会在不知情的情况下,pull到本地形成一次merge,然后再不知情的情况下,将本地merge后的结果再push到服务器上,导致已经被删除的提交再次被反映上去。正确的做法是,当发现有需要取消的提交的时候,简单的通过git的revert功能产生一个新提交回退掉该次修改即可,这样子的额外的好处是不需要为被回退的修改建立备份,master分支上自然的保存着备份。通过同样的手段,可以很轻易的将多个提交回退掉过后整理成一个提交重新提交(两次revert ^_^)。
8 备份资料库。我们都知道git这样的分布式版本系统,是不需要备份的,我这里说的备份,是定语,而不是动词。随着开发的进行,服务器上的分支会越来越多,很多时候,会给开发人员带来困扰。我现在的做法是建立一个备份用的git资料库,将一些陈旧的分支push上去,而将开发库中的陈旧分支删除掉,这样开发库会显的比较干净。另外的好处是,开发人员某些时候会因为比如更换电脑,临时交换代码等原因,需要将自己的本地开发分支临时备份或者共享,也可以很方便的push到备份库上而不会让开发库显得混乱。这里牵涉到另外一个问题,关于本地开发分支命名,git教材上一般推荐local或者development,考虑到临时共享等原因,我给项目组的建议是,个人名字缩写加上dev或者local后缀,这样,服务器上临时共享的时候会比较方便。
9 开发人员的教育。git的使用非常灵活,同样的任务,有多种完成方法,但如同灵活强大的c语言一样,灵活必然带来风险,因此在项目组中推行统一的工作流程是绝对必要的,很不幸的是,我们的开发人员都是会偷懒的,而每一个偷懒的人都认为自己是git专家。。。我写这篇文章很重要的原因,就是写给我们项目组的人看的,同学们,你们看到了吗。。。我无意指责我们的开发人员,因为到上周为止,我们手上总共有三个大的分支在并行开发,按照第一点的意见,建立本地的同步分支,再建立本地的开发分支,再加上origin上的分支,多数开发人员需要在大脑里面构建9个分支的状态,我承认,这是有些累。。。但是同学们啊,我手上还多一个release分支,10个呀。。。。
最后,写下这篇文章,一方面,是想给项目组的人总结一下git使用的一些注意事项,另一方面,也希望能够共享出来,跟同样使用git的人交换一下经验教训。欢迎拍砖。