背景與做法
我現在有兩個分支,分別是 dev 開發分支與 master 主分支
通常我在 dev 分支寫程式,確定沒問題後就會到 master 主分支 cherry-pick 我要的 dev 提交回來
不過有時總有意外,例如忘記切回 dev 就在 master 開始寫,或者是很急也很簡單的 bug 修正也會直接在 master 修正
另外,dev 做了很多提交,但有些提交可能暫時先不合併到 master,也許是還沒測試或還沒寫好之類的
所以,時間一長,master 跟 dev 就不會同步了。但我們會希望 dev 以正式環境的 master 來繼續開發下一個任務,這時候就會有兩種做法:
- 把 dev 分支刪掉,從 master 重新建一個 dev 分支出來
- 在 dev 拉 master 回來合併
做法與分析
第一種方法最簡單,但不實際,像是 dev 有 stash 還沒做完,或者有一些提交還沒測試好,就不能刪掉分支
第二種方法只是把 dev 缺乏的 master 提交補上,當然,有時候合併沒那麼順立,這就是我們今天要實做的重點。
基本上,我們是在 dev 把 master 分支從遠端倉庫拉回來合併。
也就是 pull, 其實就是做 fetch 跟 merge 的動作,之前都有提過,那就直接實做。
拉回
# 確定自己在 dev 分支
$ git branch
* dev
don_accton
master
# 從遠端倉庫拉回 master
$ git pull origin master
password: # 打密碼
Auto-merging sys/static/js/admin_leave_data.js
# 自動合併這個檔案
CONFLICT (content): Merge conflict in sys/static/js/admin_leave_data.js
# 發生內容衝突,合併不了
Auto-merging sys/models/leaveAdminModel.php
CONFLICT (content): Merge conflict in sys/models/leaveAdminModel.php
# 狀況同上
Automatic merge failed; fix conflicts and then commit the result.
# 自動合併失敗,請修好它重新提交
觀察
看來訊息還不是很完整,讓我們再仔細確認一下狀況:
$ git status
On branch dev
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Changes to be committed:
modified: sys/self/leaveCalculation.php
# 這個沒問題,已修改但尚未提交,因為我們合併發生衝突了
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: sys/models/leaveAdminModel.php
both modified: sys/static/js/admin_leave_data.js
# 就是這兩個檔案發生合併衝突了
簡單講,就是後悔可以打 git merge --abort
也適用於 cherry-pick, 那就回到還沒 pull 的狀態
如果要解決衝突,那就開啟衝突的檔案來修改
解決衝突
我用 notepad++ 開啟 leaveAdminModel.php, 搜尋 <<<
<<<<<<< HEAD
$where .= " and li.itemsName = '".$data['itemsName']."'";
=======
$where .= " and li.itemsName like '".$data['itemsName']."%'";
>>>>>>> 2c428fc952f29efcf30890a82e4761fccf45631c
- <<< 到 === 之間的內容是 dev 原本的檔案內容
- === 到 >>> 之間的內容是 master 那邊新的檔案內容
接下來就是要確定最後想改成怎樣,通常是新的留下,舊的刪掉
反正改的人要很清楚就是了,記得那些多餘的符號都要刪掉喔
這邊我們不講程式碼,直接秀修改的結果,我留下 master 版的程式碼:
$where .= " and li.itemsName like '".$data['itemsName']."%'";
接著繼續搜尋 <<<
看看,因為一個檔案可能會有多個衝突點
都處理好後存檔離開
另一個檔案也是一樣的做法,這個檔案我改比較多,就不詳列了
當然,你也可以直接修改成不是這兩個版本的最終版,反正都會重新提交
都處理好後就進入我們熟悉的 git add .
與 git commit
在 git add .
時 git status
可以看到目前是處在解決衝突的狀態
其中,在 git commit
會有一個預設的訊息:
Merge branch 'master' of 192.168.0.12:/xxxx/yyyy into dev
當然可以改成我們想要的 commit 訊息
合併 master
# 存檔繼續會看到
$ git commit
[dev a1fb85b] 合併 master
# 再次確認
$ git status
On branch dev
nothing to commit, working tree clean
# git log -1
commit a1fb85bb1d2fbbb4c4e83064b852aa854da5394e (HEAD -> dev)
Merge: 0a867c0 2c428fc
Author: Logo-Kuo <logo@forblind.org.tw>
Date: Fri Dec 25 11:03:06 2020 +0800
合併 master
好了,合併完成
補充
在 dev 中,原本 master 提交的 commit 訊息也還在,再加一個我們剛剛推的 合併 master
提交
如果在 commit 時加個參數 --no-edit
就不會要你寫 commit 訊息,但還是會有預設的,跟原本預設的 merge 內容不同
這次多了你處理衝突的檔名資訊,如下:
commit fc06b254d95eb4c81ee18cc14e5574102837c13c (HEAD -> dev)
Merge: 0a867c0 2c428fc
Author: Logo-Kuo <logo@forblind.org.tw>
Date: Fri Dec 25 11:41:27 2020 +0800
Merge branch 'master' of 192.168.0.11:/xxxx/yyyy into dev
# Conflicts:
# sys/models/leaveAdminModel.php
# sys/static/js/admin_leave_data.js
其實處理衝突的檔案資訊原本就在,只是 git commit
時是被 #
註解掉了而已
那如果你完全不想要有這個 merge 的訊息,可以考慮使用 git pull origin master --rebase
為什麼說是「考慮」呢?因為我們知道 rebase 會把所有的 commit 重做一遍,所以假設兩個分支差很多
中間有一些 merge 之類的,那你 rebase 就要處理所有的衝突,但 merge 只要解決這次的衝突,所以使用時機自己評估
我自己是非不得已不會使用 rebase, dev 分支有合併訊息是很正常的,讓正式主分支保持乾淨就好了
衝突的由來
針對這個問題,我之前也一直無法理解,就是怎樣的狀況會發生衝突。
當然,不同人相互寫同一個分支拉下推上的會衝突不意外,但怎樣叫做衝突?
我們以這次 dev 合併 master 的例子來說,事實上,master 我修改的檔案不只兩個,那為什麼只有兩個檔案發生衝突?
其實是 git 會根據最近的提交去比對,假設最近的提交改了 a 檔案的第 5 行
那在合併時新的提交也修改到 a 的第 5 行,這樣 git 不知道誰才是最終需要的版本,就會發生衝突,
然後列出衝突點讓我們決定最終的版本。
我以前還以為 git 是會自動以最新日期來直接蓋掉舊的衝突點,但預設應該不是這樣
但不知道有沒有這樣的設定就是了