首頁  >  文章  >  開發工具  >  git中什麼是merge

git中什麼是merge

青灯夜游
青灯夜游原創
2021-12-29 17:41:3619051瀏覽

在git中,merge的意思為“合併”,該命令是用於將兩個或兩個以上的開發歷史合併在一起的操作;merge命令可用於git-pull中,來整合另一代碼倉庫中的變化;也可用於從一個分支到另一個分支的合併中。

git中什麼是merge

本教學操作環境:Windows7系統、Git2.30.0版、Dell G3電腦。

git-merge完全解析

Git的git-merge是在Git中頻繁使用的一個命令,很多人都覺得git合併是一個非常麻煩的事情,一不小心就會遇到遺失程式碼的問題,從而對git望而卻步。本文基於Git 2.8.2對git-merge指令進行完整詳細的介紹,特別是關於交叉合併所帶來的程式碼遺失問題,在文末給出自己的建議,希望能夠幫助到git的使用者。本文所介紹的內容是基於Git 2.8.2

git-merge指令是用來將兩個或兩個以上的開發歷史合併在一起的操作,通常也可寫:git merge。

1.git-merge相關的選項參數

1.1摘要

#在git-merge指令中,有以下三種使用參數:

  • git merge [-n] [--stat] [--no-commit] [--squash] [--[no-]edit] [-s <strategy>] [-X ] [-S[<keyid>]] [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...]</commit></msg></keyid></strategy>
  • #git merge <msg> HEAD <commit>...</commit></msg>
  • git merge --abort

1.2git -merge簡介

git-merge指令是用於從指定的commit(s)合併到目前分支的操作​​。

註:這裡的指定commit(s)是指從這些歷史commit節點開始,一直到目前分開的時候。

git-merge指令有以下兩種用途:

  1. 用於git-pull中,來整合另一程式碼倉庫中的變化(即:git pull = git fetch git merge)
  2. 用於從一個分支到另一個分支的合併

假設下面的歷史節點存在,並且當前所在的分支為“master”:

git中什麼是merge

那麼git merge topic指令將會將在master分支上二者共同的節點(E節點)之後分開的節點(即topic分支的A B C節點)重現在master分支上,直到topic分支目前的commit節點(C節點),並位於master分支的頂部。並且沿著master分支和topic分支創建一個記錄合併結果的新節點,該節點帶有使用者描述合併變化的資訊。

即下圖中的H節點,C節點和G節點都是H節點的父節點。

git中什麼是merge

1.3git merge <msg> HEAD <commit>...</commit></msg>指令

#該指令的存在是由於歷史原因,在新版本中不應該使用它,應該使用git merge -m <msg> <commit>....</commit></msg>進行替代

1.4 git merge --abort指令

該指令僅在合併後導致衝突時才使用。 git merge --abort將會拋棄合併過程並且嘗試重建合併前的狀態。但是,當合併開始時如果存在未commit的文件,git merge --abort在某些情況下將無法重現合併前的狀態。 (特別是這些未commit的檔案在合併的過程中將會被修改時)

警告:當執行git-merge時含有大量的未commit檔案很容易讓你陷入困境,這將使你在衝突中難以回退。因此非常不鼓勵在使用git-merge時存在未commit的文件,建議使用git-stash命令將這些未commit文件暫存起來,並在解決衝突以後使用git stash pop把這些未commit檔案還原出來。

2.參數

本部分用於介紹git-merge指令中使用的參數

2.1--commit --no-commit

--commit參數使得合併後產生一個合併結果的commit節點。此參數可以覆寫--no-commit
--no-commit參數使得合併後,為了防止合併失敗並不自動提交,能夠給使用者一個機會在提交前審視和修改合併結果。

2.2--edit-e以及--no-edit

--edit-e用於在成功合併、提交前呼叫編輯器來進一步編輯自動產生的合併資訊。因此使用者能夠進一步解釋和判斷合併的結果。
--no-edit參數能夠用於接受自動合併的資訊(通常情況下並不會鼓勵這樣做)。

如果你在合併時已經給定了-m參數(下文介紹),使用--edit(或-e)依然是有用的,這將在編輯器中進一步編輯-m所含的內容。

舊版的節點可能並不允許使用者去編輯合併日誌資訊。

2.3--ff指令

--ff是指fast-forward指令。當使用fast-forward模式進行合併時,將不會創造一個新的commit節點。預設情況下,git-merge採用fast-forward模式。
關於fast-forward模式的詳細解釋,請看我的另一篇文章:一個成功的Git分支模型的「關於fast forward」一節。

2.4--no-ff指令

即使可以使用fast-forward模式,也要建立一個新的合併節點。這是當git merge在合併一個tag時的預設行為。

2.5--ff-only指令

除非目前HEAD節點已經up-to-date(更新指向最新節點)或是能夠使用fast-forward模式進行合併,否則的話將拒絕合併,並傳回一個失敗狀態。

2.5 --log[=<n>]</n>--no-log

--log[=&lt ;n>]將在合併提交時,除了含有分支名以外,還將含有最多n個被合併commit節點的日誌資訊。
--no-log並不會列出該資訊。

2.6 --stat, -n, --no-stat指令

##--stat 參數會在合併結果的末端顯示檔案差異的狀態。檔案差異的狀態也可以在git設定檔中的merge.stat配置。 相反,
-n, --no-stat參數將不會顯示該資訊。

2.7

--squash--no-squash

--squash 當一個合併發生時,從目前分支和對方分支的共同祖先節點之後的對方分支節點,一直到對方分支的頂部節點將會壓縮在一起,使用者可以經過審視後進行提交,產生一個新的節點。

注意1:此參數與

--no-ff衝突

#注意2:此參數使用後的結果類似在當前分支提交一個新節點。在某些情況下這個參數非常有用,例如使用Git Flow時(關於Git Flow,請參考:一個成功的Git分支模型),功能分支在進行一個功能需求的研發時,開發者可能在本地提交了大量且無意義的節點,當需要合併到develop分支時,可能只需要用一個新的節點來表示這一長串節點的修改內容,這時

--squash指令將會發揮作用。此外,如果功能分支的多次提交並不是瑣碎而都是有意義的,使用--no-ff命令更為合適。
--no-squash的作用正好相反。

2.8

-s --strategy=

-s --strategy=用於指定合併的策略。預設情況如果沒有指定該參數,git將依照下列情況採用預設的合併策略:

    合併節點只含有單一父節點時(如採用fast-forward模式時),採用recursive策略(下文介紹)。
  1. 合併節點含有多個父節點時(如採用no-fast-forward模式時),採用octopus策略(下文介紹)。
2.9

-X

- s 時指定該策略的特定參數(下文介紹)。

2.10

--verify-signatures, --no-verify-signatures

用於驗證被合併的節點是否帶有GPG簽名,並在合併中忽略那些不帶有GPG簽名驗證的節點。

(以下引用摘自一篇轉載的文章,由於我沒有找到原作者,因此無法提供原作者信息和原文鏈接,如果有所侵權請私信或者評論告知,我將刪除以下引用內容。)

GPG是加密軟體,可以使用GPG產生的公鑰在網路上安全的傳播你的檔案、程式碼。
為什麼說安全的?以Google所開發的repo為例,repo即採用GPG驗證的方式,每個里程碑tag都帶有GPG加密驗證,假如在里程碑v1.12.3處你想要做修改,修改完後將這個tag刪除,然後又創建同名tag指向你的修改點,這必然是可以的。但是,當你再次clone你修改後的專案時,你會發現,你對此里程碑tag的改變不被認可,驗證失敗,導致你的修改在這裡無法正常實現。這就是GPG驗證的作用,這樣就能夠保證專案作者(私鑰持有者)所製定的里程碑其他人將無法修改。那麼,就可以說,作者的程式碼是安全傳播的。
為什麼會有這種需求?一個專案從開發到發布,再到後期的更新迭代,一定會存在若干的穩定版本與開發版本(存在不穩定因素)。身為專案發起者、持有者,有權定義他(們)所認可的穩定版本,這個穩定版本,將不允許其他開發者進行變更。也以Google的repo專案為例,專案擁有者定義專案開發過程中的點A為穩定版v1.12.3,那麼使用者在下載v1.12.3版本後,所使用的肯定是A點所產生的專案、產品,就算其他開發者能夠在本地對v1.12.3進行重新指定,指定到他們修改後的B點,但是最終修改後的版本給用戶用的時候,會出現GPG簽名驗證不通過的問題,也就是說這樣的修改是不生效的。

2.11 —summary,--no-summary

--stat --no-stat相似,並將在未來版本移除。

2.12 -q--quiet

#靜默操作,不顯示合併進度資訊。

2.13 -v--verbose

#顯示詳細的合併結果資訊。

2.14 --progress--no-progress

切換是否顯示合併的進度資訊。如果二者都沒有指定,那麼在標準錯誤發生時,將在連接的終端機顯示資訊。請注意,並不是所有的合併策略都支援進度報告。

2.15-S[<keyid>]</keyid>--gpg-sign[=<keyid>]</keyid>

GPG簽章。

2.16-m <msg></msg>

#設定用於建立合併節點時的提交資訊。
如果指定了--log參數,那麼commit節點的短日誌將會附加在提交資訊裡。

2.17--[no-]rerere-autoupdate

#rerere即reuse recorded resolution,重複使用已記錄的解決方案。它允許你讓 Git 記住解決一個區塊衝突的方法,這樣在下一次看到相同衝突時,Git 可以為你自動解決它。

2.18--abort

拋棄目前合併衝突的處理過程並嘗試重建合併前的狀態。

3.關於合併的其他概念

3.1合併前的檢測

#在合併外部分支時,你應保持自己分支的整潔,否則的話當存在合併衝突時將會帶來很多麻煩。
為了避免在合併提交時記錄不相關的文件,如果有任何在index所指向的HEAD節點中登記的未提交文件,git-pull和git-merge指令將會停止。

3.2fast-forward合併

通常情況下分支合併都會產生一個合併節點,但在某些特殊情況下例外。例如呼叫git pull指令更新遠端程式碼時,如果本地的分支沒有任何的提交,那麼就沒有必要產生一個合併節點。這種情況下將不會產生一個合併節點,HEAD直接指向更新後的頂端程式碼,而這種合併的策略就是fast-forward合併。

3.3合併細節

除了上文所提到的fast-forward合併模式以外,被合併的分支將會透過一個合併節點和目前分支綁在一起,該合併節點同時擁有合併前的目前分支頂部節點和對方分支頂部節點,共同作為父節點。
一個合併了的版本將會使所有相關分支的變化一致,包括提交節點,HEAD節點和index指標以及節點樹都會被更新。只要這些節點中的檔案沒有重疊的地方,那麼這些檔案的變化都會在節點樹中改變並更新儲存。
如果無法明顯地合併這些變化,將會發生以下的情況:

  1. HEAD指標所指向的節點保持不變
  2. MERGE_HEAD指標被置於其他分支的頂部
  3. 已經合併乾淨的路徑在index文件和節點樹中同時更新
  4. 對於衝突路徑,index檔案記錄了三個版本:版本1記錄了二者共同的祖先節點,版本2記錄了目前分支的頂部,即HEAD,版本3記錄了MERGE_HEAD。節點樹中的檔案包含了合併程式運行後的結果。例如三路合併演算法會產生衝突。
  5. 其他方面沒有任何變化。特別地,你之前進行的本地修改將繼續保持原樣。
    如果你嘗試了一個導致非常複雜衝突的合併,並想重新開始,那麼可以使用git merge --abort

關於三路合併演算法:
三路合併演算法是用來解決衝突的一種方式,當產生衝突時,三路合併演算法會取得三個節點:本地衝突的B節點,對方分支的C節點,B,C節點的共同最近祖先節點A。三路合併演算法會根據這三個節點進行合併。具體過程是,B,C節點和A節點進行比較,如果B,C節點的某個檔案和A節點中的相同,那麼不產生衝突;如果B或C只有一個和A節點相比發生變化,那麼該文件將會採用該變化了的版本;如果B和C和A相比都發生了變化,且變化不相同,那麼則需要手動去合併;如果B,C都發生了變化,且變化相同,那麼並不產生衝突,會自動採用該變化的版本。最後合併後會產生D節點,D節點有兩個父節點,分別為B和C。

3.4合併tag

當合併一個tag時,Git總是會建立一個合併的提交,即使這時能夠使用fast-forward模式。此提交資訊的範本預設為此tag的資訊。額外地,如果該tag被簽名,那麼簽名的檢測資訊將會附加在提交資訊範本中。

3.5衝突是如何表示的

當產生合併衝突時,該部分會以, <code>=======表示。在=======之前的部分是目前分支這邊的情況,在=======之後的部分是對方分支的情況。

3.6如何解決衝突

在看到衝突以後,你可以選擇以下兩種方式:

    ##決定不合併。這時,唯一要做的就是重置index到HEAD節點。
  • git merge --abort用於這種情況。
  • 解決衝突。 Git會標記衝突的地方,解決完衝突的地方後使用
  • git add加入到index中,然後使用git commit產生合併節點。 你可以用以下工具來解決衝突:
  • 使用合併工具。
  • git mergetool將會呼叫一個視覺化的合併工具來處理衝突合併。
  • 查看差異。
  • git diff將會顯示三路差異(三路合併中所採用的三路比較演算法)。
  • 查看每個分支的差異。
  • git log --merge -p 將會顯示HEAD版本和MERGE_HEAD版本的差異。
  • 查看合併前的版本。
  • git show :1:檔名顯示共同祖先的版本,git show :2:檔名顯示目前分支的HEAD版本,git show :3:檔名顯示對方分支的MERGE_HEAD版本。
4.合併策略

Git可以透過新增-s參數來指定合併的策略。有些合併策略甚至含有自己的參數選項,透過

-X

4.1resolve

