git merge 是用時間前後決定merge結果的,後面會覆蓋前面的?git
答 :git 是分佈式的文件版本控制系統,在分佈式環境中時間是不可靠的,git是靠三路合併算法進行合併的。算法
答:git 儘管兩行內容不同,git 會進行取捨,當git沒法進行取捨的時候纔會進行人工解決衝突分佈式
咱們知道git 合併文件是以行爲單位進行一行一行進行合併的,可是有些時候並非兩行內容不同git就會報衝突,由於smart git 會幫咱們自動幫咱們進行取捨,分析出那個結果纔是咱們所指望的,若是smart git 都沒法進行取捨時候纔會報衝突,這個時候才須要咱們進行人工干預。那git 是如何幫咱們進行Smart 操做的呢?工具
二路合併算法就是講兩個文件進行逐行對別,若是行內容不一樣就報衝突。
佈局
Mine 表明你本地修改
Theirs 表明其餘人修改
假設對於同一個文件,出現你和其餘人一塊兒修改,此時若是git來進行合併,git就懵逼了,由於Git既不敢得罪你(Mine),也不能得罪他們(Theirs) 無理無據,git只能讓你本身搞了,可是這種狀況太多了並且也沒有必要…this
三路合併就是先找出一個基準,而後以基準爲Base 進行合併,若是2個文件相對基準(base)都發生了改變 那git 就報衝突,而後讓你人工決斷。不然,git將取相對於基準(base)變化的那個爲最終結果。
spa
Base 表明上一個版本,即公共祖先
Mine 表明你本地修改
Theirs 表明其餘人修改
這樣當git進行合併的時候,git就知道是其餘人修改了,本地沒有更改,git就會自動把最終結果變成以下,這個結構也是大多merge 工具的常見佈局,好比IDEA
.net
若是換成下面的這樣,就須要人工解決了:
翻譯
上面就是git merge 最基本的原理 「三路合併」。版本控制
下面面的合併就是咱們常見的分支graph,結合具體分析。
上面①~⑨表明一個個修改集合(commit)每一個commit都有一個惟一7位SHA-1惟一表示。
①,②,④,⑦修改集串聯起來就是一個鏈,此時用master指向這個集合就表明master分支,分支本質是一個快照,其實類比C中指針
一樣dev分支也是由一個個commit組成
如今在dev分支上因爲各類緣由要運行git merge master須要把master分支的更新合併到dev分支上,本質上就是合併修改集 ⑦(Mine) 和 ⑧(Theirs) ,此時咱們要 利用DAG(有向無環圖)相關算法找到咱們公共的祖先 ②(Base)而後進行三方合併,最後合併生成 ⑨
git merge-base –all commit_id1(Yours/Theirs) commit_id2(Yours/Theirs) 就能找出公共祖先的commitId(Base)
圖雖然複雜 可是核心原理是不變的,下面咱們看 另一個稍微高級一點的核心原理」遞歸三路合併」 也是咱們很常見看到 git merge 輸出的 recursive strategy
下圖中咱們若是要合併 ⑦(source) -> ⑥(destination):
簡短描述下 如何會出現上面的圖:
從上面咱們DAG圖能夠知道公共祖先有③和④,那到底選擇哪一個呢,咱們分別來看:
若是選擇③做爲公共祖先 根據最基本的三路合併,能夠看到最終結果⑧ 將須要手動解決衝突 /foo.c = BC???
若是選擇④做爲公共祖先 根據最基本的三路合併,能夠看到最終結果⑧ 將獲得 /foo.c=C
最終期待的結果是什麼?
因此 咱們的最佳公共祖先應該是4,最終結果應該是 /foo.c = C
git 如何選擇公共祖先呢?
你可能會說用 git merge-base ⑥ ⑦ 輸出的是 ④ 可是git 就真的是用 ④ 作祖先嗎 ?答案是No
When the history involves criss-cross merges, there can be more than one best common ancestor for two commits. For example, with this topology:
---1---o---A \ / X / \ ---2---o---o---B
both 1 and 2 are merge-bases of A and B. Neither one is better than the other (both are best merge bases). When the –all option is not given, it is unspecified which best one is output.
從git的解釋中,咱們就知道 若是有2個都是最佳公共祖先時候,這個時候git 會隨便輸出一個不肯定公共祖先。
git 是這樣進行合併的:
那什麼又叫遞歸(recursive)合併呢 ? 咱們合併 ⑥ 和 ⑦ 的時候,咱們將其 2 個公共祖先③ 和 ④ 進行 merge 爲 X ,在合併 ③ 和 ④時候 咱們又須要找到 他們的公共祖先,此時可能又有多個公共祖先,咱們又須要將他們先進行合併,如此就是遞歸了 也就是 recursive merge,以下:
Fast-Forward 翻譯爲快速前進,不少時候咱們在找2個修改集合X,Y 公共祖先的時候,會發現公共祖先就是他們中的一個,此時咱們進行merge 的時候,就是Fast-Forward便可,不會產生一個新的Commit 用於merge X和Y 。以下
當merge ② 和 ⑥時候 因爲②是公共祖先,因此進行Fast-Forward 合併,直接指向⑥ 不用生成一個新的⑧進行merge了。
如今 f 提交是咱們正在合併的提交
若是如今找 e 和 d 的共同祖先,你會發現並不惟一,b 和 c 都是。那麼此時怎麼合併呢?
git 會首先將 b 和 c 合併成一個虛擬的提交 x,這個 x 看成 e 和 d 的共同祖先。
而要合併 b 和 c,也須要進行一樣的操做,即找到一個共同的祖先 a。
咱們這裏的 a、b、c 只是個比較簡單的例子,實際上提交樹每每更加複雜,這就須要不斷重複以上操做以便找到一個真實存在的共同祖先,而這個操做是遞歸的。這即是「遞歸三路合併」的含義。
這是 git 合併時默認採用的策略。
git 還有很是簡單的快進式(Fast-Forward)合併。快進式合併要求合併的兩個分支(或提交)必須是祖孫/父子關係。例如上面的 e 和 d 並不知足此關係,因此沒法進行快進式合併。
在上面的例子合併出了 f 以後,若是將 t/walterlv 合併到 master,那麼就可使用快進式合併。這時,直接將 master 分支的 HEAD 指向 f 提交即完成了合併。固然,能夠生成也能夠不生成新的 g 提交,但內容與 f 的內容徹底同樣。
https://blog.csdn.net/u012937...
https://blog.csdn.net/WPwalte...