簡體   English   中英

Git - 即使沒有沖突,如何強制手動合並

[英]Git - how to force manual merge even if there is no conflict

這是多年來多次被問過的問題。 我找到了很多答案,尤其是這個答案:

Git - 如何在選定文件上強制合並沖突和手動合並 (@Dan Molding)

此頁面包含有關如何設置合並驅動程序的詳細指南,該合並驅動程序始終會返回失敗,從而可以進行手動合並。 我試圖適應Windows的解決方案:

  1. 我將以下內容添加到我的%homepath%\\.gitconfig

    [merge "verify"] name = merge and verify driver driver = %homepath%\\\\merge-and-verify-driver.bat %A %O %B

  2. 我改變了驅動程序

    cmd /K "echo Working > merge.log & git merge-file %1% %2% %3% & exit 1"

    (添加echo Working > merge.log以檢查是否調用了驅動程序)。

  3. 並且,在repo的根目錄下,使用以下行創建了一個.gitattributes文件:

    *.txt merge=verify

不幸的是,它不起作用。 我試圖合並一個文件, feature.txt ,唉,合並成功完成。 似乎根本沒有調用驅動程序,因為未創建merge.log文件。

我做錯了嗎? 任何強制手動合並問題的解決方案都是最受歡迎的。

這個問題有兩個部分。 相對容易的是編寫自定義合並驅動程序,正如您在步驟1和2中所做的那樣。困難的是,如果Git認為沒有必要,Git實際上並不打擾運行自定義驅動程序。 這是您在步驟3中觀察到的。

所以,當的Git運行合並驅動程序? 答案是相當復雜的,為了實現這一目標,我們必須定義術語合並基礎 ,我們將在稍后介紹。 您還需要知道Git識別文件 - 事實上,幾乎所有內容:提交,文件,補丁等等 - 它們的哈希ID 如果您已經知道所有這些,可以直接跳到最后一節。

哈希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

所述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中隱藏的另一個關鍵概念與大多數其他版本控制系統完全不同,我將順便提及。 這是實際的分支實際上由提交本身形成,並且分支名稱在這里幾乎沒有意義或貢獻。 這些名稱僅用於查找分支提示 :在這種情況下提交CD 分支本身就是我們通過繪制連接線獲得的,從較新的(子)提交回到較舊的(父)提交。

作為一個側面點,值得注意的是,這種奇怪的向后鏈接允許Git 永遠不會改變任何提交 請注意, CD都是B孩子,但是當我們制作B時,我們不一定知道我們要同時制作C D 但是,因為父母不“知道”它的孩子,Git根本不必將CD的ID存儲在B中。 它只是存儲的ID B哪位絕對沒有由當時的內部的每一個存在CD時,它創建的每個的CD

我們制作的這些圖紙顯示了提交圖 (的一部分)。

合並基地

合並基礎的正確定義太長了,無法進入這里,但現在我們已經繪制了圖形,非正式定義非常簡單,並且在視覺上很明顯。 當我們像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 sidebrmastergit 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

我們從兩個分支開始分別到EF開始,然后做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 GEF的合並,添加到br1 )然后立即生成HFE的合並,添加到br2 )。 我們可以繼續承諾兩個分支,但最終,當我們再次合並時,我們在選擇合並基礎時遇到問題,因為E F都是“最佳候選者”。

通常,即使這只是“正常工作”,但有時縱橫交錯合並會產生Git嘗試使用其默認的“遞歸”合並策略以奇特的方式處理的問題。 在這些(罕見的)情況下,您可以看到一些奇怪的合並沖突,特別是如果您設置merge.conflictstyle = diff3 (我通常建議:它顯示沖突合並中的合並基礎版本)。

你的合並驅動程序何時運行?

現在我們已經定義了合並基礎,並且看到哈希識別對象(包括文件)的方式,我們現在可以回答原始問題。

當你運行git merge branch-name ,Git:

  1. 標識當前提交,即HEAD 這也稱為本地--ours提交。
  2. 標識另一個提交,即通過branch-name提供的提交。 這是另一個分支的提示,並且被不同地稱為另一個, - --theirs ,或者有時遠程提交(“遠程”是一個非常糟糕的名稱,因為Git也將該術語用於其他目的)。
  3. 標識合並基礎。 我們稱這個提交為“基礎”。 字母B也很好,但有一個合並驅動程序, %A%B表示--ours和 - --theirs版本, %O指的是基數。
  4. 實際上,運行兩個單獨的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM