Git 深入 - 變基

變基

在 Git 中整合來自不同分支的修改主要有兩種方法:merge 以及 rebase。 在本節(jié)中我們將學習什么是“變基”,怎樣使用“變基”酿矢,并將展示該操作的驚艷之處,以及指出在何種情況下你應避免使用它怎燥。

變基的基本操作

請回顧之前在 分支的合并 中的一個例子瘫筐,你會看到開發(fā)任務分叉到兩個不同分支,又各自提交了更新铐姚。

分叉的提交歷史策肝。

Figure 35. 分叉的提交歷史

之前介紹過,整合分支最容易的方法是 merge 命令。 它會把兩個分支的最新快照(C3C4)以及二者最近的共同祖先(C2)進行三方合并驳糯,合并的結果是生成一個新的快照(并提交)篇梭。

通過合并操作來整合分叉了的歷史。

Figure 36. 通過合并操作來整合分叉了的歷史

其實酝枢,還有一種方法:你可以提取在 C4 中引入的補丁和修改恬偷,然后在 C3 的基礎上應用一次。 在 Git 中帘睦,這種操作就叫做 變基袍患。 你可以使用 rebase 命令將提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一樣竣付。

在上面這個例子中诡延,運行:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

它的原理是首先找到這兩個分支(即當前分支 experiment、變基操作的目標基底分支 master)的最近共同祖先 C2古胆,然后對比當前分支相對于該祖先的歷次提交肆良,提取相應的修改并存為臨時文件,然后將當前分支指向目標基底 C3, 最后以此將之前另存為臨時文件的修改依序應用逸绎。(譯注:寫明了 commit id惹恃,以便理解,下同)

將 `C4` 中的修改變基到 `C3` 上棺牧。

Figure 37. 將 C4 中的修改變基到 C3

現(xiàn)在回到 master 分支巫糙,進行一次快進合并。

$ git checkout master
$ git merge experiment
master 分支的快進合并颊乘。

Figure 38. master 分支的快進合并

此時参淹,C4' 指向的快照就和上面使用 merge 命令的例子中 C5 指向的快照一模一樣了。 這兩種整合方法的最終結果沒有任何區(qū)別乏悄,但是變基使得提交歷史更加整潔浙值。 你在查看一個經(jīng)過變基的分支的歷史記錄時會發(fā)現(xiàn),盡管實際的開發(fā)工作是并行的檩小,但它們看上去就像是串行的一樣开呐,提交歷史是一條直線沒有分叉。

一般我們這樣做的目的是為了確保在向遠程分支推送時能保持提交歷史的整潔——例如向某個其他人維護的項目貢獻代碼時识啦。 在這種情況下,你首先在自己的分支里進行開發(fā)神妹,當開發(fā)完成時你需要先將你的代碼變基到 origin/master 上颓哮,然后再向主項目提交修改。 這樣的話鸵荠,該項目的維護者就不再需要進行整合工作冕茅,只需要快進合并便可。

請注意,無論是通過變基姨伤,還是通過三方合并哨坪,整合的最終結果所指向的快照始終是一樣的,只不過提交歷史不同罷了乍楚。 變基是將一系列提交按照原有次序依次應用到另一分支上当编,而合并是把最終結果合在一起。

更有趣的變基例子

在對兩個分支進行變基時徒溪,所生成的“重放”并不一定要在目標分支上應用忿偷,你也可以指定另外的一個分支進行應用。 就像 從一個特性分支里再分出一個特性分支的提交歷史 中的例子那樣臊泌。 你創(chuàng)建了一個特性分支 server鲤桥,為服務端添加了一些功能,提交了 C3C4渠概。 然后從 C3 上創(chuàng)建了特性分支 client茶凳,為客戶端添加了一些功能,提交了 C8C9播揪。 最后贮喧,你回到 server 分支,又提交了 C10剪芍。

從一個特性分支里再分出一個特性分支的提交歷史塞淹。

Figure 39. 從一個特性分支里再分出一個特性分支的提交歷史

假設你希望將 client 中的修改合并到主分支并發(fā)布,但暫時并不想合并 server 中的修改罪裹,因為它們還需要經(jīng)過更全面的測試饱普。 這時,你就可以使用 git rebase 命令的 --onto 選項状共,選中在 client 分支里但不在 server 分支里的修改(即 C8C9)套耕,將它們在 master 分支上重放:

$ git rebase --onto master server client

以上命令的意思是:“取出 client 分支,找出處于 client 分支和 server 分支的共同祖先之后的修改峡继,然后把它們在 master 分支上重放一遍”冯袍。 這理解起來有一點復雜,不過效果非衬肱疲酷康愤。

截取特性分支上的另一個特性分支,然后變基到其他分支舶吗。

Figure 40. 截取特性分支上的另一個特性分支征冷,然后變基到其他分支

現(xiàn)在可以快進合并 master 分支了。(如圖 快進合并 master 分支誓琼,使之包含來自 client 分支的修改):

$ git checkout master
$ git merge client
快進合并 master 分支检激,使之包含來自 client 分支的修改肴捉。

Figure 41. 快進合并 master 分支,使之包含來自 client 分支的修改

接下來你決定將 server 分支中的修改也整合進來叔收。 使用 git rebase [basebranch] [topicbranch] 命令可以直接將特性分支(即本例中的 server)變基到目標分支(即 master)上齿穗。這樣做能省去你先切換到 server 分支,再對其執(zhí)行變基命令的多個步驟饺律。

$ git rebase master server

如圖 將 server 中的修改變基到 master 上 所示窃页,server 中的代碼被“續(xù)”到了 master 后面。

將 server 中的修改變基到 master 上蓝晒。

Figure 42. 將 server 中的修改變基到 master 上

然后就可以快進合并主分支 master 了:

$ git checkout master
$ git merge server

至此腮出,clientserver 分支中的修改都已經(jīng)整合到主分支里了,你可以刪除這兩個分支芝薇,最終提交歷史會變成圖 最終的提交歷史 中的樣子:

$ git branch -d client
$ git branch -d server
最終的提交歷史胚嘲。

Figure 43. 最終的提交歷史

變基的風險

呃,奇妙的變基也并非完美無缺洛二,要用它得遵守一條準則:

不要對在你的倉庫外有副本的分支執(zhí)行變基馋劈。

如果你遵循這條金科玉律,就不會出差錯晾嘶。 否則妓雾,人民群眾會仇恨你,你的朋友和家人也會嘲笑你垒迂,唾棄你械姻。

變基操作的實質是丟棄一些現(xiàn)有的提交,然后相應地新建一些內(nèi)容一樣但實際上不同的提交机断。 如果你已經(jīng)將提交推送至某個倉庫楷拳,而其他人也已經(jīng)從該倉庫拉取提交并進行了后續(xù)工作,此時吏奸,如果你用 git rebase 命令重新整理了提交并再次推送欢揖,你的同伴因此將不得不再次將他們手頭的工作與你的提交進行整合,如果接下來你還要拉取并整合他們修改過的提交奋蔚,事情就會變得一團糟她混。

讓我們來看一個在公開的倉庫上執(zhí)行變基操作所帶來的問題。 假設你從一個中央服務器克隆然后在它的基礎上進行了一些開發(fā)泊碑。 你的提交歷史如圖所示:

[圖片上傳失敗...(image-32d39-1577069043249)]

Figure 44. 克隆一個倉庫坤按,然后在它的基礎上進行了一些開發(fā)

然后,某人又向中央服務器提交了一些修改馒过,其中還包括一次合并臭脓。 你抓取了這些在遠程分支上的修改,并將其合并到你本地的開發(fā)分支沉桌,然后你的提交歷史就會變成這樣:

抓取別人的提交谢鹊,合并到自己的開發(fā)分支。

Figure 45. 抓取別人的提交留凭,合并到自己的開發(fā)分支

接下來佃扼,這個人又決定把合并操作回滾,改用變基蔼夜;繼而又用 git push --force 命令覆蓋了服務器上的提交歷史兼耀。 之后你從服務器抓取更新,會發(fā)現(xiàn)多出來一些新的提交求冷。

有人推送了經(jīng)過變基的提交瘤运,并丟棄了你的本地開發(fā)所基于的一些提交。

Figure 46. 有人推送了經(jīng)過變基的提交匠题,并丟棄了你的本地開發(fā)所基于的一些提交

結果就是你們兩人的處境都十分尷尬拯坟。 如果你執(zhí)行 git pull 命令,你將合并來自兩條提交歷史的內(nèi)容韭山,生成一個新的合并提交郁季,最終倉庫會如圖所示:

你將相同的內(nèi)容又合并了一次,生成了一個新的提交钱磅。

Figure 47. 你將相同的內(nèi)容又合并了一次梦裂,生成了一個新的提交

此時如果你執(zhí)行 git log 命令,你會發(fā)現(xiàn)有兩個提交的作者盖淡、日期年柠、日志居然是一樣的,這會令人感到混亂褪迟。 此外冗恨,如果你將這一堆又推送到服務器上,你實際上是將那些已經(jīng)被變基拋棄的提交又找了回來牵咙,這會令人感到更加混亂派近。 很明顯對方并不想在提交歷史中看到 C4C6,因為之前就是他把這兩個提交通過變基丟棄的洁桌。

用變基解決變基

如果你 真的 遭遇了類似的處境渴丸,Git 還有一些高級魔法可以幫到你。 如果團隊中的某人強制推送并覆蓋了一些你所基于的提交另凌,你需要做的就是檢查你做了哪些修改谱轨,以及他們覆蓋了哪些修改。

實際上吠谢,Git 除了對整個提交計算 SHA-1 校驗和以外土童,也對本次提交所引入的修改計算了校驗和—— 即 “patch-id”。

如果你拉取被覆蓋過的更新并將你手頭的工作基于此進行變基的話工坊,一般情況下 Git 都能成功分辨出哪些是你的修改献汗,并把它們應用到新分支上敢订。

舉個例子,如果遇到前面提到的 有人推送了經(jīng)過變基的提交罢吃,并丟棄了你的本地開發(fā)所基于的一些提交 那種情境楚午,如果我們不是執(zhí)行合并,而是執(zhí)行 git rebase teamone/master, Git 將會:

  • 檢查哪些提交是我們的分支上獨有的(C2尿招,C3矾柜,C4,C6就谜,C7)

  • 檢查其中哪些提交不是合并操作的結果(C2怪蔑,C3,C4)

  • 檢查哪些提交在對方覆蓋更新時并沒有被納入目標分支(只有 C2 和 C3丧荐,因為 C4 其實就是 C4')

  • 把查到的這些提交應用在 teamone/master 上面

從而我們將得到與 你將相同的內(nèi)容又合并了一次缆瓣,生成了一個新的提交 中不同的結果,如圖 在一個被變基然后強制推送的分支上再次執(zhí)行變基 所示虹统。

在一個被變基然后強制推送的分支上再次執(zhí)行變基捆愁。

Figure 48. 在一個被變基然后強制推送的分支上再次執(zhí)行變基

要想上述方案有效,還需要對方在變基時確保 C4' 和 C4 是幾乎一樣的窟却。 否則變基操作將無法識別昼丑,并新建另一個類似 C4 的補丁(而這個補丁很可能無法整潔的整合入歷史夸赫,因為補丁中的修改已經(jīng)存在于某個地方了)菩帝。

在本例中另一種簡單的方法是使用 git pull --rebase 命令而不是直接 git pull。 又或者你可以自己手動完成這個過程茬腿,先 git fetch呼奢,再 git rebase teamone/master

如果你習慣使用 git pull 切平,同時又希望默認使用選項 --rebase握础,你可以執(zhí)行這條語句 git config --global pull.rebase true 來更改 pull.rebase 的默認配置。

只要你把變基命令當作是在推送前清理提交使之整潔的工具悴品,并且只在從未推送至共用倉庫的提交上執(zhí)行變基命令禀综,就不會有事。 假如在那些已經(jīng)被推送至共用倉庫的提交上執(zhí)行變基命令苔严,并因此丟棄了一些別人的開發(fā)所基于的提交定枷,那你就有大麻煩了,你的同事也會因此鄙視你届氢。

如果你或你的同事在某些情形下決意要這么做欠窒,請一定要通知每個人執(zhí)行 git pull --rebase 命令,這樣盡管不能避免傷痛退子,但能有所緩解岖妄。

變基 vs. 合并

至此型将,你已在實戰(zhàn)中學習了變基和合并的用法,你一定會想問荐虐,到底哪種方式更好茶敏。 在回答這個問題之前,讓我們退后一步缚俏,想討論一下提交歷史到底意味著什么。

有一種觀點認為贮乳,倉庫的提交歷史即是 記錄實際發(fā)生過什么忧换。 它是針對歷史的文檔,本身就有價值向拆,不能亂改亚茬。 從這個角度看來,改變提交歷史是一種褻瀆浓恳,你使用 謊言 掩蓋了實際發(fā)生過的事情刹缝。 如果由合并產(chǎn)生的提交歷史是一團糟怎么辦? 既然事實就是如此颈将,那么這些痕跡就應該被保留下來梢夯,讓后人能夠查閱。

另一種觀點則正好相反晴圾,他們認為提交歷史是 項目過程中發(fā)生的事颂砸。 沒人會出版一本書的第一版草稿,軟件維護手冊也是需要反復修訂才能方便使用死姚。 持這一觀點的人會使用 rebase 及 filter-branch 等工具來編寫故事人乓,怎么方便后來的讀者就怎么寫。

現(xiàn)在都毒,讓我們回到之前的問題上來色罚,到底合并還是變基好?希望你能明白账劲,這并沒有一個簡單的答案戳护。 Git 是一個非常強大的工具,它允許你對提交歷史做許多事情瀑焦,但每個團隊姑尺、每個項目對此的需求并不相同。 既然你已經(jīng)分別學習了兩者的用法蝠猬,相信你能夠根據(jù)實際情況作出明智的選擇切蟋。

總的原則是,只對尚未推送或分享給別人的本地修改執(zhí)行變基操作清理歷史榆芦,從不對已推送至別處的提交執(zhí)行變基操作柄粹,這樣喘鸟,你才能享受到兩種方式帶來的便利。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驻右,一起剝皮案震驚了整個濱河市什黑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌堪夭,老刑警劉巖愕把,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異森爽,居然都是意外死亡恨豁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門爬迟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橘蜜,“玉大人,你說我怎么就攤上這事付呕〖聘#” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵徽职,是天一觀的道長象颖。 經(jīng)常有香客問我,道長姆钉,這世上最難降的妖魔是什么力麸? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮育韩,結果婚禮上克蚂,老公的妹妹穿的比我還像新娘。我一直安慰自己筋讨,他們只是感情好埃叭,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悉罕,像睡著了一般赤屋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壁袄,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天类早,我揣著相機與錄音,去河邊找鬼嗜逻。 笑死涩僻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逆日,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼嵌巷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了室抽?” 一聲冷哼從身側響起搪哪,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坪圾,沒想到半個月后晓折,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡兽泄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年漓概,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片已日。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖栅屏,靈堂內(nèi)的尸體忽然破棺而出飘千,到底是詐尸還是另有隱情,我是刑警寧澤栈雳,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布护奈,位于F島的核電站,受9級特大地震影響哥纫,放射性物質發(fā)生泄漏霉旗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一蛀骇、第九天 我趴在偏房一處隱蔽的房頂上張望厌秒。 院中可真熱鬧,春花似錦擅憔、人聲如沸鸵闪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚌讼。三九已至,卻和暖如春个榕,著一層夾襖步出監(jiān)牢的瞬間篡石,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工西采, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梅割,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓停巷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親湿诊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容