只使用三路合併演算法合併兩個分支的頂部節點(例如當前分支和你拉取下的另一個分支)。這種合併策略遵循三路合併演算法,由兩個分支的HEAD節點以及共同子節點進行三路合併。

當然,真正會困擾我們的其實是交叉合併(criss-cross merge)這種情況。所謂的交叉合併,是指共同祖先節點有多個的情況,例如在兩個分支合併時,很有可能出現共同祖先節點有兩個的情況發生,這時候無法按照三路合併演算法進行合併(因為共同祖先節點不唯一)。 resolve策略在解決交叉合併問題時是這樣處理的,這裡參考《Version Control with Git》:

In criss-cross merge situations, where there is more than one possible merge basis, the resolve strategy works like this: pick one of the possible merge bases, and hope for the best. This is 能as it sounds. It often turns out that the users have been working on different parts of the code. In that case, Git detects that it's remerging some reads that are alavy in place and ps. these are slight changes that do cause conflict, at least the conflict should be easy for the developer to handle

##這裡簡單翻譯一下:在交叉合併的情況時有一個以上的合併基準點(共同祖先節點),resolve策略是這樣運作的:選擇其中一個可能的合併基準點並期望這是合併最好的結果。實際上這並沒有聽起來的那麼糟。通常情況下使用者會修改不同部分的程式碼,在這種情況下,很多的合併衝突其實是多餘和重複的。而使用resolve進行合併時,產生的衝突也較容易處理,真正會遺失程式碼的情況很少。

4.2recursive

只使用三路合併演算法合併兩個分支。和resolve不同的是,在交叉合併的情況時,這種合併方式是遞歸調用的,從共同祖先節點之後兩個分支的不同節點開始遞歸調用三路合併演算法進行合併,如果產生衝突,那麼該文件不再繼續合併,直接拋出衝突;其他未產生衝突的檔案將一直執行到頂部節點。額外地,這種方式也能夠偵測並處理涉及修改檔案名稱的操作。這是git合併和拉取程式碼的預設合併操作。

recursive合併策略有以下參數:

4.2.1 ours

該參數將強迫衝突發生時,自動使用目前分支的版本。這種合併方式不會產生任何困擾情況,甚至連git都不會去檢查其他分支版本所包含的衝突內容這種方式會拋棄對方分支任何衝突內容。

4.2.2 theirs

剛好和ours相反。

theirs和ours參數都適用於合併二進位檔案衝突的情況。

4.2.2 patience

在這個參數下,

git merge-recursive花費一些額外的時間來避免錯過合併一些不重要的行(如函數的括號)。如果目前分支和對方分支的版本分支分離非常大時,建議採用這種合併方式。

4.2.3

diff-algorithm=[patience|minimal|histogram|myers]

告知

git merge-recursive使用不同的比較演算法。

4.2.4

ignore-space-change, ignore-all-space, ignore-space-at-eol

根據指定的參數來對待空格衝突。

    如果對方的版本只是添加了空格的變化,那麼衝突合併時採用我們自己的版本
  • 如果我們的版本含有空格,但是對方的版本包含大量的變化,那麼衝突合併時採用對方的版本
  • 採用正常的處理流程
4.2.5

no-renames

關閉重新命名檢測。

4.2.6

subtree[=]

#這個選項是subtree合併策略的高階形式,將會猜測兩顆節點樹在合併的過程中如何移動。不同的是,指定的路徑將在合併開始時除去,以使得其他路徑能夠在尋找子樹的時候進行匹配。 (關於subtree合併策略詳見下文)

4.3octopus

這種合併方式用於兩個以上的分支,但在遇到衝突需要手動合併時會拒絕合併。這種合併方式更適合將多個分支捆綁在一起的情況,也是多分支合併的預設合併策略。

4.4ours

這種方式可以合併任意數量的分支,但是節點樹的合併結果總是目前分支所衝突的部分。這種方式能夠在取代舊版本時具有很高的效率。請注意,這種方式和recursive策略下的ours參數是不同的。

4.5subtree

subtree是修改版的recursive策略。當合併樹A和樹B時,如果B是A的子樹,B先調整至符合A的樹結構,而不是讀取相同的節點。

4.5總結

在使用三路合併的策略時(指預設的recursive策略),如果一個檔案(或一行程式碼)在目前分支和對方分支都產生變化,但稍後又在其中一個分支回退,

那麼這種回退的變化將會在結果中體現。這一點可能會使一些人感到困惑。這是由於在合併的過程中,git只關注共同祖先節點以及兩個分支的HEAD節點,而不是兩個分支的所有節點。因此,合併演算法將會把被回退的部分認為成沒有變化,這樣,合併後的結果就會變成另一個分支中變化的部分。

推薦學習:《

Git教學

以上是git中什麼是merge的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn