即時(shí)分支合并是Git最給力的殺手锏。
問(wèn)題?:外部因素要求必須切換場(chǎng)景。在發(fā)布版本中突然蹦出個(gè)嚴(yán)重缺陷。某個(gè)特性完 成的截至日期就要來(lái)臨。在項(xiàng)目關(guān)鍵部分可以提供幫助的一個(gè)開(kāi)發(fā)正打算離職。所有情 況逼迫你停下所有手頭工作,全力撲到到這個(gè)完全不同的任務(wù)上。
打斷思維的連續(xù)性會(huì)使你的生產(chǎn)力大大降低,并且切換上下文也更麻煩,更大的損失。 使用中心版本控制我們必須從中心服務(wù)器下載一個(gè)新的工作拷貝。分布式系統(tǒng)的情況就 好多了,因?yàn)槲覀兡軌蛟诒镜乜寺∷枰陌姹尽?/p>
但是克隆仍然需要拷貝整個(gè)工作目錄,還有直到給定點(diǎn)的整個(gè)歷史記錄。盡管Git使用文 件共享和硬鏈接減少了花費(fèi),項(xiàng)目文件自身還是必須在新的工作目錄里重建。
方案?:Git有一個(gè)更好的工具對(duì)付這種情況,比克隆快多了而且節(jié)省空間:?git branch?。
使用這個(gè)魔咒,目錄里的文件突然從一個(gè)版本變到另一個(gè)。除了只是在歷史記錄里上跳 下竄外,這個(gè)轉(zhuǎn)換還可以做更多。你的文件可以從上一個(gè)發(fā)布版變到實(shí)驗(yàn)版本到當(dāng)前開(kāi) 發(fā)版本到你朋友的版本等等。
曾經(jīng)玩過(guò)那樣的游戲嗎?按一個(gè)鍵(“老板鍵”),屏幕立即顯示一個(gè)電子表格或別的? 那么如果老板走進(jìn)辦公室,而你正在玩游戲,就可以快速將游戲藏起來(lái)。
在某個(gè)目錄:
$ echo "I'm smarter than my boss" > myfile.txt
$ git init
$ git add .
$ git commit -m "Initial commit"
我們已經(jīng)創(chuàng)建了一個(gè)Git倉(cāng)庫(kù),該倉(cāng)庫(kù)記錄一個(gè)包含特定信息的文件?,F(xiàn)在我們鍵入:
$ git checkout -b boss # 之后似乎沒(méi)啥變化
$ echo "My boss is smarter than me" > myfile.txt
$ git commit -a -m "Another commit"
看起來(lái)我們剛剛只是覆蓋了原來(lái)的文件并提交了它。但這是個(gè)錯(cuò)覺(jué)。鍵入:
$ git checkout master # 切到文件的原先版本
嘿真快!這個(gè)文件就恢復(fù)了。并且如果老板決定窺視這個(gè)目錄,鍵入:
$ git checkout boss # 切到適合老板看的版本
你可以在兩個(gè)版本之間相切多少次就切多少次,而且每個(gè)版本都可以獨(dú)立提交。
比如你正在開(kāi)發(fā)某個(gè)特性,并且由于某種原因,你需要回退三個(gè)版本,臨時(shí)加進(jìn)幾行打 印語(yǔ)句來(lái),來(lái)看看一些東西是如何工作的。那么:
$ git commit -a
$ git checkout HEAD~3
現(xiàn)在你可以到處加丑陋的臨時(shí)代碼。你甚至可以提交這些改動(dòng)。當(dāng)你做完的時(shí)候,
$ git checkout master
來(lái)返回到你原來(lái)的工作??矗形刺峤蛔兏冀Y(jié)轉(zhuǎn)了。
如果你后來(lái)想保存臨時(shí)變更怎么辦?簡(jiǎn)單:
$ git checkout -b dirty
只要在切換到主分支之前提交就可以了。無(wú)論你什么時(shí)候想回到臟的變更,只需鍵入:
$ git checkout dirty
我們?cè)谇懊嬲鹿?jié)討論加載舊狀態(tài)的時(shí)候,曾經(jīng)接觸過(guò)這個(gè)命令。最終我們把故事說(shuō)全: 文件改變成請(qǐng)求的狀態(tài),但我們必須離開(kāi)主分支。從現(xiàn)在開(kāi)始的任何提交都會(huì)將你的文 件提交到另一條不同的路,這個(gè)路可以之后命名。
換一個(gè)說(shuō)法,在checkout一個(gè)舊狀態(tài)之后,Git自動(dòng)把你放到一個(gè)新的,未命名的分支, 這個(gè)分支可以使用?git checkout -b?來(lái)命名和保存。
你正在做某件事的當(dāng)間,被告知先停所有的事情,去修理一個(gè)新近發(fā)現(xiàn)的臭蟲(chóng),這個(gè)臭 蟲(chóng)在提交 1b6d…
:
$ git commit -a
$ git checkout -b fixes 1b6d
那么一旦你修正了這個(gè)臭蟲(chóng):
$ git commit -a -m "Bug fixed"
$ git checkout master
并可以繼續(xù)你原來(lái)的任務(wù)。你甚至可以“合并”到最新修訂:
$ git merge fixes
一些版本控制系統(tǒng),創(chuàng)建分支很容易,但把分支合并回來(lái)很難。使用Git,合并簡(jiǎn)直是家 常便飯,以至于甚至你可能對(duì)其發(fā)生沒(méi)有察覺(jué)。
我們很久之前就遇到合并了。?pull?命令取出提交并合并它們到你的當(dāng)前分支。如果 你沒(méi)有本地變更,那這個(gè)合并就是一個(gè)“快進(jìn)”,相當(dāng)于中心式版本控制系統(tǒng)里的一個(gè) 弱化的獲取最新版本操作。但如有本地變更,Git將自動(dòng)合并,并報(bào)告任何沖突。
通常,一個(gè)提交只有一個(gè)“父提交”,也叫前一個(gè)提交。合并分支到一起產(chǎn)生一個(gè)至少 有兩個(gè)父的提交。這就引出了問(wèn)題:?HEAD~10
?真正指哪個(gè)提交?一個(gè)提交可能有多個(gè) 父,那我們跟哪個(gè)呢?
原來(lái)這個(gè)表示每次選擇第一個(gè)父。這是可取的,因?yàn)樵诤喜r(shí)候當(dāng)前分支成了第一個(gè)父; 多數(shù)情況下我們只關(guān)注我們?cè)诋?dāng)前分支都改了什么,而不是從其他分支合并來(lái)的變更。
你可以用插入符號(hào)來(lái)特別指定父。比如,顯示來(lái)自第二個(gè)父的日志:
$ git log HEAD^2
你可以忽略數(shù)字以指代第一個(gè)父。比如,顯示與第一個(gè)父的差別:
$ git diff HEAD^
你可以結(jié)合其他類(lèi)型使用這個(gè)記號(hào)。比如:
$ git checkout 1b6d^^2~10 -b ancient
開(kāi)始一個(gè)新分支 “ancient” ,表示第一個(gè)父的第二個(gè)父的倒數(shù)第十次提交的狀態(tài)。
經(jīng)常在硬件項(xiàng)目里,計(jì)劃的第二步必須等第一步完成才能開(kāi)始。待修的汽車(chē)傻等在車(chē)庫(kù) 里,直到特定的零件從工廠(chǎng)運(yùn)來(lái)。一個(gè)原型在其可以構(gòu)建之前,可能苦等芯片成型。
軟件項(xiàng)目可能也類(lèi)似。新功能的第二部分不得不等待,直到第一部分發(fā)布并通過(guò)測(cè)試。 一些項(xiàng)目要求你的代碼需要審批才能接受,因此你可能需要等待第一部分得到批準(zhǔn),才 能開(kāi)始第二部分。
多虧了無(wú)痛分支合并,我們可以不必遵循這些規(guī)則,在第一部分正式準(zhǔn)備好前開(kāi)始第二 部分的工作。假設(shè)你已經(jīng)將第一部分提交并發(fā)去審批,比如說(shuō)你現(xiàn)在在主分支。那么分 岔:
$ git checkout -b part2
接下來(lái),做第二部分,隨時(shí)可以提交變更。只要是人就可能犯錯(cuò)誤,經(jīng)常你將回到第一 部分在修修補(bǔ)補(bǔ)。如果你非常幸運(yùn),或者超級(jí)棒,你可能不必做這幾行:
$ git checkout master # 回到第一部分
$ 修復(fù)問(wèn)題
$ git commit -a # 提交變更
$ git checkout part2 # 回到第二部分
$ git merge master # 合并這些改動(dòng)
最終,第一部分獲得批準(zhǔn):
$ git checkout master # 回到第一部分
$ submit files # 對(duì)世界發(fā)布
$ git merge part2 # 合并第二部分
$ git branch -d part2 # 刪除分支“part2”
現(xiàn)在你再次處在主分支,第二部分的代碼也在工作目錄。
很容易擴(kuò)展這個(gè)技巧,應(yīng)用到任意數(shù)目的部分。它也很容易追溯分支:假如你很晚才意 識(shí)到你本應(yīng)在7次提交前就創(chuàng)建分支。那么鍵入:
$ git branch -m master part2 # 重命名“master”分支為“part2”。
$ git branch master HEAD~7 # 以七次前提交建一個(gè)新的“master”。
分支?master
?只有第一部分內(nèi)容,其他內(nèi)容在分支?part2
?。 我們現(xiàn)在后一個(gè)分支; 我們創(chuàng)建了?master
?分支還沒(méi)有切換過(guò)去,因?yàn)槲覀兿肜^續(xù)工作在?part2
?。這是不 尋常的。直到現(xiàn)在,我們已經(jīng)在創(chuàng)建之后切換到分支,如:
$ git checkout HEAD~7 -b master # 創(chuàng)建分支,并切換過(guò)去。
或許你喜歡在同一個(gè)分支下完成工作的方方面面。你想為自己保留工作進(jìn)度并希望其他 人只能看到你仔細(xì)整理過(guò)后的提交。開(kāi)啟一對(duì)分支:
$ git branch sanitized # 為干凈提交創(chuàng)建分支
$ git checkout -b medley # 創(chuàng)建并切換分支以進(jìn)去工作
接下來(lái),做任何事情:修臭蟲(chóng),加特性,加臨時(shí)代碼,諸如此類(lèi),經(jīng)常按這種方式提交。 然后:
$ git checkout sanitized
$ git cherry-pick medley^^
應(yīng)用分支 “medley” 的祖父提交到分支 “sanitized” 。通過(guò)合適的挑選(像選櫻桃 那樣)你可以構(gòu)建一個(gè)只包含成熟代碼的分支,而且相關(guān)的提交也組織在一起。
列出所有分支:
$ git branch
默認(rèn)你從叫 “master” 的分支開(kāi)始。一些人主張別碰“master”分支,而是創(chuàng)建你自 己版本的新分支。
選項(xiàng)?-d?和?-m?允許你來(lái)刪除和移動(dòng)(重命名)分支。參見(jiàn)?git help branch?。
分支“master” 是一個(gè)有用的慣例。其他人可能假定你的倉(cāng)庫(kù)有一個(gè)叫這個(gè)名字的分 支,并且該分支包含你項(xiàng)目的官方版本。盡管你可以重命名或抹殺 “master” 分支, 你最好還是尊重這個(gè)約定。
很快你會(huì)發(fā)現(xiàn)你經(jīng)常會(huì)因?yàn)橐恍┫嗨频脑騽?chuàng)建短期的分支:每個(gè)其它分支只是為了保 存當(dāng)前狀態(tài),那樣你就可以直接跳到較老狀態(tài)以修復(fù)高優(yōu)先級(jí)的臭蟲(chóng)之類(lèi)。
可以和電視的換臺(tái)做類(lèi)比,臨時(shí)切到別的頻道,來(lái)看看其它臺(tái)那正放什么。但并不是簡(jiǎn) 單地按幾個(gè)按鈕,你不得不創(chuàng)建,檢出,合并,以及刪除臨時(shí)分支。幸運(yùn)的是,Git已經(jīng) 有了和電視機(jī)遙控器一樣方便的快捷方式:
$ git stash
這個(gè)命令保存當(dāng)前狀態(tài)到一個(gè)臨時(shí)的地方(一個(gè)隱藏的地方)并且恢復(fù)之前狀態(tài)。你的 工作目錄看起來(lái)和你開(kāi)始編輯之前一樣,并且你可以修復(fù)臭蟲(chóng),引入之前變更等。當(dāng)你 想回到隱藏狀態(tài)的時(shí)候,鍵入:
$ git stash apply # 你可能需要解決一些沖突
你可以有多個(gè)隱藏,并用不同的方式來(lái)操作他們。參見(jiàn)?git help slash?。也許你已 經(jīng)猜到,Git維護(hù)在這個(gè)場(chǎng)景之后的分支以執(zhí)行魔法技巧.
你可能猶疑于分支是否值得一試。畢竟,克隆也幾乎一樣快,并且你可以用?cd?來(lái)在 彼此之間切換,而不是用Git深?yuàn)W的命令。
考慮一下瀏覽器。為什么同時(shí)支持多標(biāo)簽和多窗口?因?yàn)樵试S兩者同時(shí)接納了多種風(fēng) 格的用戶(hù)。一些用戶(hù)喜歡只保持一個(gè)打開(kāi)的窗口,然后用標(biāo)簽瀏覽多個(gè)網(wǎng)頁(yè)。一些可能 堅(jiān)持另一個(gè)極端:任何地方都沒(méi)有標(biāo)簽的多窗口。一些喜好處在兩者之間。
分支類(lèi)似你工作目錄的標(biāo)簽,克隆類(lèi)似打開(kāi)的瀏覽器新窗口。這些是本地操作很快,那 為什么不試著找出最適合你的組合呢?Git讓你按你確實(shí)所希望的那樣工作。
更多建議: