一次合並會結合兩個或多個歷史提交分支。盡管Git還支持同時合並三個,四個或多個分支,但是大多數情況下,一次合並只結合兩個分支。
在Git中,合並必須發生在一個版本庫中------也就是說,所有要進行合並的分支必須在同一個版本庫中。版本庫中的分支怎么來的並不重要。
當一個分支中的修改與另一個分支中的修改不發生沖突的時候,Git會自動計算合並結果,並創建一個新的提交來表標識新的統一狀態。
但是當分支沖突時,Git並不解決沖突,所以,當Git無法自動合並時,需要在所有沖突都被認為解決后做一次最終提交。
1,一個最簡單的合並的例子
創建一個新的版本庫,新建文件"file",添加圖片中的內容。然后提交到版本庫。
ok,在當前分支 master 下繼續添加文件”other_file“,添加圖片中的內容,並提交到版本庫中。
至此,版本庫里已經有一個包含連詞提交的分支 master,每次提交引入都引入了一個新文件。接下來,新建並切換到不同的分支。
新的分支 alternate 基於 master^(master的前一次提交,本例中就代表master的初始提交,master~數字 代表是同一一個意思),在該分支下修改文件”file“,新增圖片中的內容,最后提交到版本庫中。
現在就有了兩條分支,兩條分支都開始了自己的工作,都針對文件”file“進行了修改,因為這兩個修改並不影響相同文件的相同部分,所以合並應該會順利進行,不會發生事務。
git merge 操作是區分上下文的。當前分支始終是目標分支,其他一個或多個分支之中是被合並到當前分支。現在想要 alternate 分支合並到 master 分支,所以在操作之前必須檢出 master 分支,然后再進行 git merge 操作。
nice,合並順利進行,兩個分支對文件”file“的修改順利的合並在一起。然后來看看提交歷史:
兩個分支在 53d24be 這個提交處分開(當時的 master 是提交了other file,所以分支基於的 master^ 就是"init 3 line in file"這個提交),分支修改了文件”file“,產生了一個提交 4a547440。最后執行提交產生一個新的提交 475e5e8 。
可以看出,Git執行合並的操作都會產生一個合並后的提交,並添加到當前分支中,而被合並分支不受合並的影響。
2,有沖突的合並
不廢話,直接來實際的操作。首先在 master 分支上,繼續修改文件“file”,然后提交到版本庫
然后,切換到 alternate 分支上,用不同的內容修改文件“file”,然后也提交到版本庫
繼續,檢出 master 分支並嘗試着進行合並
Git已經提示我們了:合並的時候在文件“file”檢測到沖突,自動合並失敗,解決沖突后才能提交。一旦遇到沖突,一定要使用 git diff 命令來查看一下沖突的程度
結果顯示了 工作區 和 暫存區 之間的差異。
在傳統的 UNIX/Linux 系統中 diff 命令的輸出風格里,改變的內容顯示在<<<<<<<和=======之間;替代的內容在=======和>>>>>>>之間。
在解決合並沖突時,根據實際工作情況自己選擇任何解決方案。包括只取一邊或另一邊的版本,或者兩邊的混合,甚至是全新的內容。
ok,開始手動解決沖突,解決后。文件“file”的內容如圖,如果對沖突解決很滿意,那么就應該 git add 命令,為合並提交而暫存之。
Git的提示已經很明顯了:沖突已解決,但是依然處於合並狀態(當前的分支為 master | MERGING),使用 git commit 命令來結束掉本次合並。執行 git commit 命令,Git會進入到vim編輯器中,並准備了一條模板消息。ok,搞定!
深入處理合並沖突
重新初始化一個版本庫,創建一個文件“hello”,內容為“hello”,提交到版本庫中
創建一個名為 alt 的分支,在文件“hello”末尾追加內容 world。然后提交到版本庫
切回到 master 分支,此時文件”hello“的內容為 hello,在末尾追加 worlds,並提交大版本庫
此時,hello的內容,master 分為 hello worlds,alt 分支為 hello world。嘗試合並 alt 分支,一定會發生沖突,Git提示文件”hello“里有沖突,使用命令 git status 查看當前狀態,Git會定位沖突的文件以及提示可以做的操作:
1,Git提示我們有未完成的合並。
2,建議我們解決沖突后運行 git commit 命令,又或者可以執行 git merge --abort 命令來取消本次合並。
3,Git提示我們沖突發生在 hello 這個文件里。
現在,使用 git diff 命令來檢查沖突的細節:
這里的 alt 有一個特殊的名字------MERGE_HEAD,所以我們完全可以拿 HEAD 和 MERGE_HEAD 版本跟工作區("合並的")版本進行比較:
在較新版本的Git中,git diff --ours 是 git diff HEAD 的同義詞,--ours 更好理解。同樣,git diff MERGE_HEAD 可以寫成 git diff --theirs。
在解決沖突的過程中,可以使用一些特殊的 git log 選項來幫助我們尋找出變更的確切來源和原因:git log --merge --left-right -p <file|path>,git log 的選項如下
--merge:只顯示根產生沖突的文件相關的提交。
--left-right:如果提交來自合並的”左邊“則顯示<(”我們的“版本,當前分支的版本),如果提交來自由合並的”右邊“則顯示>(”它們的“版本,目標分支的版本)
-p:顯示提交消息和每個提交相關聯的補丁。
<file|path>:如果版本庫很復雜,且沖突的文件很多,可以指定確切的路徑或文件。
可以看到,輸出的信息還是很多的。有一種手段可以緩解來自大合並中繁多的沖突的痛苦:那就是定義良好的小提交。Git對小規模提交處理的很好,因此沒有必要等到最后才提交一個又龐大又影響廣泛的修改。小規模的提交和更頻繁的合並周期可以減少解決沖突的痛苦。
那么,Git是如何追蹤沖突的呢?
主要有以下幾個部分:
1,.git/MERGE_HEAD,任何使用提到 MERGE_HEAD,Git都知道去查看哪個文件。
2,.git/MERGE_MSG,包含當前解決沖突后執行 git commit 命令是用到的日志消息。
3,Git的索引項包含每個沖突文件的三個副本:合並基礎,”我們的“版本 和 ”他們的“版本,分別給這三個副本分配各自的編號 1,2,3。
4,沖突的版本不保存在暫存區。只保存在工作區。
查看Git索引項是如何存儲的,可以使用 git ls-files 底層。如果指向查看根沖突相關的文件,可以使用 -u 選項
可以看出,hello 文件存儲了三次,用前面說過的 git cat-file -p 命令來查看各個版本的內容
更可以使用 git diff 來比較兩個指定版本之間有什么區別
了解了Git是如何追蹤沖突之后,就可以很淡定的處理沖突了。
這里執行 git add 命令是把索引項重寫,只有一份 hello 的副本。
必須解決索引中記錄的所有沖突文件。只要有未解決的沖突,就不能提交。因此,當解決一個文件的沖突之后,執行 git add(或者 git rm,git-update-index等)以清除它的沖突狀態。
注意:不要對有沖突標記的文件執行 git add命令。雖然會清楚索引中的沖突,並允許提交,但是文件內容將是錯誤的。
最終,執行 git commit 命令,解決沖突過程中產生的 .git/MERGE_HEAD .git/MERGE_MSG 都會被Git刪除。