Git教程 · 變基與揀取
- 1?? 工作原理:復制提交
- 2?? 避免“鉆石鏈”
- 3?? 什么情況下會遇到沖突
- 4?? 移植分支
- 5?? 執行變基后原提交的情況
- 6?? 提交的原件與副本存在于同一版本庫中所帶來的問題
- 7?? 撿取
- 🌾 總結
通常, 一段提交歷史中往往都存在著許多雜亂的分支。Git 可以盡可能地幫助我們理順這些歷史記錄。這里會用到的最重要的工具當然就是 rebase
命令了,它可以可以將某一次提交在提交圖上產生的影響從一個節點轉移到另一節點。
我們可以用該命令做以下幾件事情。
- 如果你不小心在錯誤的分支上執行了一次提交。例如你可能將一次 bug 修復提交到了 當前開發線(即 master 分支)上。
- 當多個開發者在致力于開發同一軟件時,他們會頻繁地整合自己的修改。如果不進行 變基,他們可能會創建出一部帶有多個小分支和分岔的歷史(我們稱之為鉆石鏈)。 通過
rebase
命令,我們可以將其改造成一部較為平滑的線性歷史。
1?? 工作原理:復制提交
變基操作的工作原理很簡單: Git 會讓我們想要移動的提交序列在目標分支上按照相同的順序重新再現一遍。這就相當于我們為各個原提交做了個副本,它們擁有相同的修改集、同一作者、日期以及注釋信息。
請注意:乍看之下,好像 Git 只是在執行變基重操作時移動了相關提交。但事實上,這 些“被轉移”的提交往往都是一些擁有不同提交散列值的新提交。了解這一情況非常重要,
尤其是在提交已經從原分支已經擴散到其子分支時。
由于新提交會被記錄在提交圖中的不同位置,所以當然有可能會引發沖突,因為其原本的修改未必適合當前的情況。對于這類修改,我們必須要通過手動來解決合并沖突。
2?? 避免“鉆石鏈”
如果為同一軟件工作的若干個開發者頻繁地歸并各種修改,該項目所建立起來的提交歷史看起來就會像一條鉆石鏈。這時候我們可以利用變基操作將其整理成一部內容等效,但線性發展的歷史。
下面,我們通過下圖中的具體例子來看看變基操作的具體過程。如你所見,從 master 分支上岔出了一個名為 feature-a 的分支,其中包含了C 和 D 兩個提交。同時, master 分支也得到了進一步的開發,于是多出了一個提交B。
現在,你可以通過
git merge master
命令來合并這些修改,然后再用 rebase
命令理順其
歷史紀錄。該命令需要一個參數,以說明我們要將活動分支上的最新修改納入哪一個分支。
> #Branch "feature-a" is active
> git rebase master
在收到這個命令之后,Git就會去做以下事情,以便將活動分支(feature-a)融合到 master
分支上。
- 確認涉及到哪些提交:Git會確認是要將活動分支 feature-a 上的哪一些目前不在目標分支 ( master) 上,在這里就是提交C 和 D。
- 確認目標位置:Git 會確認目標提交的位置,該提交就是 master 上 feature-a 將要執行變基操作地方,在這里就是提交B。
- 復制提交:以目標提交為基礎重演上述提交中的所有修改,并相應創建提交 C’ 和 D’。
- 將活動分支重置:活動分支將被移動到上述被復制提交的頂部,在這里就是提交D’。
然而在很多情況下,我們可能不會直接去調用 rebase
命令。相反,我們通常會用 pull 命令加上 --rebase
選項來對遠程版本庫中的修改進行變基處理。
注意:舊提交 C 和 D 偶爾還會留在版本庫中,雖然它們已經不再直接可見,因為 feature-a 分支現在已經指向了 D’ 。但是,我們依然還是可以通過散列值對C 和 D 進行訪問。 只有在用 gc 命令執行垃圾回收之后,它們才會真正從版本庫中消失。
3?? 什么情況下會遇到沖突
和 merge
命令一樣,rebase
命令也會在相關修改不匹配的時候以沖突的形式被終止。 但它們之間有個重要的區別:即在合并過程中,我們得到的是兩個分支合體之后的單一提交結果。而在變基過程中,我們是在依次執行重復的若干次提交。如果一切順利,其最后一次所提交的內容應該會與其執行 merge
命令時的結果相同,因為 Git 在這兩個命令中采用 了相同的沖突解決算法。但如果 rebase
命令在執行過程中遇到沖突情況,該命令進程就會被打斷,相關文件中也會出現沖突標志。我們需要先手動或通過合并工具對文件進行清理, 并重新將它們添加到暫存區中。然后再執行 rebase
命令加 --continue
選項,從該點繼續之前的進程。
> git add foo.txt
> git add bar.txt
> git rebase --continue
當然,我們也可以用 --abort
選項取消這次的 rebase
命令,或者用 --skip
選項跳過引起沖突的提交。這樣該次提交就被直接忽略,其中的修改將不會出現在新分支上。
需要特別注意,與合并操作不同的是,在被中斷變基作業的那些提交副本中可能已經有一部分被執行變基操作了。
4?? 移植分支
有時候,在已經創建了一個分支,并完成其首次提交的情況下,我們也可以通過 --onto
選項將該分支移植到提交圖中的另一個位置上。
在下面的例子中, feature-a 分支被移植到了release1 分支上。
> #Branch "feature-a" is active
> git rebase master --onto release1
在這里, rebase
命令的第一個參數所指定的是原分支(即這里的 master 分支)。然后,
Git 就會去確認活動分支(即 feature-a) 上所有不屬于原分支的所有提交(在這里就是提交E 和 F) 。 然后通過--onto
選項將這些提交拷貝到指定位置上(即這里的 release1 分支)。
某一分支已經被移動到了提交圖中的另一位置上。
- 在必要情況下,我們可以切換到待移動的分支上
git checkout the-branch
- 確定原位置
即原分支,相關分支是從這里被移出去的。Git會將其中所有不屬于原分支的提交移出來。- 檢查所要移動的內容
提前對可能會受到影響的提交做個相應的檢查是一個明智的選擇,因為一個變基操作錯誤可能會給版本庫帶來一個非常混亂的局面。
git log origin..the-branch
- 確定目標位置
選擇一個分支來充當被移動分支執行變基操作的目標位置。- 執行變基操作
git rebase origin --onto target
注意: rebase
命令中的原位置并不一定非得是一個分支。它也可以是任何提交。
5?? 執行變基后原提交的情況
這些提交會在變基過程中被復制。但其原件(即本例中的提交C 和 D) 依然還可以通過散列值來進行訪問,如圖所示。通常情況下,當沒有分支可以進一步從這些提交中繼續發展時,下一輪垃圾收集過程(通過 gc
命令)就會直接將它們從版本庫中刪除。
6?? 提交的原件與副本存在于同一版本庫中所帶來的問題
重復容易造成版本庫中的混亂。它們可以很容易引起誤解,讓人以為某段既定的代碼修 改包含在哪一分支上,不包含在哪一分支上。通常來說,git log HEAD..a-branch
顯示的是在a-branch 上而不在當前分支的那些提交。如果存在重復的話,當前分支也可能已經包含了該代碼的修改。這會增加審查以及質量保證方面的復雜性。
除此之外,這種重復還有可能會給我們稍后對帶有重復提交的分支與帶有原始提交的分 支之間的合并帶來麻煩。在最好的情況下,Git會自己識別出同樣的修改出現了不止一次,并對其只采用一次。而在最壞的情況下,如果該重復提交被當作沖突來處理, Git 是無法檢測到的,然后它會試圖多次采用這一修改。結果就會產生一些令用戶意外的沖突。
一旦我們將某次提交傳遞給了一個遠程版本庫,就不應該再用 rebase
命令來移動該提交了。否則,由于其他開發者可能會在其原作上繼續他們的工作,這在將來再次合并修改的時候一定會帶來問題。
7?? 撿取
接下來,我們再來介紹另一種復制提交的方式: cherry-pick
命令。我們可以用它來指定自己需要的提交,Git 會為此創建一次新的提交,該提交中會擁有相同的修改集與當前分支中的元數據。
> git cherry-pick 23ec70f6b0
那么對于撿取操作,我們應該了解哪些事情呢?
cherry-pick
不會參考歷史紀錄。因而merge
和rebase
還可以被正確地識別成文件的 重命名與移動操作,cherry-pick
則不能。- 撿取操作有時候會被用來將一些小bug 的修復傳遞到各種不同的發行版中。
- 該操作的另一種應用是從即將刪除的分支中轉移出有用的提交。
- 警告:撿取操作也有可能會引發我們之前所說的重復提交問題。
🌾 總結
- 變基操作:Git 能將提交復制到提交圖中的其他地方。盡管其中的修改與元數據(作者、日期)將保持不變,但該復制結果會有一個新的提交散列值。你可以通過
rebase
命令以多種方式對提交圖進行重構。 - 只適用于推送之前:通常情況下,我們應該只對那些還未被傳遞給其他版本庫的提交試用rebase 命令。否則,這樣做可能給日后帶來非常麻煩的合并沖突。
- 理順歷史:如果我們在并行式開發的過程中使用
merge
命令解決了其中的沖突,就會得到一部經歷了多次分岔與合并的歷史。如果用rebase
來代替merge
, 我們就會得到一部呈線性發展的歷史。 - 變基過程中的沖突:Git 會逐段逐段重演被復制的提交。如果因為某些修改與工作區內容不相符而引發了沖突,變基的進程就會被中斷。與執行 merge 命令的過程一樣, 開發者可以先手動解決掉沖突,再繼續變基的過程。
- rebase --onto :通過該選項,我們可以將某一分支移動到提交圖中另一個完全不同的位置。
《【Git教程】(六)分支合并 —— 合并過程,各類合并沖突及解決思路 ~》
