git rebase命令常常被認爲是Git巫術,初學者應該遠離它,但它實際上可讓開發團隊在使用時更加輕鬆。在本文中,咱們將git rebase與相關git merge命令進行比較。git
首先要理解的是git rebase和git merge解決了一樣的問題。這兩個命令都旨在將更改從一個分支集成到另外一個分支 - 它們只是以不一樣的方式進行。安全
試想一下當你開始在專用分支中開發新功能時另外一個團隊成員以新提交更新master分支會發生什麼。這會出現分叉歷史記錄,對於使用Git做爲協做工具的任何人來講都應該很熟悉。編輯器
如今,咱們來講說當master新提交與你正在開發的功能相關。要將新提交合併到你的feature分支中,你有兩個選擇:merge或rebase。工具
最簡單的是將master分支合併到feature分支中:spa
git checkout feature
3d
git merge master
code
或者,你能夠簡化爲一行:cdn
git merge master feature
blog
這會在feature分支中建立一個新的「merge commit」,它將兩個分支的歷史聯繫在一塊兒,爲你生成以下所示的分支結構:排序
合併很好,由於它是一種非破壞性的操做。現有分支結構不會以任何方式更改。這避免了rebase的全部潛在缺陷(下面討論)。
另外一方面,這也意味着每次上游更改時feature都須要合併,且有無關的合併提交。若是master改動很是頻繁,這可能會嚴重污染你分支的歷史記錄。儘管可使用高級git log選項減輕此問題的影響,但它可能使其餘開發人員難以理解項目的歷史更改記錄。
做爲merge的替代方法,你可使用如下命令將feature分支rebase到master分支上:
git checkout feature
git rebase master
這會將整個feature分支移動到master分支的頂端,從而有效地整合了全部master的新提交。可是,rebase不是使用merge commit,而是經過爲原始分支中的每一個提交建立全新的提交來重寫項目歷史記錄。
rebase的主要好處是能夠得到更清晰的項目歷史記錄。首先,它消除了沒必要要的git merge產生的merge commit。其次,正如在上圖中所看到的,rebase也會產生完美線性的項目歷史記錄 - 你能夠從feature分支頂端一直跟隨到項目的開始而沒有任何的分叉。這使得它比命令git log,git bisect和gitk更容易導航項目。
可是,對這個原始的提交歷史記錄有兩個權衡:安全性和可追溯性。若是你不遵循rebase的黃金法則,重寫項目歷史記錄可能會對你的協做工做流程形成災難性後果。其次rebase會丟失merge commit提供的上下文 - 你沒法看到上游更改什麼時候合併到功能中。
Interactive rebase使你有機會在將提交移動到新分支時更改提交。這比自動rebase更強大,由於它提供了對分支提交歷史的徹底控制。一般,這用於在合併特徵分支到master分支以前清理雜亂的歷史記錄。
要開始基於交互式會話,請將i選項傳遞給git 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,最重要的是瞭解何時不使用它。git rebase的黃金法則是永遠不要在公共分支使用它。
例如,想一想若是你把master分支rebase到你的feature分支會發生什麼:
rebase將master全部提交移動到feature頂端。問題是這隻發生在你的倉庫中。全部其餘開發人員仍在使用原始版本master。因爲rebase致使全新的提交,Git會認爲你的master分支的歷史與其餘人的歷史不一樣。
同步兩個master分支的惟一方法是將它們合併在一塊兒,從而產生額外的合併提交和兩組包含相同更改的提交(原始提交和來自rebase分支的更改)。這將是一個很是使人困惑的狀況。
所以,在你運行git rebase以前,老是問本身,「還有其餘人在用這個分支嗎?」若是答案是確定的,那就把你的手從鍵盤上移開,考慮使用非破壞性的方式進行(例如,git revert命令)。不然,你能夠爲所欲爲地重寫歷史記錄。
若是你嘗試將rebase過的master分支推到遠程倉庫,Git將阻止你這樣作,由於它與遠程master分支衝突。可是,你能夠經過傳遞--force標誌來強制推送,以下所示:
#這個命令要很是當心!
git push --force
這將覆蓋遠程master分支以匹配rebase過的分支,並使團隊的其餘成員感到困惑。所以,只有在確切知道本身在作什麼時才能很是當心地使用此命令。
rebase能夠根據你團隊的須要盡多地或少許地整合到你現有的Git工做流程中。在本節中,咱們將瞭解rebase在功能開發的各個階段的好處。
任何工做流程git rebase的第一步是爲每一個功能建立專用分支。這爲你提供了必要的分支結構,以安全地利用rebase:
將rebase加入工做流程的最佳方法之一是清理本地正在進行的功能。經過按期執行交互式rebase,你能夠確保功能中的每一個提交都具備針對性和意義。這使你能夠寫代碼而無需擔憂將其分解爲隔離多個的提交 - 你能夠在過後修復它。
調用git rebase時,有兩個基(base)選項:feature的父分支(例如master),或feature中的歷史提交。咱們在Interactive Rebasing部分看到了第一個選項的示例。當你只須要修復最後幾回提交時,後一種選擇很好。例如,如下命令僅針對最後3次提交的交互式rebase。
git checkout feature
git rebase -i HEAD~3
經過指定HEAD~3爲新的基,你實際上並無移動分支 - 你只是交互式地重寫其後的3個提交。請注意,這不會將上游更改合併到feature分支中。
若是要使用此方法重寫整個功能,git merge-base命令可用於查找feature分支的原始基。如下內容返回原始基礎的提交ID,而後你能夠將其傳遞給git rebase:
git merge-base feature master
交互式rebase的使用是引入git rebase工做流的好方法,由於它隻影響本地分支。其餘開發人員惟一能看到的就是你的成品,這應該是一個簡潔易懂的分支歷史記錄。
但一樣,這僅適用於私有功能分支。若是你經過相同的分支與其餘開發人員協做,則該分支是公共的,而且你能重寫其歷史記錄。
在概念部分中,咱們瞭解了feature分支如何使用git merge或git rebase合併master上游更改。merge是一個安全的選擇,能夠保留倉庫的整個歷史記錄,而rebase則經過將feature分支移動到master頂端來建立線性歷史記錄。
這種使用git rebase相似於本地清理(而且能夠同時執行),但在此過程當中它包含了那些master上游提交。
請記住,rebase到遠程分支而不是master。當與另外一個開發人員協做使用相同的功能而且你須要將他們的更改合併到你的倉庫時,就會發生這種狀況。
例如,若是你和另外一個名爲John的開發人員新增了對feature分支的提交,從John的倉庫中獲取遠程分支後,你的倉庫可能以下所示:
你能夠用從master集成上游更改相同的方法來解決這個分叉問題:要麼用john/feature合併本地feature,或rebase本地feature到john/feature。
請注意,此rebase不違反黃金法則,由於只有你的本地feature提交被移動 - 以前的全部內容都不會受到影響。這就像是「將個人更改添加到John已經完成的工做。」在大多數狀況下,這比經過merge與遠程分支同步更直觀。
默認狀況下,git pull命令執行合併,但你能夠經過向其傳遞--rebase選項來強制它與遠程分支rebase集成。
在你的團隊經過某feature後,你能夠選擇將該feature rebase到master分支的頂端,而後git merge再將該功能集成到主代碼庫中。
這是將上游更改合併到功能分支中的相似狀況,但因爲你不容許在master分支中重寫提交,所以你必須最終使用git merge該功能進行集成。可是,經過在合併以前執行rebase,你能夠確保合併產生完美的線性歷史記錄。這也使你有機會壓縮在拉取請求期間添加的任何後續提交。
若是你不熟悉git rebase,能夠隨時在臨時分支中執行rebase。這樣,若是你不當心弄亂了feature的歷史記錄,能夠查看原始分支,而後重試。例如:
git checkout feature
git checkout -b temporary-branch
git rebase -i master
#[清理歷史]
git checkout master
git merge temporary-branch
這就是你須要知道的關於rebase你的分支。若是你更喜歡提交的乾淨,消除沒必要要合併的線性歷史記錄,那麼你在繼承另外一分支的更改時應該使用git rebase 而不是git merge。
另外一方面,若是你想保留項目的完整歷史記錄並避免重寫公共提交的風險,你能夠仍然使用git merge。這兩種選擇都是徹底能夠的,但至少能夠選擇利用git rebase有它的好處。