GIT使用 rebase 和 merge 的正確姿勢
背景
使用GIT這么久了從來沒有深層次的研究過,一般情況下,只要會用pull,commit,push等幾個基本提交命令就可以了,公司的項目分支管理這部分操作一直都是我負(fù)責(zé),對于分支的合并我一直都使用merge操作,也知道還有一個rebase,但是一直不會用,百度了很多,說的基本都差不多,按照步驟在公司項目里操作,簡直就是噩夢,只要rebase就出現(xiàn)噩夢般的沖突,所以一直不敢用,今天自己搗騰了一番終于領(lǐng)略到一些,不多說直接進入干貨。
先來兩張合理使用rebase,merge和只使用merge的對比圖
使用 rebase

使用 merge

使用 rebase 和 merge 的基本原則:
- 下游分支更新上游分支內(nèi)容的時候使用
rebase - 上游分支合并下游分支內(nèi)容的時候使用
merge - 更新當(dāng)前分支的內(nèi)容時一定要使用
--rebase參數(shù)
例如現(xiàn)有上游分支 master,基于 master 分支拉出來一個開發(fā)分支 dev,在 dev 上開發(fā)了一段時間后要把 master 分支提交的新內(nèi)容更新到 dev 分支,此時切換到 dev 分支,使用 git rebase master
等 dev 分支開發(fā)完成了之后,要合并到上游分支 master 上的時候,切換到 master 分支,使用 git merge dev
一、創(chuàng)建兩個GIT項目,project1和 project2,同時分別加入三個文件并提交master分支
$ git clone git@gitlab.xpaas.lenovo.com:baiyl3/project1.git
$ cd project1
$ touch file1 file2 file3
$ git add .
$ git commit -m '在項目一中初始化三個代碼文件'
$ git push -u origin master
$ git clone git@gitlab.xpaas.lenovo.com:baiyl3/project2.git
$ cd project2
$ touch file1 file2 file3
$ git add .
$ git commit -m '在項目二中初始化三個代碼文件'
$ git push -u origin master
從代碼提交時間軸圖看兩個項目現(xiàn)在的狀態(tài)都一致


二、分別給兩個項目創(chuàng)建三個分支 B1,B2,B3 (* 對應(yīng)數(shù)字)
$ 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最新提交點拉出來的三個新分支,如下圖從時間軸也可以看出


三、現(xiàn)在我們分別切換分支到 B1,B2,B3,并修改對應(yīng)的文件 file1,file2,file3,最后切換到 mastet 分支添加一個 README.md 文件,然后再看時間軸:

從上圖的結(jié)果可以看出,B1, B2, B3, master 四個分支分別在不同的時間點做了代碼提交,那么最后一次 master 上做的修改在 B1, B2, B3 三個分支上肯定沒有
四、此時在這三個分支上開發(fā)的同學(xué)發(fā)現(xiàn)他們要做的功能要在 master 最新的基礎(chǔ)上開發(fā),或者他們也想要 master 上最新的內(nèi)容,重點來了,現(xiàn)在我們怎么辦?方法有兩種,一種是使用 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 'git@gitlab.xpaas.lenovo.com: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 這條命令默認(rèn)使用了 --merge 的方式更新代碼,如果你不指定用 --rebase,有的時候就會發(fā)現(xiàn)日志里有這樣的一次提交 Merge branch 'dev' of gitlab.xpaas.lenovo.com:liuyy23/lenovo-mbg into dev 什么?自己分支合并到了自己分支,顯然這個沒有什么必要,而且在時間軸上也不好看,平白無故多了一條線出來,對于強迫癥的我來說看著就難受。。。
還有就是使用 rebase 之后,如果直接使用 git push origin B1 發(fā)現(xiàn)是不好使的,提示也說明了提交失敗的原因,我個人是這么理解的,使用 rebase 之后,master分支上比B1分支上多的修改,直接“插入”到了B1分支修改的內(nèi)容之后,也就是 master 分支的修改在 B1 分支上重演了一遍,相對遠(yuǎn)程 B1 分支而言,本地倉庫的B1分支的“基底”已經(jīng)變化了,直接 push 是不行的,所以確保沒有問題的情況下必須使用 --force 參數(shù)才能提交,這也就是官方對 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 分支開發(fā)完成了,要合并到 master 分支,根據(jù)基本原則,在 master 分支上都使用 git merge B1 就可以合并,看下圖結(jié)果:
$ git checkout master
$ git merge B1
Updating c782e83..00032a7
Fast-forward
file1 | 1 +
1 file changed, 1 insertion(+)
$ git push origin master


接下來對 B2,B3 分別在 project1 和 project2 上做相同的操作,我們看結(jié)果:


再看看命令行下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 就感覺所有人都在同一條直線上開發(fā)一樣,很干凈的log,看著很舒服,而一直使用 merge 的log看起來就很亂,我這只是4個分支的例子,我們項目每周基本都是十幾個分支,真的是看起來亂入一團哇。。。
這個例子中的操作都沒有出現(xiàn)不同分支修改同一個文件導(dǎo)致沖突的情況,實際開發(fā)中這種情況非常多,rebase 的時候出現(xiàn)沖突后 git 也會列出來哪些文件沖突了,等你解決沖突之后使用 git rebase --continue 就會繼續(xù)處理,所以為了避免這種沖突太多,而且不好解決,我們項目中基本都是一個需求就一個分支,而且開發(fā)過程中要隨時更新上游分支的內(nèi)容下來,確保在最新的代碼基礎(chǔ)上開發(fā),這也是 GIT 推薦的方式,盡可能多建分支開發(fā),而不是在一個開發(fā)分支上很多人操作,如果是這種情況建議不要用 rebase,另外負(fù)責(zé)分支合并的人在合并下游分支代碼的時候要確保你這個上游分支不要在這段時間內(nèi)提交代碼,有一個方法就是把上游分支 設(shè)置 protect
五、注意事項
- 更新當(dāng)前分支代碼的時候一定要使用
git pull origin xxx --rebase - 合并代碼的時候按照最新分支優(yōu)先合并為原則
- 要經(jīng)常從上游分支更新代碼,如果長時間不更新上游分支代碼容易出現(xiàn)大量沖突
Memo
本文轉(zhuǎn)載至 https://zhuanlan.zhihu.com/p/34197548