面試的時(shí)候,我問過很多人悴能,你怎么理解git揣钦?對(duì)于那些有svn使用經(jīng)驗(yàn)的童鞋,我則往往還追問漠酿,svn跟git有什么不同冯凹。
聽到過很多不同的回答,但似乎還沒有一個(gè)回答與我一致炒嘲。這里宇姚,寫寫我對(duì)git的理解。
(做為面試題目夫凸,我期望的不是聽到一個(gè)“正確的回答”空凸,我期望了解到的是,候選人為什么這么回答寸痢,以求對(duì)候選人有更全面的了解。)
git commit / push / pull 等等操作紊选,現(xiàn)在有玩github的工程師應(yīng)該都了解啼止;但這些僅僅是了解git的基本使用。
還有g(shù)it merge / rebase / cherry-pick等等操作兵罢;還有l(wèi)ocal / remote等的區(qū)別献烦。
我會(huì)覺得,了解或者說(shuō)知道一個(gè)工具的使用卖词,并不足夠巩那。更加需要理解的吏夯,是一個(gè)工具為什么被設(shè)計(jì)成為這樣。
需要理解工具背后的設(shè)計(jì)理念即横。
修改 vs 版本
git管理的是修改(changeset)噪生,而svn管理的是版本(revision)。
在這點(diǎn)上东囚,git跟hg的設(shè)計(jì)理念是一致的跺嗽,Joel Spolsky為推廣hg,曾經(jīng)寫過一篇很好的文章页藻。
源碼桨嫁,經(jīng)過修改,可以得到版本份帐。當(dāng)我們使用git的時(shí)候璃吧,我們需要把注意力,或者說(shuō)废境,關(guān)注點(diǎn)放在修改上畜挨,而不是修改之后的版本。
就好像裝修房子彬坏,每添置一件家具都是一個(gè)修改:床朦促、沙發(fā)、餐桌栓始、衣柜务冕、書架。
假設(shè)說(shuō)這五件家具都買齊后幻赚,我們可以得到一個(gè)裝修完畢之后的版本禀忆。
家具的添置,是有順序的落恼,假設(shè)便是床箩退、沙發(fā)、餐桌佳谦、衣柜戴涝、書架這么順序;那么如果我們以“版本”為單位去思考的話钻蔑,便會(huì)有五個(gè)版本:
- 床啥刻、
- 床、沙發(fā)
- 床咪笑、沙發(fā)可帽、餐桌
- 床、沙發(fā)窗怒、餐桌映跟、衣柜
- 床蓄拣、沙發(fā)、餐桌努隙、衣柜球恤、書架
而如果以修改為單位去思考的話,便會(huì)有五個(gè)修改:
- 床
- 沙發(fā)
- 餐桌
- 衣柜
- 書架
我們往往可以方便調(diào)整修改的順序剃法、個(gè)數(shù)碎捺,以達(dá)到不同的修改組合-版本。
比方說(shuō)贷洲,我們要一個(gè)只有沙發(fā)跟書架的“版本”收厨,那么開一個(gè)新的分支,引入:
- 沙發(fā)
- 書架
兩個(gè)修改就好了优构。
而如果我們是用“版本”去思考诵叁,那么是很難(僅僅是“很難”,不是不可能)得出新的“版本組合”的钦椭。
這便是“面向修改思考”得到的便利拧额。
git是源碼管理工具,而git(以及hg等)認(rèn)為彪腔,管理修改侥锦,要比管理版本靈活、便利德挣、強(qiáng)大得多恭垦。
很多習(xí)慣svn的思維工程師,便是在這點(diǎn)繞不過來(lái)格嗅,他們總是習(xí)慣性的去想“發(fā)布哪個(gè)版本番挺?”,而不是發(fā)布“包含哪些修改的分支屯掖?”玄柏。
要看使用的是何種思維,可以看看平時(shí)討論的時(shí)候贴铜,是提到分支比較多粪摘,還是提到版本比較多。
歷史 vs 管理
git是源碼管理工具绍坝。源碼徘意,是需要工具來(lái)管理。
有管理的源碼陷嘴,跟沒管理的源碼,有什么區(qū)別间坐?處女座會(huì)說(shuō)灾挨,區(qū)別很大邑退。
軟件的源碼,需要有整理劳澄、規(guī)范的進(jìn)行變更地技,它的修改歷史,應(yīng)該是清晰的秒拔。
今天加個(gè)功能A莫矗、B、C砂缩,明天發(fā)布了版本1.4作谚,后天做了bugfix x/y/z等等,整個(gè)過程都應(yīng)該有規(guī)范的記錄庵芭。
所有的提交妹懒,都應(yīng)該是有“分支”,分支的使用可以參考gitflow双吆。
分支內(nèi)眨唬,比方說(shuō)實(shí)現(xiàn)某個(gè)新功能的feature 分支,內(nèi)部可以有多個(gè)步驟好乐,這個(gè)步驟先整理代碼匾竿,那個(gè)步驟再實(shí)現(xiàn)功能,然后再添加配置等等等等蔚万。
所有的提交岭妖,都應(yīng)該是“原子提交”:包含并且僅包含某個(gè)該步驟應(yīng)有的所有操作。
這樣可以使源碼的演變變得非常便于管理笛坦,非常容易追溯bug区转,查閱某段代碼是什么時(shí)候因?yàn)楹喂时惶砑印?/p>
關(guān)鍵在于,工程師開發(fā)的時(shí)候版扩,肯定不是嚴(yán)格照著這樣規(guī)范的方式編寫代碼废离。
我們經(jīng)常是在寫一段代碼的時(shí)候,發(fā)現(xiàn)還有別的地方可以改一改礁芦,然后就順手改了蜻韭。
代碼的真實(shí)修改歷史,必然是雜亂無(wú)章的柿扣。
SVN的設(shè)計(jì)哲學(xué)是認(rèn)為肖方,代碼修改歷史是神圣的,源碼管理工具必須永遠(yuǎn)真實(shí)的把全部修改歷史都記錄下來(lái)未状;它的任務(wù)是記錄歷史俯画,杜絕一切歷史被篡改的可能。
git則認(rèn)為司草,歷史是任人打扮的姑娘艰垂;只要?dú)v史能變得漂亮泡仗,要怎么改都行!所以猜憎,git會(huì)提供各種個(gè)樣的命令娩怎,方便工程師把代碼變更歷史變成我們想要的樣子。
gitflow里面描述的開發(fā)流程胰柑,看起來(lái)很美截亦,但那往往不是真實(shí)發(fā)生的修改流程;但是柬讨,git會(huì)提供命令崩瓤,方便我們把代碼變更修改成為gitflow所描述的那樣。
然后姐浮,代碼變更歷史谷遂,就會(huì)有一個(gè)個(gè)feature / release / hotfix等等,非常清晰卖鲤,明了肾扰,便于追溯、管理蛋逾。
漏提交了文件集晚,提交不是原子的怎么辦? git commit --amend
修改的順序不對(duì)怎么辦区匣? git rebase -i
曾經(jīng)有兩個(gè)項(xiàng)目偷拔,開始的時(shí)候是獨(dú)立的,git倉(cāng)庫(kù)也是獨(dú)立的亏钩,但后來(lái)(一年多后)慢慢變成一個(gè)莲绰。
類似于我們做代碼重構(gòu),兩個(gè)原先獨(dú)立的類耦合度越來(lái)越高姑丑,重構(gòu)時(shí)可以合并成為一個(gè)類會(huì)更簡(jiǎn)單蛤签。
把其中一個(gè)代碼倉(cāng)庫(kù)做為一個(gè)子目錄合并到另一個(gè)倉(cāng)庫(kù)中,然后栅哀,使用git filter-branch震肮,修改該子目錄下的所有提交歷史,讓它看起來(lái)就好像從一開始就在該路徑下一樣留拾。
然后戳晌,我們得到了有兩個(gè)修改起點(diǎn)的git倉(cāng)庫(kù)。
git對(duì)源碼變更歷史的修改能力可以達(dá)到這樣痴柔。
有的人對(duì)源碼的提交沦偎,分支的管理都處理得很隨意,他們關(guān)心的僅僅是代碼最終的版本是怎么樣的,而不關(guān)心代碼是如何變成它現(xiàn)在的樣子豪嚎;源碼管理工具鸿捧,在他們手上,僅僅是“源碼備份工具”疙渣;copy & paste目錄就好了呀,為什么還要使用git堆巧?為什么還要關(guān)心能否使用git bisect來(lái)快速定位bug妄荔?
分布式 vs 快
講了這么多,我還完全沒有提到git所謂的“分布式”概念谍肤,因?yàn)樵谖铱磥?lái)啦租,git是否是分布式與否,并不是關(guān)鍵荒揣,“分布式”僅僅是一個(gè)實(shí)現(xiàn)細(xì)節(jié)篷角,本地可以包含整個(gè)倉(cāng)庫(kù),可以在本地完成所有的git操作系任,這樣所有的git操作都可以很快恳蹲。
操作很快才是目的,而“分布式”是為了達(dá)到這個(gè)目的而采取的實(shí)現(xiàn)細(xì)節(jié)俩滥,如果遠(yuǎn)程操作可以比本地操作更快嘉蕾,那么,我認(rèn)為git也完全可以是集中式的霜旧。
按我的理解错忱,"快"才是git的設(shè)計(jì)理念。Linus說(shuō)挂据,他刻意追求讓git開新分支這個(gè)操作變得無(wú)比快(只要寫入41個(gè)字節(jié))以清,因?yàn)橹挥幸粋€(gè)操作無(wú)比的快,人們才有可能頻繁的是使用它崎逃。分支操作無(wú)比快掷倔,工程師才會(huì)使用基于分支的開發(fā)。
hg在很多地方都跟git很像婚脱,但是hg不夠快今魔。
在windows上使用sourcetree去操作git,很慢障贸,開個(gè)分支都要等错森,這怎么能忍呢?