[英]Git - how to force manual merge even if there is no conflict
這是多年來多次被問過的問題。 我找到了很多答案,尤其是這個答案:
Git - 如何在選定文件上強制合並沖突和手動合並 (@Dan Molding)
此頁面包含有關如何設置合並驅動程序的詳細指南,該合並驅動程序始終會返回失敗,從而可以進行手動合並。 我試圖適應Windows的解決方案:
我將以下內容添加到我的%homepath%\\.gitconfig
:
[merge "verify"] name = merge and verify driver driver = %homepath%\\\\merge-and-verify-driver.bat %A %O %B
我改變了驅動程序
cmd /K "echo Working > merge.log & git merge-file %1% %2% %3% & exit 1"
(添加echo Working > merge.log
以檢查是否調用了驅動程序)。
並且,在repo的根目錄下,使用以下行創建了一個.gitattributes
文件:
*.txt merge=verify
不幸的是,它不起作用。 我試圖合並一個文件, feature.txt
,唉,合並成功完成。 似乎根本沒有調用驅動程序,因為未創建merge.log文件。
我做錯了嗎? 任何強制手動合並問題的解決方案都是最受歡迎的。
這個問題有兩個部分。 相對容易的是編寫自定義合並驅動程序,正如您在步驟1和2中所做的那樣。困難的是,如果Git認為沒有必要,Git實際上並不打擾運行自定義驅動程序。 這是您在步驟3中觀察到的。
所以,當它的Git運行合並驅動程序? 答案是相當復雜的,為了實現這一目標,我們必須定義術語合並基礎 ,我們將在稍后介紹。 您還需要知道Git識別文件 - 事實上,幾乎所有內容:提交,文件,補丁等等 - 它們的哈希ID 。 如果您已經知道所有這些,可以直接跳到最后一節。
哈希ID(或有時是對象ID或OID)是您在提交時看到的那些丑陋的名字:
$ git rev-parse HEAD
7f453578c70960158569e63d90374eee06104adc
$ git log
commit 7f453578c70960158569e63d90374eee06104adc
Author: ...
Git存儲的所有東西都有一個唯一的哈希ID,根據對象的內容(文件或提交或其他)計算。
如果將同一文件存儲兩次 (或更多次),則會獲得兩次(或更多)相同的哈希ID。 由於每次提交最終都會存儲截至提交時每個文件的快照,因此每個提交都有一個每個文件的副本,由其哈希ID列出。 你其實可以看到這些:
$ git ls-tree HEAD
100644 blob b22d69ec6378de44eacb9be8b61fdc59c4651453 README
100644 blob b92abd58c398714eb74cbe66671c7c3d5c030e2e integer.txt
100644 blob 27dfc5306fbd27883ca227f08f06ee037cdcb9e2 lorem.txt
中間的三個丑陋的ID是三個哈希ID。 這三個文件位於這些ID下的HEAD
提交中。 我在幾個提交中有相同的三個文件,通常內容略有不同。
所述DAG,或d irected 甲環狀ģ拍攝和,是繪制提交之間的關系的一種方式。 要真正正確地使用Git,您至少需要對DAG的含義有一個模糊的概念。 它也被稱為提交圖 ,它在某些方面是一個更好的術語,因為它避免了專門的信息學術語。
在Git中,當我們創建分支時,我們可以通過各種方式繪制它們。 我喜歡在這里使用的方法(在文本中,在StackOverflow上)是在左邊提交早期提交,在右邊提交稍后提交,並用單個大寫字母標記每個提交。 理想情況下,我們以Git保留它們的方式繪制它們,這是相反的:
A <- B <- C <-- master
在這里,我們只有三個提交,全部都在master
。 分支名稱 master
“指向”三個提交中的最后一個。 這就是Git實際上通過從分支名稱master
讀取其哈希ID來實際找到提交C
,實際上名稱master
實際上只存儲了這一個ID。
Git通過讀取提交C
找到提交B
Commit C
里面有commit B
的哈希ID。 我們說C
“指向” B
,因此向后指向箭頭。 同樣, B
“指向” A
由於A
是第一次提交,因此它沒有先前的提交,因此它沒有后向指針。
這些內部箭頭告訴Git每個提交的父提交。 大多數時候,我們並不關心它們都是倒退的,所以我們可以更簡單地將它繪制為:
A--B--C <-- master
這讓我們假裝C
顯然是在B
之后,盡管事實上在Git中很難。 (與聲稱“ B
來自C
之前”相比,這在Git中非常容易:它很容易向后移動,因為內部箭頭都是向后的。)
現在讓我們畫一個實際的分支。 假設我們創建一個新的分支,從提交B
開始,並進行第四次提交D
(我們不確定它們到底是什么時候 ,但最后它無關緊要):
A--B--C <-- master
\
D <-- sidebr
名稱sidebr
現在指向提交D
,而名稱master
指向提交C
這里的一個關鍵Git概念是提交B
在兩個分支上。 這是master
和 sidebr
。 對於提交A
也是如此。 在Git中,任何給定的提交都可以並且通常在許多分支上同時進行。
Git中隱藏的另一個關鍵概念與大多數其他版本控制系統完全不同,我將順便提及。 這是實際的分支實際上由提交本身形成,並且分支名稱在這里幾乎沒有意義或貢獻。 這些名稱僅用於查找分支提示 :在這種情況下提交C
和D
分支本身就是我們通過繪制連接線獲得的,從較新的(子)提交回到較舊的(父)提交。
作為一個側面點,值得注意的是,這種奇怪的向后鏈接允許Git 永遠不會改變任何提交 。 請注意, C
和D
都是B
孩子,但是當我們制作B
時,我們不一定知道我們要同時制作C
和 D
但是,因為父母不“知道”它的孩子,Git根本不必將C
和D
的ID存儲在B
中。 它只是存儲的ID B
哪位絕對沒有由當時的內部的每一個存在C
和D
時,它創建的每個的C
和D
。
我們制作的這些圖紙顯示了提交圖 (的一部分)。
合並基礎的正確定義太長了,無法進入這里,但現在我們已經繪制了圖形,非正式定義非常簡單,並且在視覺上很明顯。 當我們像Git那樣向后工作時,兩個分支的合並基礎是它們首先聚集在一起的點。 也就是說,這是兩個分支上的第一個這樣的提交。
因此,在:
A--B--C <-- master
\
D <-- sidebr
合並基礎是提交B
如果我們提交更多提交:
A--B--C--F <-- master
\
D--E--G <-- sidebr
合並基礎仍然是提交B
如果我們實際上成功合並,則新的合並提交有兩個父提交而不是一個:
A--B--C--F---H <-- master
\ /
D--E--G <-- sidebr
這里,提交H
是合並,我們通過運行git merge sidebr
在master
上git merge sidebr
,它的兩個父項是F
(以前是master
的提示)和G
(仍然是 sidebr
) 。
如果我們現在繼續提交,然后決定進行另一次合並, G
將成為新的合並基礎:
A--B--C--F---H--I <-- master
\ /
D--E--G--J <-- sidebr
H
有兩個父母,當我們向后看時,我們(和Git)“同時”跟隨父母。 因此,如果我們運行另一個合並,則提交G
是兩個分支上的第一個。
請注意,在這種情況下, F
不在sidebr
:我們必須在遇到它們時遵循父鏈接,因此J
返回到G
,它返回到E
等等,這樣我們就不會在開始時到達F
sidebr
但是,如果我們將下一個從 master
合並到 sidebr
:
A--B--C--F---H--I <-- master
\ / \
D--E--G--J---K <-- sidebr
現在提交F
在兩個分支上。 但事實上,提交I
也在兩個分支上,所以即使這使得合並雙向進行,我們也可以。 我們可以通過所謂的“縱橫交錯”來解決問題,我會畫出一個只是為了說明問題,但不是在這里進行說明:
A--B--C--E-G--I <-- br1
\ X
D---F-H--J <-- br2
我們從兩個分支開始分別到E
和F
開始,然后做git checkout br1; git merge br2; git checkout br2; git merge br1
git checkout br1; git merge br2; git checkout br2; git merge br1
git checkout br1; git merge br2; git checkout br2; git merge br1
make G
( E
和F
的合並,添加到br1
)然后立即生成H
( F
和E
的合並,添加到br2
)。 我們可以繼續承諾兩個分支,但最終,當我們再次合並時,我們在選擇合並基礎時遇到問題,因為E
和 F
都是“最佳候選者”。
通常,即使這只是“正常工作”,但有時縱橫交錯合並會產生Git嘗試使用其默認的“遞歸”合並策略以奇特的方式處理的問題。 在這些(罕見的)情況下,您可以看到一些奇怪的合並沖突,特別是如果您設置merge.conflictstyle = diff3
(我通常建議:它顯示沖突合並中的合並基礎版本)。
現在我們已經定義了合並基礎,並且看到哈希識別對象(包括文件)的方式,我們現在可以回答原始問題。
當你運行git merge branch-name
,Git:
HEAD
。 這也稱為本地或--ours
提交。 branch-name
提供的提交。 這是另一個分支的提示,並且被不同地稱為另一個, - --theirs
,或者有時遠程提交(“遠程”是一個非常糟糕的名稱,因為Git也將該術語用於其他目的)。 B
也很好,但有一個合並驅動程序, %A
和%B
表示--ours
和 - --theirs
版本, %O
指的是基數。 git diff
命令: git diff base ours
git diff base theirs
。 這兩個差異告訴Git“發生了什么”。 記住,Git的目標是結合兩組變化 :“我們在我們的工作中做了什么”和“他們在他們的工作中做了什么”。 這就是兩個git diffs
顯示的結果:“base vs our”就是我們所做的,而“base vs theirs”就是他們所做的。 (這也是Git如果發現任何文件被添加,刪除和/或重命名的方式,從基礎到我們和/或基礎到他們的 - 但這是一個不必要的復雜現在,我們將忽略。)
這是組合這些變化的實際機制,它們調用合並驅動程序,或者 - 在我們的問題情況下 - 不是。
請記住,Git的每個對象都由其哈希ID編目。 根據對象的內容,每個ID都是唯一的。 這意味着它可以立即判斷任何兩個文件是否100%相同:當且僅當它們具有相同的散列時,它們才完全相同。
這意味着如果在base-vs-ours或base-vs-theirs中,這兩個文件具有相同的哈希值,那么要么我們沒有做任何更改,要么他們沒有做任何更改。 如果我們沒有進行任何更改並且他們進行了更改,那么為什么顯然結合這些更改的結果就是他們的文件 。 或者,如果他們沒有做任何更改並且我們進行了更改,結果就是我們的文件。
同樣,如果我們和他們的哈希相同 ,那么我們都做了相同的更改。 在這種情況下,組合更改的結果是文件 - 它們是相同的,所以Git選擇哪一個甚至都不重要。
因此,對於所有這些情況,Git只選擇基本版本中具有不同散列(如果有)的新文件。 這是合並結果,沒有合並沖突,Git完成了合並該文件。 它永遠不會運行您的合並驅動程序,因為顯然沒有必要。
只有當所有三個文件都有三個不同的哈希值時,Git才能進行真正的三向合並。 這是它將運行您的自定義合並驅動程序,如果您已定義一個。
有一種解決方法,但它不適合膽小的人。 Git不僅提供自定義合並驅動程序 ,還提供自定義合並策略 。 有四種內置合並策略 ,都通過-s
選項選擇: -s ours
, -s recursive
, -s resolve
和-s octopus
。 但是,您可以使用-s custom-strategy
來調用自己-s custom-strategy
。
問題是要編寫合並策略, 你必須識別合並基礎,在模糊合並基礎的情況下做任何你想要的遞歸合並(la -s recursive
),運行兩個git diff
,弄清楚文件添加/刪除/重命名操作,然后運行您的各種驅動程序。 因為這會接管整個megillah ,你可以做任何你想做的事 - 但你必須做很多事情。 據我所知,沒有使用這種技術的固定解決方案。
tl; dr:我試着重復你描述的內容,似乎有效。 與您的版本相比有2個更改,但沒有它們我也合並失敗(因為驅動程序基本上無法運行)
我試過這個:
創建合並驅動程序$HOME/bin/errorout.bat
:
exit 1
為合並類型創建一個部分
[merge "errorout"]
name = errorout
driver = ~/bin/errorout.bat %A %O %B
創建.gitattributes文件:
*.txt merge=errorout
之后,報告錯誤,因為我認為您希望報告錯誤:
$ git merge a
C:\...>exit 1
Auto-merging f.txt
CONFLICT (content): Merge conflict in f.txt
Automatic merge failed; fix conflicts and then commit the result.
我有git版本2.11.0.rc1.windows.1。 我無法按照您指定的運行成功執行復雜命令,它報告了一些語法錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.