原文地址git
git rebase
因其「新手應當遠離的Git黑魔法」的名號名聲在外,但只要使用得當,其可使團隊開發變得無比輕鬆。本文將對比兩個類似的命令:git rebase
與git rebase
來區分它們的使用場景,最終將「黑魔法」歸入本身的工做流中。安全
首先你須要明白git rebase
與git merge
解決的是同一類問題,即都被設計來解決將一個分支與另外一個分支整合的工做,因此它們可謂一體兩面。工具
想象一下但你在專用分支上開發一個新的特性時,團隊的另外一個成員更新了主分支。這致使了及時記錄上的一個分叉,這一場景對以Git做爲協做工具的你我是再熟悉不過了。fetch
假設這一提交與你在開發的功能有關,爲了將提交歸入你的特性分支中,你必須在合併(merge)和變基(rebase)中做出選擇。this
最簡單的方式就是使用以下操做將主分支合併入特性分支中:spa
git checkout feature git merge master
或者用一行操做:設計
git merge master feature
此時在特性分支上有了一次「合併提交(merge commit)」將兩個分支的歷史線中出現了一個共同節點以下圖:3d
merge
是一個很優秀的命令,它是一個非破壞性的操做(歷史線不改變結構)。已存在的分支並不會有任何變化。這能避免全部由rebase帶來的潛在陷阱(後文將涉及)。code
不過這也意味着,在每次相關上游變化時,你都要添加一條無關的合併提交。若是主分支更新活躍,使用合併會很大程度上污染你的特性分支歷史線。雖然git log
命令能夠必定程度緩解這一問題,可是其餘程序猿將難以理解項目的歷史線。blog
做爲merge
的替代,你可使用一下命令進行rebase
:
git checkout feature git rebase master
這樣全部的特性分支被移至主分支的末端,有效地歸入了主分支的新提交。可是rebase
命令重寫了項目的歷史線經過對先前分支的每個提交都建立新的提交。
rebase
帶來的主要好處是更清晰的項目歷史線。首先,再也不會有由merge帶來的沒必要要的合併提交。其次,如上圖所示,rebase命令將項目歷史線變得理想的線形(從頭至尾都沒有分叉),這樣,使用git log
,git bisect
,gitk
這樣的命令都能輕鬆定位你的項目。
可是,對於rebase操做有兩個須要考慮的點:安全性和可追溯性。你若不遵循「Rebase黃精準則」重寫歷史線將會給你的合做工做流帶來滅頂之災。還有一點須要注意的是:rebase並不存在像merge同樣的上下文,這意味着你很難定位由上游變化而帶來的特性的合併的位置。
交互式rebase讓你有能力去控制最終移動到分支末尾的提交。這比自動提交來的更強大,因其提供了對提交歷史完整的控制。這一般被用於將某個分支合併進主分支以前,去清理雜亂的歷史線。
經過加上在git rebase
中加入i
選項來開啓交互式rebase:
git checkout feature git rebase -i master
以後會打開一個文本編輯,其中羅列了將會被移動的全部提交:
pick 33d5b7a Message for commit #1 pick 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3
這一列表展現的就是最終執行rebase
後分支。經過改變pick命令或重排順序,你能夠將分支歷史線變成須要的樣子。例如,若是第二行提交是爲了修正了第一行提交的中德小問題,你能夠將二者合爲一條經過fixup
命令:
pick 33d5b7a Message for commit #1 fixup 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3
當你保存並關閉了文件,git會根據你的指令去執行rebase
命令。項目歷史線的結果以下:
去除掉非必要的提交後可讓你的特性分支歷史線更容易讀懂,這是簡單的git merge
一旦你理解了rebase,最重要的事就是學會什麼時候不使用rebase。Rebase黃金法則:永遠不要在公開分支上使用git rebase
。
例如,想象一下若是你將你的特性分支rebase到主分支:
rebase將移動全部主分支上的全部提交到特性分支末端。問題是這隻發生在你的倉庫中。其餘開發者依舊在最先的分支中開發。因爲改變了最新的提交,Git會認爲你的分支歷史線與其餘成員產生了分岔。
惟一能使兩天分支同步的辦法就是將它們合併在一塊兒,這將會致使一條額外的提交與兩組對相同變化的提交(初始分支上,你rebase的分支上)。沒必要說,這是一個極容易讓人混淆的狀態。
在你執行git rebase
以前,問一問本身,「其餘人會看到這條分支?」若是會,把手從鍵盤上移開而且想一個無破壞性的方式來解決(例如git revert
命令)。此外,你能夠盡情重寫歷史線。
若是你要將一個rebase過的分支發佈到遠程分支上,Git會阻止你這樣作,由於這會和遠程主分支產生衝突。可是,能夠加上-- force參數來強制發佈:
# Be very careful with this command! git push --force
該操做會用你倉庫中的主分支覆蓋遠程倉庫中的主分支,而且讓團隊中其餘成員一頭霧水。因此,請謹慎使用這條命令。
惟一須要強制推送的狀況是在你推送了一個我的特性分支到遠程倉庫後,你完成了一次本地的清理。這好像就在說「哎喲喂,我不想要以前上傳的特性分支了,用如今的版本替代吧」同時,須要確保沒有人在以前那個版本的提交下開展工做。
Rebase能夠被儘量多/少地歸入大家現有的工做流中,這取決於團隊對於它的態度。本節中咱們將瞭解到rebase在分支開發的各個階段帶來的好處。
使用git rebase
的任何工做流的第一步是爲每一個特性建立一個專有分支。這將爲你安全地使用rebase
提供必須的分支結構。
將rebase
歸入大家的工做的的一個最好的方法是去清理本地在開發的特性。經過週期性的使用交互式rebase,能夠確保你特性上的每一次提交是清晰且有意義的。這使得你能夠去書寫代碼而沒必要擔心將其分割爲一次獨立的提交,由於過後仍是能夠修復的。
當調用git rebase
命令時,你需呀注意兩個選項:特性的父分支(如主分支)或是在你的特性上更早的提交。咱們參考交互式rebase一節中關於第一選項的例子。當你僅須要修改最近的幾回提交時後一個參數是頗有用的。例如,例以下面的命令建立一個只有最近三次提交的交互式rebase。
git checkout feature git rebase -i HEAD~3
經過將HEAD~3
指定爲新的「基」,你實際並不須要移動分支,你只是重寫了三次提交。值得注意的是上述操做並無把依賴的變化歸入特性分支。
若是你想要使用這種方法重寫所有的特性,git merge-base
命令將可以幫助你找到特性分支以前的基。如下命令會返回以前基的提交ID
,用於以後執行git rebase
使用。
git merge-base feature master
這種交互式rebase的用法是將git rebase
引入大家工做流的一個好方法。其餘開發者看到的只會是你最終的成品,其將擁有清晰,易於理解的特性分支歷史。
可是再次重申,這隻適用於專有特性分支,若是你和其餘開發者同時在相同的分支上合做,那條分支就是共有的,重寫歷史線將不被容許。
交互式rebase用於清理本地提交的功能是不存在git merge
類的替代功能的。
在概述一節中,你瞭解到瞭如何使用git merge
/git rebase
將主分支上游的變化歸入一個特性分支中去。merge是一個能夠保證倉庫歷史線的安全選擇,同時,rebase經過將你的特性分支移至主分支末端來建立一條線形的歷史線。
該用法與本地清理類似(能夠同時進行),可是該過程從主分支歸入了上游提交。
請牢記,除了遠程主分支外的任意遠程分支的rebase都是徹底合法的。這一般發生在團隊協同開發同一特性,你須要將成員的修改歸入你的倉庫時。
例如,若是你的夥伴John提交了一次修改,當你fetch遠程分支從John的倉庫中,你的分支就會像下圖同樣:
解決這一分岔你有兩個選擇:
1.用本地分支與John的分支進行merge:
2.使用rebase將你本地的特性分支移至John的分支末端:
注意這並未違背Rebase黃金法則,由於移動只發生在你的本地分支提交後,在此以前都是不變的。也就是說「添加個人修改在John的工做以後」。在多數狀況下這比經過合併提交使遠程分支同步的方法來得更爲直觀。
默認狀況下,git pull
命令執行了一個merge,可是你但是經過加上--rebase
選項強制其使用rebase。
Pull Request
檢查分支若是你在代碼檢查階段有使用Pull Request
,那麼你須要避免使用git rebase
在你建立了pull request
後。一旦你執行了pull request
,其餘開發者將會看見你的提交,這意味着這就是一個公共分支。重寫歷史線將致使Git和你的團隊成員不能追蹤到在此特性上補增的提交。
任何從其餘開發者歸入的修改請使用git merge
而不要用git rebase
。
所以,一般的清理代碼的作法是在執行你的pull request
前使用交互式rebase。
當一個特性經過團隊的審覈後,在將特性整合進主代碼庫前,你能夠選擇將特性rebase到主分支末端。
該步驟和把上游修改整合進特性類似,但不一樣的是,因爲你不能在主分支上重寫提交(commits),你最終必須使用git merge
來合併特性。然而經過在rebase
以前執行rebase
能夠確保迅速的合併,而且獲得一條完美的線形歷史線。這也提供了一個在pull request
階段塞入任意補增提交的機會。
若是你還沒法徹底適應git rebase
你能夠在短時間分支上使用它。這時,當你不當心弄亂了你的分支歷史線,你能夠checkout
早前的分支,而且再次嘗試:
git checkout feature git checkout -b temporary-branch git rebase -i master # [Clean up the history] git checkout master git merge temporary-branch
以上就是開始rebase你的分支前你須要知道的一切。若是你想要一條幹淨,清晰的歷史線(沒有非必要的合併提交),那麼你須要使用git rebase
去吸納別的分支上的修改而非使用git merge
。
不過,若是你想要保留完整的項目歷史線,避免重寫公共提交,那你須要貫徹git merge
。這二者都是徹底有效的。可是至少如今你須要衡量一下使用git rebase
這個選擇將帶來的優點。