面试的时候,我问过很多人,你怎么理解git?对于那些有svn使用经验的童鞋,我则往往还追问,svn跟git有什么不同。
听到过很多不同的回答,但似乎还没有一个回答与我一致。这里,写写我对git的理解。
(做为面试题目,我期望的不是听到一个“正确的回答”,我期望了解到的是,候选人为什么这么回答,以求对候选人有更全面的了解。)
git commit / push / pull 等等操作,现在有玩github的工程师应该都了解;但这些仅仅是了解git的基本使用。
还有git merge / rebase / cherry-pick等等操作;还有local / remote等的区别。
我会觉得,了解或者说知道一个工具的使用,并不足够。更加需要理解的,是一个工具为什么被设计成为这样。
需要理解工具背后的设计理念。
修改 vs 版本
git管理的是修改(changeset),而svn管理的是版本(revision)。
在这点上,git跟hg的设计理念是一致的,Joel Spolsky为推广hg,曾经写过一篇很好的文章。
源码,经过修改,可以得到版本。当我们使用git的时候,我们需要把注意力,或者说,关注点放在修改上,而不是修改之后的版本。
就好像装修房子,每添置一件家具都是一个修改:床、沙发、餐桌、衣柜、书架。
假设说这五件家具都买齐后,我们可以得到一个装修完毕之后的版本。
家具的添置,是有顺序的,假设便是床、沙发、餐桌、衣柜、书架这么顺序;那么如果我们以“版本”为单位去思考的话,便会有五个版本:
- 床、
- 床、沙发
- 床、沙发、餐桌
- 床、沙发、餐桌、衣柜
- 床、沙发、餐桌、衣柜、书架
而如果以修改为单位去思考的话,便会有五个修改:
- 床
- 沙发
- 餐桌
- 衣柜
- 书架
我们往往可以方便调整修改的顺序、个数,以达到不同的修改组合-版本。
比方说,我们要一个只有沙发跟书架的“版本”,那么开一个新的分支,引入:
- 沙发
- 书架
两个修改就好了。
而如果我们是用“版本”去思考,那么是很难(仅仅是“很难”,不是不可能)得出新的“版本组合”的。
这便是“面向修改思考”得到的便利。
git是源码管理工具,而git(以及hg等)认为,管理修改,要比管理版本灵活、便利、强大得多。
很多习惯svn的思维工程师,便是在这点绕不过来,他们总是习惯性的去想“发布哪个版本?”,而不是发布“包含哪些修改的分支?”。
要看使用的是何种思维,可以看看平时讨论的时候,是提到分支比较多,还是提到版本比较多。
历史 vs 管理
git是源码管理工具。源码,是需要工具来管理。
有管理的源码,跟没管理的源码,有什么区别?处女座会说,区别很大。
软件的源码,需要有整理、规范的进行变更,它的修改历史,应该是清晰的。
今天加个功能A、B、C,明天发布了版本1.4,后天做了bugfix x/y/z等等,整个过程都应该有规范的记录。
所有的提交,都应该是有“分支”,分支的使用可以参考gitflow。
分支内,比方说实现某个新功能的feature 分支,内部可以有多个步骤,这个步骤先整理代码,那个步骤再实现功能,然后再添加配置等等等等。
所有的提交,都应该是“原子提交”:包含并且仅包含某个该步骤应有的所有操作。
这样可以使源码的演变变得非常便于管理,非常容易追溯bug,查阅某段代码是什么时候因为何故被添加。
关键在于,工程师开发的时候,肯定不是严格照着这样规范的方式编写代码。
我们经常是在写一段代码的时候,发现还有别的地方可以改一改,然后就顺手改了。
代码的真实修改历史,必然是杂乱无章的。
SVN的设计哲学是认为,代码修改历史是神圣的,源码管理工具必须永远真实的把全部修改历史都记录下来;它的任务是记录历史,杜绝一切历史被篡改的可能。
git则认为,历史是任人打扮的姑娘;只要历史能变得漂亮,要怎么改都行!所以,git会提供各种个样的命令,方便工程师把代码变更历史变成我们想要的样子。
gitflow里面描述的开发流程,看起来很美,但那往往不是真实发生的修改流程;但是,git会提供命令,方便我们把代码变更修改成为gitflow所描述的那样。
然后,代码变更历史,就会有一个个feature / release / hotfix等等,非常清晰,明了,便于追溯、管理。
漏提交了文件,提交不是原子的怎么办? git commit --amend
修改的顺序不对怎么办? git rebase -i
曾经有两个项目,开始的时候是独立的,git仓库也是独立的,但后来(一年多后)慢慢变成一个。
类似于我们做代码重构,两个原先独立的类耦合度越来越高,重构时可以合并成为一个类会更简单。
把其中一个代码仓库做为一个子目录合并到另一个仓库中,然后,使用git filter-branch,修改该子目录下的所有提交历史,让它看起来就好像从一开始就在该路径下一样。
然后,我们得到了有两个修改起点的git仓库。
git对源码变更历史的修改能力可以达到这样。
有的人对源码的提交,分支的管理都处理得很随意,他们关心的仅仅是代码最终的版本是怎么样的,而不关心代码是如何变成它现在的样子;源码管理工具,在他们手上,仅仅是“源码备份工具”;copy & paste目录就好了呀,为什么还要使用git?为什么还要关心能否使用git bisect来快速定位bug?
分布式 vs 快
讲了这么多,我还完全没有提到git所谓的“分布式”概念,因为在我看来,git是否是分布式与否,并不是关键,“分布式”仅仅是一个实现细节,本地可以包含整个仓库,可以在本地完成所有的git操作,这样所有的git操作都可以很快。
操作很快才是目的,而“分布式”是为了达到这个目的而采取的实现细节,如果远程操作可以比本地操作更快,那么,我认为git也完全可以是集中式的。
按我的理解,"快"才是git的设计理念。Linus说,他刻意追求让git开新分支这个操作变得无比快(只要写入41个字节),因为只有一个操作无比的快,人们才有可能频繁的是使用它。分支操作无比快,工程师才会使用基于分支的开发。
hg在很多地方都跟git很像,但是hg不够快。
在windows上使用sourcetree去操作git,很慢,开个分支都要等,这怎么能忍呢?
网友评论