Git 的衝突與分支

關於衝突

當我們在使用 git 跟人家一起合作時,照著上一次講到的每次都是 pull, commit, push 這三個步驟。
只有一個人的時候還感覺不出有些什麼問題。
但是如果有一個狀況是,我跟其他人 pull 到的瞬間都是相同的程式碼,各自修改了不同的部份之後 commit 接著準備要 push,但好死不死對方比我早 push 回代管主機。
那換成比較慢的我要 push 回代管主機時會發現出現了問題。
主要原因是因為剛剛另一個人已經把代管主機的原始程式碼更新了,所以你原先 pull 到的內容已經過舊囉!

這其實也是為什麼在每次修改程式碼之前都會希望你做一次 pull 的主要原因之一,就是希望自己電腦裡的程式碼是跟代管主機上的版本一樣的新。
那麼這次由於對方已經快了我們一步導致我們 pull 的版本過舊,那我們要怎麼呢?
這種狀況一發生,他上面就會跟你說「版本過舊,請重新 pull」,那他怎麼說就怎麼做就可以了。

git pull

這時運氣好一點就沒有其他是要做了,因為你跟另一位開發者修改的部份是不同的部份,所以 Git 會很有智慧的幫你解決掉衝突,他會幫你合併代管主機上新版的程式碼到自己的電腦上。

接著再把這一版的程式碼 commit 後 push 回代管主機就行了。

但是如果好死不死看到了有關 conflict 的訊息,就代表跟另一位開發者同時修改到一樣的程式碼了。
看到 conflict 也不用太驚慌,去剛剛自己修改過程式碼的地方, Git 會提醒你跟另一個開發者衝突到的地方。
不過他是會直接顯示在各個有衝突的檔案裡,所以翻一翻剛剛修改過的檔案,應該會有像下面這樣的文字內容。

 <<<<<<< HEAD:mergetest
 我打的內容
 =======
 別人打的內容
 >>>>>>> 4e2b407f501b68f8588aa645acafffa0224b9b78:mergetest

這個目的就是要檢查哪一個才是要保留的內容,會由 ======= 將你寫的內容跟別人改的內人分隔開來。
接著刪掉不要的內容,同時也包括 <<<<<<<, =======, >>>>>>> 這些分隔符號,他們只是代表著衝突的開始、分隔跟結束。
所有衝突都修改之後就能開開心心的 commit 跟 push 囉!

關於分支與合併

如果我們在進行開發的時候,程式已經夠穩定, bug 已經少許多了。那我們又想開發新功能,又知道開發新功能一定又會將更多的 bug 帶到程式碼當中。
有沒有更好的解決方案呢?

還有一種狀況就是,當我的軟體已經發佈了第 1.0 版之後,為了滿足繼續維護 1.0 版,減少 1.0 版的重大 bug ,但又要同時開發有許多新功能的 2.0 版。這兩個需求,版本控制系統很早就聽到了,而 Git 當然也能解決這些問題。

在 Git 中,可以使用 branch 去作到分支的效果。簡單來講就是內容同時存在著很多不同的版本。
如果是要解決我上面提到的每個發佈的版本都還要繼續維護,但又不拖累未來的開發,就能在每個發佈的版本繼續開個支線去維護。

稍微做一下示意圖:

       0.1.1   0.2.1
        /       /
     0.1.0   0.2.0
------/------/----------[開發版]-->主線開發

又或是想要以不影響主線程式的穩定為前提,而繼續開發新功能,也能做個分支出來。等到開發完新的功能之後再合併回主線。

branch

我們可以很輕易的使用 branch 去打開一個支線

git branch [支線名稱]

如果沒有打上支線名稱的話, Git 會顯示目前所有的支線。如果打上支線名稱的話, Git 就自然而然會幫你生出一個支線。

checkout

接著,產生完支線之後,就是要有辦法可以在各個主線、支線中切換,我們可以使用 checkout 這個指令來幫忙。

git checkout 支線名稱/檔案名

當 checkout 一個支線名稱的時候,在 Git 中的確是切換到不同的支線。但你會發現,你打開的檔案根本沒有隨著支線的切換,而有不同的內容。
主要是因為每次都將所有檔案內容都改成該支線的內容對電腦來說是很沒效率的。所以可以透過 checkout 檔案名稱 來把這份檔案的內容改成該支線應該要有的內容。

附帶一提,有些地方會說 checkout 可以作到還原的動作。這是因為在你 commit 之後做了修改,但又沒有 commit 這些修改的話, checkout 照剛剛說的是會把這份檔案改成該支線應該有的內容,所以說這份檔案中所有尚未 commit 的內容當然都會消失囉!

merge

最後當然就是將開發新功能的分支合併回主線當中囉!不過這並不是必要的,因為就我剛剛所說的,對每個發佈的版本持續做維護,其實沒必要將這些額外的分支合併回主線,那如果我真的想把支線合併回來該怎麼做呢?

git merge 支線名稱

這個指令就能將整個支線合併回主線囉!不過要特別注意的就是,合併依然會有 conflict 的問題,而解決方法就如同我前面講的那樣,照著前述的方法解決就行了。