GIT使用rebase和merge的正確姿勢
背景
使用GIT這麼久了從來沒有深層次的研究過,一般情況下,只要會用pull
,commit
,push
等幾個基本提交命令就可以了,公司的專案分支管理這部分操作一直都是我負責,對於分支的合併我一直都使用merge
操作,也知道還有一個rebase
,但是一直不會用,百度了很多,說的基本都差不多,按照步驟在公司專案裡操作,簡直就是噩夢,只要rebase
就出現噩夢般的衝突,所以一直不敢用,今天自己搗騰了一番終於領略到一些,不多說直接進入乾貨。
先來兩張合理使用rebase
,merge
和只使用merge
的對比圖
使用 rebase
使用 merge
使用 rebase
和 merge
的基本原則:
- 下游分支更新上游分支內容的時候使用
rebase
- 上游分支合併下游分支內容的時候使用
merge
- 更新當前分支的內容時一定要使用
--rebase
引數
例如現有上游分支 master,基於 master 分支拉出來一個開發分支 dev,在 dev 上開發了一段時間後要把 master 分支提交的新內容更新到 dev 分支,此時切換到 dev 分支,使用 git rebase master
等 dev 分支開發完成了之後,要合併到上游分支 master 上的時候,切換到 master 分支,使用 git merge dev
一、建立兩個GIT專案,project1
和 project2
,同時分別加入三個檔案並提交master
分支
$ git clone [email protected]:baiyl3/project1.git
$ cd project1
$ touch file1 file2 file3
$ git add .
$ git commit -m '在專案一中初始化三個程式碼檔案'
$ git push -u origin master
$ git clone [email protected]:baiyl3/project2.git
$ cd project2
$ touch file1 file2 file3
$ git add .
$ git commit -m '在專案二中初始化三個程式碼檔案'
$ git push -u origin master
從程式碼提交時間軸圖看兩個專案現在的狀態都一致
二、分別給兩個專案建立三個分支 B1,B2,B3 (* 對應數字)
$ git checkout -b B*
$ git push origin B*
$ git branch -a
B1
B2
* B3
master
remotes/origin/B1
remotes/origin/B2
remotes/origin/B3
remotes/origin/master
$ git logs --graph
* 891d1ed<baiyl3> - (HEAD -> B3, origin/master, origin/B3, origin/B2, origin/B1, master, B2, B1) 在專案二中初始化三個程式碼檔案 (23 minutes ago)
file1
file2
file3
從 git log
中我們看到 B1,B2,B3 都基於master最新提交點拉出來的三個新分支,如下圖從時間軸也可以看出
三、現在我們分別切換分支到 B1,B2,B3,並修改對應的檔案 file1,file2,file3,最後切換到 mastet 分支新增一個 README.md 檔案,然後再看時間軸:
從上圖的結果可以看出,B1, B2, B3, master 四個分支分別在不同的時間點做了程式碼提交,那麼最後一次 master 上做的修改在 B1, B2, B3 三個分支上肯定沒有
四、此時在這三個分支上開發的同學發現他們要做的功能要在 master 最新的基礎上開發,或者他們也想要 master 上最新的內容,重點來了,現在我們怎麼辦?方法有兩種,一種是使用 rebase
,另一種是使用 merge
,我們分別在 project1 和 project2 兩個專案上使用這兩種方式解決這個問題
在專案 project1 使用 rebase
$ cd project1
$ git checkout B1
$ git pull origin B1 --rebase
From gitlab.xpaas.lenovo.com:baiyl3/project1
* branch B1 -> FETCH_HEAD
Already up-to-date.
Current branch B1 is up to date.
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: FILE1 第一次修改
$ git push origin B1
To gitlab.xpaas.lenovo.com:baiyl3/project1.git
! [rejected] B1 -> B1 (non-fast-forward)
error: failed to push some refs to '[email protected]:baiyl3/project1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git push origin B1 --force
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 328 bytes | 328.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for B1, visit:
remote: http://gitlab.xpaas.lenovo.com/baiyl3/project1/merge_requests/new?merge_request%5Bsource_branch%5D=B1
remote:
To gitlab.xpaas.lenovo.com:baiyl3/project1.git
+ b3052a0...00032a7 B1 -> B1 (forced update)
$ ls
README.md file1 file2 file3
在上面的過程中,更新程式碼我使用的是 git pull origin B1 --rebase
而不是 git pull origin B1
這也是平時使用 rebase 注意的一點,git pull
這條命令預設使用了 --merge
的方式更新程式碼,如果你不指定用 --rebase
,有的時候就會發現日誌裡有這樣的一次提交 Merge branch 'dev' of gitlab.xpaas.lenovo.com:liuyy23/lenovo-mbg into dev
什麼?自己分支合併到了自己分支,顯然這個沒有什麼必要,而且在時間軸上也不好看,平白無故多了一條線出來,對於強迫症的我來說看著就難受。。。
還有就是使用 rebase 之後,如果直接使用 git push origin B1
發現是不好使的,提示也說明了提交失敗的原因,我個人是這麼理解的,使用 rebase 之後,master分支上比B1分支上多的修改,直接“插入”到了B1分支修改的內容之後,也就是 master 分支的修改在 B1 分支上重演了一遍,相對遠端 B1 分支而言,本地倉庫的B1分支的“基底”已經變化了,直接 push
是不行的,所以確保沒有問題的情況下必須使用 --force
引數才能提交,這也就是官方對 rebase
作為 “變基” 的解釋(個人觀點)。
接下來我們接著在 project2 專案上使用 merge 操作
$ cd project
$ git pull origin B1
From gitlab.xpaas.lenovo.com:baiyl3/project2
* branch B1 -> FETCH_HEAD
Already up-to-date.
$ git merge master
Merge made by the 'recursive' strategy.
README.md | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
$ git push origin B1
Counting objects: 2, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 278 bytes | 278.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0)
remote:
remote: To create a merge request for B1, visit:
remote: http://gitlab.xpaas.lenovo.com/baiyl3/project2/merge_requests/new?merge_request%5Bsource_branch%5D=B1
remote:
To gitlab.xpaas.lenovo.com:baiyl3/project2.git
d3ea69c..e040c7b B1 -> B1
ls
README.md file1 file2 file3
可以看到 merge 之後,在 B1 分支上多出一條合併的log
此時,我們的 B1 分支開發完成了,要合併到 master 分支,根據基本原則,在 master 分支上都使用 git merge B1
就可以合併,看下圖結果:
$ git checkotu master
$ git merge B1
Updating c782e83..00032a7
Fast-forward
file1 | 1 +
1 file changed, 1 insertion(+)
$ git push origin master
接下來對 B2,B3 分別在 project1 和 project2 上做相同的操作,我們看結果:
再看看命令列下log的情況
$ git logs --graph
* 5826260<baiyl3> - (HEAD -> master, origin/master, origin/B3, B3) FILE3 第一次修改 (6 minutes ago)|
| file3
* cffcc9a<baiyl3> - (origin/B2, B2) FILE2 第一次修改 (8 minutes ago)|
| file2
* 00032a7<baiyl3> - (origin/B1, B1) FILE1 第一次修改 (87 minutes ago)|
| file1
* c782e83<baiyl3> - 新增README.md檔案 (2 hours ago)|
| README.md
* b783e0a<baiyl3> - 在專案一中初始化三個程式碼檔案 (3 hours ago)
file1
file2
file3
git logs --graph
* bc3f385<baiyl3> - (HEAD -> master, origin/master, origin/B3, B3) Merge branch 'master' into B3 (4 minutes ago)
|\
| * 64b4f3d<baiyl3> - (origin/B2, B2) Merge branch 'master' into B2 (5 minutes ago)
| |\
| | * e040c7b<baiyl3> - (origin/B1, B1) Merge branch 'master' into B1 (35 minutes ago)
| | |\
| | | * 2cedfcb<baiyl3> - 新增README.md檔案 (2 hours ago)| | | |
| | | | README.md
| | * | d3ea69c<baiyl3> - FILE1 第一次修改 (2 hours ago)
| | |/ | | |
| | | file1
| * | 5975eae<baiyl3> - FILE2 第一次修改 (2 hours ago)
| |/ | |
| | file2
* | 37ec6de<baiyl3> - FILE3 第一次修改 (2 hours ago)
|/ |
| file3
* 891d1ed<baiyl3> - 在專案二中初始化三個程式碼檔案 (3 hours ago)
file1
file2
file3
使用 rebase 就感覺所有人都在同一條直線上開發一樣,很乾淨的log,看著很舒服,而一直使用 merge 的log看起來就很亂,我這只是4個分支的例子,我們專案每週基本都是十幾個分支,真的是看起來亂入一團哇。。。
這個例子中的操作都沒有出現不同分支修改同一個檔案導致衝突的情況,實際開發中這種情況非常多,rebase 的時候出現衝突後 git 也會列出來哪些檔案衝突了,等你解決衝突之後使用 git rebase --continue
就會繼續處理,所以為了避免這種衝突太多,而且不好解決,我們專案中基本都是一個需求就一個分支,而且開發過程中要隨時更新上游分支的內容下來,確保在最新的程式碼基礎上開發,這也是 GIT 推薦的方式,儘可能多建分支開發,而不是在一個開發分支上很多人操作,如果是這種情況建議不要用 rebase,另外負責分支合併的人在合併下游分支程式碼的時候要確保你這個上游分支不要在這段時間內提交程式碼,有一個方法就是把上游分支 設定 protect
五、注意事項
from:https://zhuanlan.zhihu.com/p/34197548
- 更新當前分支程式碼的時候一定要使用
git pull origin xxx --rebase
- 合併程式碼的時候按照最新分支優先合併為原則
- 要經常從上游分支更新程式碼,如果長時間不更新上游分支程式碼容易出現大量衝突