附录C Git与SVN面对面
C.1 面对面访谈录
Git:我的提交历史本身就是一幅美丽的图画——DAG(Directed Acylic Graph,有向无环图),可以看到各个分支之间的合并关系。而你SVN,你的提交历史怎么是一条直线呢?要是在重症监护室看到你,还以为你挂掉了呢?
SVN:我觉得挺好,至少我每次提交会有一个全局的版本号,而且我的版本号是递增的。你的版本号不是递增的吧?
Git:你说的对,我的版本号不是一个简单递增的数字,而是一个长达40位的十六进制数字(哈希值),但是可以使用短格式,只要不冲突。虽然我的提交编号看起来似乎是无序的,但实际上我的每一个提交都记录了父提交甚至是双亲或多亲提交,因此可以很容易地从任意一个提交开始建立一条指向历史提交的跟踪链。
SVN:是啊,我的一个提交和前一个提交有时根本没有关系,例如一个提交是发生在主线/trunk中的,下一个提交可能就发生在/branches/1.3.x分支中。你要知道,要想画出一个像你那样的分支图,我要做多少工作吗?我不容易呀。
Git:我一直很奇怪,你的分支和里程碑怎么看起来和目录一样?我的分支和里程碑名字虽然看起来像是目录,但实际上和工作区的目录完全没有关系,只是对提交ID的一个记号而已。
SVN:我一开始觉得我用轻量级拷贝的方式实现分支和里程碑会很酷,也很快。但是我发现很多人在使用我的时候,直接在版本库的根目录下创建文件而不是把文件创建在/trunk目录下,这就导致这些人无法再创建分支和里程碑了,因为无法将根目录拷贝到子目录呀!
Git:那么你是如何对分支合并进行跟踪的呢?因为我有DAG的提交关系图,很容易就可以看出分支之间的合并历史,但是你是怎么做到的呢?
SVN:我用了一点小技巧,通过属性(svn:mergeinfo)记录了合并的分支名和版本范围,这样再合并的时候,我会根据相关属性确定是否要合并。但是如果经常在子目录下合并,会有太多的svn:mergeinfo属性等待我检查,我会很困扰。还有我的这个功能是在1.5以后的版本才提供的,因此老版本会破坏这个机制。
SVN:对了,我的属性能干很多事哦,我甚至可以把我的照片作为属性附加在文件上。
Git:这点我承认,你的属性非常强大。其实我也支持属性,只不过实现方式不同罢了。而且我可以通过评注的方式为任意对象(提交、文件、里程碑等)添加评注,也可以实现把照片作为评注附加在文件上,可是这个功能有什么实际用处呢?
SVN:我有轻量级拷贝,而我的分支和里程碑就是通过拷贝实现的,很强大哦。
Git:我根本就不需要轻量级拷贝,因为我对文件的保存和文件的路径无关,我只关心内容。所以相同内容的文件无论它们的文件名相差有多大,在我这里只保存一份。而你SVN,如果用户忘了用轻量级拷贝,版本库是不是负担很重啊。
SVN:听说你不能针对目录授权,这可是我的强项,所以公司无论大小都在用我作为版本控制系统。
Git:不要说你的授权了,简直是一团糟。虽然这本书的作者为你写了一个图形化的授权管理工具[1],对你的授权会有所改善,但是你糟糕的分支和里程碑的实现,会导致授权在新的分支和里程碑中又要逐一进行设置,工作量其大无比。虽然泛路径授权是一个解决方案,但是官方并没有提供啊。
Git:说说我的授权吧。如果你认真地读过本书服务器架设的相关章节,你会为我能够提供按照分支,以及按照路径进行写操作授权的功能而击掌叫好的。当然我的读操作授权还不能做到很精细,但是可以将版本库拆分成若干个小的版本库啊,再参照本书介绍的各种多版本库协同模式,也会找到一个适合的解决方案的啊。
Git:我的工作区很干净。只在工作区的根目录下有一个.git目录,此外再无其他。
SVN:我要在工作区的每一个目录下都放置一个.svn目录,这个目录在Linux下可是隐藏的哦。这个目录下不但有跟踪工作区文件状态的跟踪文件,而且还有每一个文件的原始拷贝呢。这样有的操作就可以脱离网络执行了,例如:差异比较、工作区文件的回滚。
Git:嗯,你要是像我一样能再多保存一点内容(整个版本库)就更好了。像你这样在每个工作区子目录下都有一个.svn目录,而且每个.svn目录下都有文件的原始拷贝,在进行内容搜索的时候会搜索出两份吧,太干扰了。而且你这么做和CVS一样有安全风险,造成本地文件名的信息泄漏,千万不要在Web服务器上用SVN检出哦。
Git:我的操作可以不需要网络。因为我在本地拥有完整的版本库,几乎所有操作都是在本地完成的。
SVN:正如前面说到的,我有部分命令可以不需要网络,但是其他绝大多数命令还是要依赖网络的。
SVN:你怎么没有更新(update)命令啊?还有你为什么老是要执行检出命令(checkout)?对我而言,检出命令只在工作区创建时一次性完成。
Git:你的这个问题怎么和CVS问的一样。你的更新(update)命令执行的很慢,对吧?首先你要用检出命令(checkout)建立工作区,然后你要经常执行更新(update)命令进行更新,否则很容易造成你的更改和他人的更改发生冲突。
Git:之所以你需要更新是因为你的版本库在远程啊。别忘了我的版本库是在本地,本地的版本库会随着我在本地工作区中的操作(如提交)而更新。我的检出(checkout)操作一般用于用户切换分支,或者从本地版本库检出丢失的文件或覆盖本地错误改动的文件。如果我没记错的话,你切换分支用的是svn switch命令对么?
Git:实际上我也有一个比较耗时的网络操作命令:git fetch或git pull,这两个操作是从远程版本库获取他人的改动。一般使用我(Git)做团队协作的时候,会部署一个集中共享的版本库,我就从这个共享的版本库执行拉回操作。也许你(SVN)会觉得git fetch或git pull和你的svn update命令更像吧。至于你的检出命令(svn checkout),实际上和我的克隆命令(git clone)很相似,只不过我的克隆命令不但创建了本地工作区,而且在本地还复制了和远程版本库一样的本地版本库。
SVN:为什么你的检入(commit)命令执行得那么快?
Git:是的,我的检入命令飞一般就执行完了,这也是因为我的版本库就在本地。也许你(SVN)会觉得我的推送命令(git push)和你的检入命令(svn commit)更相像,其实这是一个误会。如果我不做本地提交,是不能通过推送命令(git push)将我的本地提交共享给(推送给)其他版本库的。你(SVN)每一次的提交都要和版本库进行网络通信,而我可以在本地版本库进行多次提交,直到我的主人想喝咖啡了才执行一次git push,将我本地版本库中的新提交推送给远程版本库。
SVN:我能一次检出一个目录,你好像不能吧?
Git:所以我有子模组,以及repo等第三方工具,可以帮助我把一个大的版本库拆成多个版本库组合来使用啊。而且我还有稀疏检出的功能,只不过很少有人用到罢了。
SVN:我能添加空目录,你好像不能吧?
Git:是的,我现在还不能记录空目录,但是用户往往在空目录下创建一个隐含文件,并将该隐含文件添加到版本库中,这就实现了空目录添加的功能。
[1]http://www.ossxp.com/doc/pysvnmanager/user-guide/user-guide.html