Git 分支與合併

weixin_33766168發表於2016-10-19

前言:以下內容全部截選自實驗樓課程【Git 實戰教程】,更多Git 使用介紹,可以點選這裡,進行檢視~


一、分支與合併

Git的分支可以讓你在主線(master分支)之外進行程式碼提交,同時又不會影響程式碼庫主線。

分支的作用體現在多人協作開發中,比如一個團隊開發軟體,你負責獨立的一個功能需要一個月的時間來完成,你就可以建立一個分支,只把該功能的程式碼提交到這個分支,而其他同事仍然可以繼續使用主線開發,你每天的提交不會對他們造成任何影響。當你完成功能後,測試通過再把你的功能分支合併到主線。

1.分支

一個Git倉庫可以維護很多開發分支。現在我們來建立一個新的叫 experimental的分支:

$ git branch experimental

執行git branch命令可以檢視當前的分支列表,已經目前的開發環境處在哪個分支上:

$ git branch experimental* master

experimental 分支是你剛才建立的,master分支是Git系統預設建立的主分支。星號標識了你當工作在哪個分支下,輸入git checkout 分支名可以切換到其他分支:

$ git checkout experimentalSwitched to branch 'experimental'

切換到experimental分支,切換完成後,先編輯裡面的一個檔案,再提交(commit)改動,最後切換回 “master”分支:

# 修改檔案file1
$ echo "update" >> file1
# 檢視當前狀態
$ git status
# 新增並提交file1的修改
$ git add file1
$ git commit -m "update file1"
# 檢視file1的內容
$ cat file1
test
update
# 切換到master分支
$ git checkout master

檢視下file1中的內容會發現剛才做的修改已經看不到了。因為剛才的修改時在experimental分支下,現在切換回了master分支,目錄下的檔案都是master分支上的檔案了。

現在可以在master分支下再作一些不同的修改:

# 修改檔案file2
$ echo "update again" >> file2
# 檢視當前狀態
$ git status
# 新增並提交file2的修改
$ git add file2
$ git commit -m "update file2 on master"
# 檢視file2的內容
$ cat file2
test
update again

這時,兩個分支就有了各自不同的修改,分支的內容都已經不同,如何將多個分支進行合併呢?

2.合併

可以通過下面的git merge命令來合併experimental到主線分支master:

# 切換到master分支
$ git checkout master
# 將experimental分支合併到master
$ git merge -m 'merge experimental branch' experimental

-m引數仍然是需要填寫合併的註釋資訊。

由於兩個branch修改了兩個不同的檔案,所以合併時不會有衝突,執行上面的命令後合併就完成了。

如果有衝突,比如兩個分支都改了一個檔案file3,則合併時會失敗。首先我們在master分支上修改file3檔案並提交:

# 切換到master分支
$ git checkout master
# 修改file3檔案
$ echo "master: update file3" >> file3
# 提交到master分支
$ git commit -a -m 'update file3 on master'

然後切換到experimental,修改file3並提交:

# 切換到experimental分支
$ git checkout experimental
# 修改file3檔案
$ echo "experimental: update file3" >> file3
# 提交到master分支
$ git commit -a -m 'update file3 on experimental'

切換到master進行合併:

$ git checkout master
$ git merge experimental
Auto-merging file3
CONFLICT (content): Merge conflict in file3
Automatic merge failed; fix conflicts and then commit the result.

合併失敗後先用git status檢視狀態,會發現file3顯示為both modified,檢視file3內容會發現:

$ cat file3
test
<<<<<<< HEAD
master: update file3
=======
experimental: update file3
>>>>>>> experimental

上面的內容也可以使用git diff檢視,先前已經提到git diff不加引數可以顯示未提交到快取區中的修改內容。

可以看到衝突的內容都被新增到了file3中,我們使用vim編輯這個檔案,去掉git自動產生標誌衝突的<<<<<<等符號後,根據需要只保留我們需要的內容後儲存,然後使用git add file3git commit命令來提交合並後的file3內容,這個過程是手動解決衝突的流程。

# 編輯衝突檔案
$ vim file3
# 提交修改後的檔案
$ git add file3
$ git commit -m 'merge file3'

當我們完成合並後,不再需要experimental時,可以使用下面的命令刪除:

$ git branch -d experimental

git branch -d只能刪除那些已經被當前分支的合併的分支. 如果你要強制刪除某個分支的話就用git branch –D

撒銷一個合併

如果你覺得你合併後的狀態是一團亂麻,想把當前的修改都放棄,你可以用下面的命令回到合併之前的狀態:

$ git reset --hard HEAD^
# 檢視file3的內容,已經恢復到合併前的master上的檔案內容
$ cat file3

快速向前合併

還有一種需要特殊對待的情況,在前面沒有提到。通常,一個合併會產生一個合併提交(commit), 把兩個父分支裡的每一行內容都合併進來。

但是,如果當前的分支和另一個分支沒有內容上的差異,就是說當前分支的每一個提交(commit)都已經存在另一個分支裡了,git 就會執行一個“快速向前"(fast forward)操作;git 不建立任何新的提交(commit),只是將當前分支指向合併進來的分支。

二、高階分支與合併

1.在合併過程中得到解決衝突的協助

git會把所有可以自動合併的修改加入到索引中去, 所以git diff只會顯示有衝突的部分. 它使用了一種不常見的語法:

$ git diff

回憶一下, 在我們解決衝突之後, 得到的提交會有兩個而不是一個父提交: 一個父提交會成為HEAD, 也就是當前分支的tip; 另外一個父提交會成為另一分支的tip, 被暫時存在MERGE_HEAD. 在合併過程中, 索引中儲存著每個檔案的三個版本. 三個"檔案暫存(file stage)"中的每一個都代表了檔案的不同版本:

$ git show :1:file.txt 
$ git show :2:file.txt
$ git show :3:file.txt

當你使用git diff去顯示衝突時, 它在工作樹(work tree), 暫存2(stage 2)和暫存3(stage 3)之間執行三路diff操作, 只顯示那些兩方都有的塊(換句話說, 當一個塊的合併結果只從暫存2中得到時, 是不會被顯示出來的; 對於暫存3來說也是一樣).

上面的diff結果顯示了file.txt在工作樹, 暫存2和暫存3中的差異. git不在每行前面加上單個'+'或者'-', 相反地, 它使用兩欄去顯示差異: 第一欄用於顯示第一個父提交與工作目錄檔案拷貝的差異, 第二欄用於顯示第二個父提交與工作檔案拷貝的差異. (參見git diff-files中的"COMBINED DIFF FORMAT"取得此格式詳細資訊.) 在用直觀的方法解決衝突之後(但是在更新索引之前), diff輸出會變成下面的樣子:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,1 @@@
- Hello world
-Goodbye
++Goodbye world

上面的輸出顯示瞭解決衝突後的版本刪除了第一個父版本提供的"Hello world"和第二個父版本提供的"Goodbye", 然後加入了兩個父版本中都沒有的"Goodbye world". 一些特別diff選項允許你對比工作目錄和三個暫存中任何一個的差異:

$ git diff -1 file.txt 
$ git diff --base file.txt 
$ git diff -2 file.txt 
$ git diff --ours file.txt 
$ git diff -3 file.txt
$ git diff --theirs file.txt

git log和gitk命令也為合併操作提供了特別的協助:

$ git log --merge
$ gitk --merge

這會顯示所有那些只在HEAD或者只在MERGE_HEAD中存在的提交, 還有那些更新(touch)了未合併檔案的提交. 你也可以使用git mergetool, 它允許你使用外部工具如emacs或kdiff3去合併檔案. 每次你解決衝突之後, 應該更新索引:

$ git add file.txt

完成索引更新之後, git-diff(預設地)不再顯示那個檔案的差異, 所以那個檔案的不同暫存版本會被"摺疊"起來.

2.多路合併

你可以一次合併多個頭, 只需簡單地把它們作為git merge的引數列出. 例如,

$ git merge scott/master rick/master tom/master

相當於

$ git merge scott/master
$ git merge rick/master
$ git merge tom/master

3.子樹

有時會出現你想在自己專案中引入其他獨立開發專案的內容的情況. 在沒有路徑衝突的前提下, 你只需要簡單地從其他專案拉取內容即可.
如果有衝突的檔案, 那麼就會出現問題. 可能的例子包括Makefile和其他一些標準檔名. 你可以選擇合併這些衝突的檔案, 但是更多的情況是你不願意把它們合併. 一個更好解決方案是把外部專案作為一個子目錄進行合併.

這種情況不被遞迴合併策略所支援, 所以簡單的拉取是無用的. 在這種情況下, 你需要的是子樹合併策略.

這下面例子中, 我們設定你有一個倉庫位於/path/to/B (如果你需要的話, 也可以是一個URL). 你想要合併那個倉庫的master分支到你當前倉庫的dir-B子目錄下. 下面就是你所需要的命令序列:

$ git remote add -f Bproject /path/to/B (1)
$ git merge -s ours --no-commit Bproject/master (2)
$ git read-tree --prefix=dir-B/ -u Bproject/master (3)
$ git commit -m "Merge B project as our subdirectory" (4)
$ git pull -s subtree Bproject master (5)

子樹合併的好處就是它並沒有給你倉庫的使用者增加太多的管理負擔. 它相容於較老(版本號小於1.5.2)的客戶端, 克隆完成之後馬上可以得到程式碼.

然而, 如果你使用子模組(submodule), 你可以選擇不傳輸這些子模組物件. 這可能在子樹合併過程中造成問題.

另外, 若你需要修改內嵌外部專案的內容, 使用子模組方式可以更容易地提交你的修改.


以上內容截選自實驗樓課程【Git 實戰教程】,更多Git 使用介紹,可以點選這裡,進行檢視,課程是為《Git Community Book 中文版》提供的配套實驗,該書籍匯聚了Git社群的很多精華, 幫助你儘快的掌握Git,該課程則通過動手實踐的方式帶你掌握這些精華。

相關文章