Merge,Rebase,Cherry-Pick 一文解惑


代碼合並在日常開發中是較為常見的場景,采用合適的合並方式,可以起到事半功倍的效果。對應在 Git 中合並的方式主要有三個,Merge,Rebase,Cherry-Pick. 開始部分會首先介紹一下這三個命令,並錄制了一些動畫,用於演示三個命令的不同合並過程。之后會實操這三個命令,演示如何解決沖突。

Git Merge

Merge 會將兩個分支合並到一起,並生成一個新的 commit 記錄。新生成的 commit 節點會有兩個父節點。一般在開發某個新功能時,會選擇新的分支,之后在合並回主線。

在 master 分支上,使用 git merge bugFix 將 bugFix 分支合並到 master 分支。

Git rebase

rebase 也能合並分支,它會取出一系列 commit 記錄,復制它們。然后在目標分支逐個放下去。rebase 的好處是能保持線性的提交歷史,從而使歷史更加清晰。

git-rebase

在 bugFix 分支上,使用 git rebase master 分支,會將 bugFix 分支上的 commit 記錄復制到 master 分支,並且保證了提交歷史是線性的。

rebase 除了可以在合並時使用,還可用於整理 commit 記錄。

使用 rebase 來整理記錄

使用 git rebase -i 命令打開交互式的 rebase UI. 在打開的界面中會顯示每個 commit 的哈希值以及提交說明,可以在其中調整 commit 的順序、刪除不想要的提交、以及合並提交。

通過 git rebase -i HEAD~4 后,我們看到了實際的交互式界面,但實際界面一般由 vim 打開並進行相應的操作。在這里,我們刪除 c2,調整了 c5 和 c4 的位置。

這里需要注意的是:在真實的環境中,打開后的交互頁面,commit 記錄方向和 log 記錄的 commit 是相反的。

cherry-pick

cherry-pick 而是將一些 commit 復制到當前的分支的 HEAD 上,和 rebase 相比,更加靈活,可以隨意的選擇 commit 進行復制。

通過 git cherry-pick c3 c4 c7 將其他分支上的 3 個 commit 復制到當前的 master.

三種情況下的沖突解決

場景模擬:團隊開發時,不同的開發者需要經常會對公共類進行修改。當修改的位置重合或者刪除公共的代碼片段后,便會產生沖突。現在模擬這一場景。

首先,創建一個倉庫用於演示,其中包含一個名叫 origin.txt 的文件,並在其中寫入 "line1",在 master 分支提交。

git 倉庫初始化:

mkdir merge-conflict-things
cd merge-conflict-things
git init
vim origin.txt
echo line1 > origin.txt

創建初始 commit 及三種情況 branch 用於演示:

git add .
git commit -m "add origin.txt"
git branch merge_branch
git branch rebase_branch
git branch cherrypick_branch

查看初始化提交信息

git log --oneline --all  -n4 --graph

准備工作:在 master 分支上,創建兩個 commit.

第一個 commit : 對 origin.txt 增加一些內容,相當於增加一個 feature.

origin.txt 中添加 "line2" 后:

line1
line2

第二個 commit 用於對過去提交的內容進行修改,相當於 Bug Fix.

origin.txt 的內容如下:

line1 - fixed
line2

接下來,會在三個分支進行相同的操作,並演示如何解決沖突。

git merge

merge_branch 分支,模擬和 master 分支相同的操作,這里提交的過程就不演示了,只要和 master 分支提交的內容不同即可。

第一個 commit:用於增加 feature "line 2 in merge branch"。

第二個 commit:同於修改之前的 bug "line 1 fixed in merge branch"。

提交后的內容如下,merge 分支多了兩個 commit :

下面將 merge 和 master 分支進行合並,通常的規范是在次分支去主動合並主分支的代碼,然后再推送給主分支,這樣做是由於所有分支都是基於主分支,萬一在主分支發生錯誤,影響較為廣泛。

進行 merge 操作

git merge master

由於 master 和 merge 分支修改了相同的內容,這時 git 並不知道哪個是需要的,所以提示 origin.txt 發生沖突,讓我們手動解決。

<<< HEAD ===== 中間的內容是當前所在的分支的內容,下面的是 master 分支上的內容。

這里你需要注意的是,這里提示沖突的行是兩個 commit 的內容。這就意味着:

merge 解決沖突時內容是基於兩個分支的所有的 commit.

選擇 merge_branch 的方案進行提交。

git add origin.txt
git commit -m "fixed conflict with master"

可以看到,merge 操作將分開的分支進行合並,並**形成一個新的 merge commit. **

git rebase

rebase_branch 分支上模擬 master 的提交操作。

第一個 commit 添加了新行:“line2 - rebase branch”

第二個 commit 用於修改bug: “line1 - rebase branch”

結果如下:

使用 rebase 合並操作

git rebase master

查看沖突的文件:

rebase 的合並過程不同於 merge,在合並時會選取 master 的所有 commit,和當前分支中一個出現沖突的 commit 進行合並。

如上圖中所示,HEAD 指針停留於master 分支上最新的 commit 節點,而 rebase 指針指向添加 feature 的節點。

由於 rebase 和 merge 相比較為特殊,這里詳細演示下,選取 master 分支內容,選取 rebase 分支內容 ,選擇 master 和 rebase 分支的共同內容的處理方法。

1. 選擇 rebase 分支內容作為合並后的結果

保存當前的修改:

git add origin.txt
git rebase --continue

修改后的內容直接就提交成功了,因為第二次需要合並的 commit 本就是基於當前內容的 commit,並不會產生沖突。

2. 選擇 master 分支內容作為合並后的結果

保存當前的修改:

git add origin.txt
# 由於我們選擇了 master 分支內容進行合並,而當前又基於 master,和 master 本身分支上的內容沒有任何區別。
# 所以執行 skip 跳過這次沖突。 也就是意味着放棄當前分支提交的 commit,這個也能理解,既然要 master 的 commit,
# 當前分支的 commit 自然就不需要了,直接忽略掉,同時當前的 commit 也不會出現在 commit 的歷史中。
git rebase --skip

接着會出現第二次沖突:

再次產生的沖突的原因是,之前解決沖突時,選擇了 master 的內容,也就意味着在 rebase_branch 提交的 commit add feature for origin.txt in rebase 被放棄了,而當前的 commit fixed before commit in rebase_branch 是基於丟棄的那個 commit 。

了解到產生沖突的原因,任意選擇內容,保存就可以了,這里我們選擇了 rebase_merge 分支上的內容。

git add .
git rebase --continue

3. 選擇將 master 和 rebase_branch 的內容合並后的結果

這里我們選擇將 master 和 rebase_branch 的分支都保留下來:

保存當前的修改:

git add origin.txt
git rebase --continue

可以看到,第二次沖突產生,原因是 rebase_branch 分支中過去的 commit 出現變更,和 rebase_branch 原有 commit 的記錄不一致。

同樣的,我們也會遇到三種情況:

A 以第一次解決沖突后的內容為准:

git add .
# 這就意味着 rebase_branch 最新節點的添加的內容被舍棄,所以沒有任何改變,可以直接跳過
git rebase --skip

B 以 rebase_branch 分支上的結果為准

C 保留 rebase_branch 和 之前修改后的內容:

B C 處理方法相同:

git add .
# 保存我們的更改就可以了
git rebase --continue

注意有時 git rebase --continute 操作無法生效時,原因在於 rebase 會逐一應用當前分支的 commit 到目標分支(一般是 master)。像例子中演示的那樣,第一次解決沖突后的內容,和第二次解決沖突后的內容一樣,這時用 git rebase --skip 跳過就好了。

git cheery-pick

cherrypick_branch 分支上模擬 master 的提交操作。

使用 cherry-pick 進行合並操作

git cherry-pick  eb1a8fb

出現了沖突,可以看到和 rebase 操作的合並操作剛好相反,這里是以 cherrypick_branch 的最新 commit 為准,去合並 master 的 commit.

選擇 master 內容,進行合並:

git add origin.txt
git commit -m "fixed add feature conflict with master"

合並 master 分支上提交的第二個 commit:

git cherry-pick 23e106b

再次出現了沖突:

修改后,提交就可以了:

git add origin.txt
git commit -m "fixed add feature conflict with master"

注意有時 git commit 操作無法生效,提示你是否提交的一個空的 commit. 原因在於修改后的沖突內容和當前分支的原有內容一樣。但這樣有些矛盾,取過來別人的 commit,最后卻不用。解決辦法是,取消 cherry-pick 這個 commit 或者提交一個空的 commit 信息。

總結

git merge 是基於兩個分支上的最新內容到有沖突的內容之間的所有 commit 進行合並,之后會形成一個新的 commit 記錄,將兩個分支重新關聯起來。

git rebase 正如它的中文名字“變基操作”一樣,會將所在分支新添加的內容,增加到目的分支,並保證了 commit 提交記錄的串行性。簡單來說就是,會以目的分支(一般是 master)為基礎,逐一的將當前分支的 commit 記錄應用。需要注意的是,在應用時並不是直接應用在 master 分支,而是將 master 分支整體拷貝,然后將當前 commit 應用在拷貝后的分支上。

git cherry-pick 和 rebase 操作正好相反,會以當前的分支為基礎,然后將 commit 一個個的拿過來應用。形成的 commit 記錄也是串行的。

最后附上一個 Git 練習網站,網站上設置了各種任務,可以用於檢驗自己的操作水平。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM