簡體   English   中英

為什么這個 GIT 合並不會導致沖突?

[英]Why does this GIT merge not result in conflicts?

我們今天在工作中發現了 GIT 的一個嚴重問題,我想知道這是一個錯誤還是設計使然,以及如何解決這個問題。

考慮以下事件序列:

  1. 在 Master 上創建分支“test1”
  2. 切換到大師:
    1. 編輯文件並將更改提交為提交“X”
  3. 切換到“test1”:
    1. 櫻桃選擇從大師提交“X”
    2. 恢復之前的提交
    3. 將 Master 合並到“test1”中

結果:即使文件已在兩個分支中進行編輯,也不會報告合並沖突,更糟糕的是,即使是最近的提交,步驟 3.2 中的還原也沒有保留。

這是一個巨大的問題,從以下最近的示例中可以看出:我的一位同事對不同的分支進行了類似的更改,注意到這些更改的一部分是惡意的,因此在其中一個分支上手動還原了其中的一部分. 合並分支后,他驚訝地發現他的恢復沒有通過合並。

我上傳了一個最小的例子到谷歌驅動器來演示這個問題。 您可以將 Master 合並到 test1 中,反之亦然,以自己查看。

https://drive.google.com/drive/folders/19a-QPwOQKsn9PywUPd2DRnvUOml03nZ-?usp=sharing

如果有任何問題,我將 TortoiseGIT 2.12.0.0 與 Git for Windows 2.32.0.2 一起使用。

你得到那個結果,因為那是正確的結果

好吧,讓我們修改該語句:這是 Git merge 規則的正確結果 (據我所知,根據大多數其他合並程序的規則,這也是正確的,但有些算法至少會將此標記為注意。Git 不使用這樣的算法。)

如果 Git 的合並結果不是你想要的結果,你有補救措施:見下文。

當 Git 進行合並時,Git 會注意三個快照:

  • 一個快照是當前快照,即,如果您運行git rev-parse HEAD您將獲得其哈希 ID 的提交。 如果HEAD附加到分支名稱(通常是這樣),那就是給定分支的提示提交。

  • 一個快照是您在命令行中命名的快照: git merge foo查找foo以獲取提交哈希 ID。

  • 第三個,在許多方面也是最重要的,快照是合並基礎 (Git 將這個編號為“#1”, HEAD / --ours為 #2,另一個 / --theirs為 #3,在內部,即使我們必須先定位其他兩個輸入才能定位此合並基礎輸入.) 合並庫的提交哈希 ID 通過提交圖定位。 在您的情況下,它是您正在調用 commit X 的提交之前的提交。

讓我們像這樣繪制這些提交,將更新的提交放在右側,單個大寫字母代表每個實際提交哈希 ID:

          X   <-- master
         /
...--G--H   <-- here's where both branches start diverging
         \
          X'-X"  <-- test1

在這里, commit X包含您在master所做的更改; 提交X'具有相同的更改,而X"撤消這些更改,以便X"中的快照與H中的快照完全匹配。

Git 的合並算法包括執行以下操作,假設您在master (因此提交X是當前/ HEAD提交)並且正在合並test1 (提交X" ):

  • 將提交H提取為“階段 1”。

  • 將提交X提取為“階段 2”。

  • 將提交X"提取為“階段 3”。(請注意,我們剛剛確定X"的內容與H的內容匹配。)

  • 對於索引/階段中的每個文件,都存在於所有三個階段插槽中:

    • 比較插槽 1 中的副本與插槽 2 和 3 中的副本。
    • 對於所有 3 個插槽中相同的任何文件,結果是該文件的任何版本(三個都匹配)。
    • 對於插槽 1 和插槽 2 或插槽 3 中相同的任何文件,結果是不相同的版本:獲取更改后的文件。
    • 對於所有三個插槽都不同的任何文件,請運行差異算法以查找各個更改,並將它們組合起來。 這一步可能有合並沖突,但如果沒有,結果就是正確的合並。
  • 對於不存在於所有三個插槽中的文件(例如,可能發生了重命名或復制的位置),事情變得更加復雜。 這些可能會導致高級別的沖突,這些沖突被視為沖突,但不會顯示為沖突的工作樹副本。 但是這種情況在這里並不適用,所以我們可以忽略它。

git merge已經完成,但現在有清理步驟:

  • 正確合並的文件被放到槽 0(如果/根據需要寫出到工作樹)。

  • 有合並沖突的文件留在所有三個插槽中; 工作樹在合並時得到 Git 的最大努力,包括沖突標記。

  • 在發生沖突的情況下,合並現在在中間停止; 用戶必須完成它。 每個文件的工作樹和索引副本都存在於此處供用戶使用。

  • 否則,除非被告知停止而不提交,否則git merge自行完成合並,通常是通過創建一個新的合並提交

如果此合並結果不是您想要的,您的補救措施包括但不限於以下內容:

  • 更改輸入(例如,通過向一個或兩個分支添加更多提交)。

  • 使用git merge -n以便git merge在提交合並結果之前停止。 使用索引和工作樹文件——現在都暫存提交,索引槽 #0 中每個文件只有一個版本——來產生你想要的結果。 然后,提交結果。 請注意,這稱為邪惡合並 它沒有什么問題,但是如果你讓 Git 重復合並——例如,使用花哨的新git rebase --rebase-merges代碼git rebase --rebase-merges不會知道讓合並成為一個邪惡的合並,所以明智的做法是用提交消息或其他東西清楚地標記這一點。

  • 進行合並,讓它成為“錯誤的”,提交它(並且可能標記它,特別是對於稍后的skip-during-bisect),然后添加一個提交來修復問題。

在您描述的情況下:這是git merge操作的預期結果——torek 的回答詳細說明了原因。

在 git 方面:

您提到的工作流程的一個問題是git cherry-pick沒有注冊原始提交的鏈接。 您必須知道,不知何故,管理cherry-pick有點像管理代碼中的“復制/粘貼”:如果在一側修復了錯誤,則應手動將其移植到另一側。

順便說一下, git rebase也是如此:如果您應用了一個 rebase 操作,該操作以某種方式保留了原始分支,並且還創建了一個帶有一些復制提交的新分支,那么這也是復制/粘貼提交。

一個更通用的原則是:

git merge沒有沖突的情況下成功”並不意味着“生成的代碼沒有錯誤”。

您應該始終驗證生成的代碼是否符合您的期望:

  • 應審查結果代碼,
  • 您應該使用其他步驟驗證您的代碼,例如編譯/構建腳本、單元測試和 QA,
  • 在您的示例中:如果該問題足夠嚴重,您可以例如添加一個 linter(或任何檢查代碼的腳本)來檢查被控代碼不存在

對於您的發布分支尤其如此; 有時,例如您描述的那個(開發人員合並到一個分支),這可能更多是關於您的團隊意識到潛在的陷阱——並檢查差異。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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