首先看下面舉例:
- 假定 1-2 是現在的master分支狀態;
- 這個時候從 master 分支 checkout 出來一個 Dev01 分支;
- 然后 master 提交了 3、4,Dev01 提交了5、6、7;
- 這個時候 master 分支狀態就是 1-2-3-4 ,Dev01 分支狀態變成 1-2-5-6-7
- 如果在 Dev01 分支上,用 rebase master,Dev01 分支狀態就成了 1-2-3-4-5-6-7
- 如果在 Dev01 分支上,用 merge:
1-2-5-6-7-8
........ |3-4|
7.會出來一個 8,這個 8 的提交就是把 3-4 合進來的提交。
2者區別:
假設當前從 master 分別切出了兩個分支:learn-rebase 和 learn-merge,它倆分別都進行了兩次 commit,我們希望把兩個分支的內容都合並到 master 上
Merge(合並)
fast-forward
使用 merge 可以合並多個歷史記錄的流程。
首先我們合並 learn-merge 分支,只看藍色的部分,learn-merge 分支與 master 分支的最新 commit 在同一條線上,這說明 learn-merge 分支的歷史記錄包含 master 分支所有的歷史記錄,那么這個合並是非常簡單的。只需要通過把 master 分支的位置移動到 learn-merge 的最新分支上,Git 就會合並。這樣的合並被稱為 fast-forward(快進)合並。(fast-forward 模式下是不可能出現沖突的)
操作如下:
➜ git branch
learn-merge
learn-rebase
* master
➜ git merge learn-merge
Updating 594a6c9..c2b4ae3
Fast-forward
myfile.txt | 2 ++
1 file changed, 2 insertions(+)
結果如下:
非 fast-forward
接下來需要合並 learn-rebase 分支,此時可以看到 master 的最新 commit 已經和 learn-merge 保持一致了,與 learn-rebase 分支不在一條直線上,那此時就不能進行 fast-forward 合並了。
在這種情況下使用 merge 方法,可以合並兩個修改並生成一個新的提交。(此時可能會出現沖突的情況,因為 learn-rebase 分支可能與 learn-merge 分支修改同一個文件)
操作如下:
➜ git merge learn-rebase
Auto-merging myfile.txt
CONFLICT (content): Merge conflict in myfile.txt Automatic merge failed; fix conflicts and then commit the result. ➜ vim myfile.txt # 解決沖突 ➜ git add myfile.txt ➜ git commit -m 'fix conflicts' [master 2a6899b] fix conflicts
結果如下:
設置 non fast-forward
執行合並時,如果設定了non fast-forward 選項,即使在能夠 fast-forward 合並的情況下也會生成新的提交並合並。
設置方法:添加 -no-ff
選項
rebase(變基)
與 merge 會保留修改內容的歷史記錄不同,rebase 是在原有提交的基礎上將差異內容反映進去。讓我們回到 merge learn-rebase 之前的樣子。
rebase 的步驟分為兩步,第一步將 learn-rebase 分支的歷史記錄添加在 master 分支的后面。這個時候可能會有沖突,當出現沖突時,解決沖突后的提交不是使用 commit
命令,而是執行 rebase
命令指定 --continue
選項。若要取消 rebase,指定 --abort
選項。
操作如下:
➜ git checkout learn-rebase
Switched to branch 'learn-rebase' ➜ git rebase master First, rewinding head to replay your work on top of it... Applying: add rebase Using index info to reconstruct a base tree... M myfile.txt Falling back to patching base and 3-way merge... Auto-merging myfile.txt CONFLICT (content): Merge conflict in myfile.txt error: Failed to merge in the changes. Patch failed at 0001 add rebase The copy of the patch that failed is found in: .git/rebase-apply/patch When you have resolved this problem, run "git rebase --continue". If you prefer to skip this patch, run "git rebase --skip" instead. To check out the original branch and stop rebasing, run "git rebase --abort". 1 merge 與 rebase 的差別 ➜ vim myfile.txt # 解決沖突 ➜ git add myfile.txt ➜ git rebase --continue Applying: add a new line
結果如下:
然后再使用 fast-forward 合並即可
➜ git checkout master
Switched to branch 'master' ➜ git merge learn-rebase Updating 0b9e66f..1fc71b9 Fast-forward myfile.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
對比 merge 和 rebase 最終的歷史記錄,可以發現 merge 保持了修改內容的歷史記錄,但是歷史記錄會很復雜;而 rebase 后的歷史記錄簡單,是在原有提交的基礎上將差異內容反映進去。
rebase 黃金定律
奇妙的變基也並非完美無缺,要用它得遵守一條准則:
不要對在你的倉庫外有副本的分支執行變基。
變基操作的 實質是丟棄一些現有的提交,然后相應地新建一些內容一樣但實際上不同的提交。 如果你已經將提交推送至某個倉庫,而其他人也已經從該倉庫拉取提交並進行了后續工作,此時,如果你用 git rebase 命令重新整理了提交並再次推送,你的同伴因此將不得不再次將他們手頭的工作與你的提交進行整合,如果接下來你還要拉取並整合他們修改過的提交,事情就會變得一團糟。
merge vs rebase
到底該使用 merge 方式開發還是使用 rebase 方法開發是有爭議的
有一種觀點認為,倉庫的提交歷史即是 記錄實際發生過什么。 它是針對歷史的文檔,本身就有價值,不能亂改。 從這個角度看來,改變提交歷史是一種褻瀆,你使用謊言掩蓋了實際發生過的事情。 如果由合並產生的提交歷史是一團糟怎么辦? 既然事實就是如此,那么這些痕跡就應該被保留下來,讓后人能夠查閱。
另一種觀點則正好相反,他們認為提交歷史是 項目過程中發生的事。 沒人會出版一本書的第一版草稿,軟件維護手冊也是需要反復修訂才能方便使用。 持這一觀點的人會使用 rebase 及 filter-branch 等工具來編寫故事,怎么方便后來的讀者就怎么寫。
總的原則是,只對尚未推送或分享給別人的本地修改執行變基操作清理歷史,從不對已推送至別處的提交執行變基操作,這樣,你才能享受到兩種方式帶來的便利。
撤銷提交的不同
如果使用 merge 進行合並,可以使用 revert 命令對 merge 的內容進行撤銷操作(參考 revert),而使用 rebase 則不行(已經沒有 merge commit 了),而需要使用 rebase -i
對提交進行重新編輯(參考 交互式 rebase)。
轉自:時間被海綿吃了 https://segmentfault.com/a/1190000012897637
參考
- https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372
- https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA
- https://backlog.com/git-tutorial/cn/stepup/stepup1_4.html
- https://medium.com/@porteneuve/getting-solid-at-git-rebase-vs-merge-4fa1a48c53aa