如何執行復雜的Git合併任務

如何執行復雜的Git合併任務

在開發專案中,Git是一個天賜良機。然而,如果舞池裡有很多舞者,總有一兩個會互相踩到對方的腳趾。對於您的專案來說,這意味著兩個開發者可能會在同一套程式碼上工作,並且都有可能提交。在這種情況下,您需要採取一些 Git 合併策略來解決衝突。

雖然 Git 合併可以很簡單,但很多時候您還需要更高階的方法。比如遞迴合併、三方合併等等。您甚至可能需要在某些時候撤銷 Git 合併。

本教程將討論一些複雜的 Git 合併技術。事實上,我們可以直接進入正題!

  1. Git 合併策略介紹
  2. 如何處理更復雜的 Git 合併衝突
  3. 如何撤銷 Git 合併
  4. Git 合併與重定向的區別
  5. 幫助您更好地管理 Git 合併的工具

Git 合併策略介紹

合併的核心概念很簡單:把兩個分支合併起來,把多個提交變成一個提交。然而,為了確保提交和合並的程式碼是正確的,您可以使用一些技巧。

下面我們將介紹一些您需要了解的重要策略。它們並不按順序排列,在你的開發生涯中,你可能會用到它們。此外,您還需要對指標、分支和提交等Git基本概念有紮實的理解。

雙向合併和三向合併的區別

瞭解雙向合併和三向合併的區別很有幫助。我們接下來介紹的大多數合併策略都是針對三向合併的。事實上,三向合併更簡單明瞭。請看下面這個例子:

  • 您有一個有若干提交的分支和一個也有提交的特性分支。
  • 然而,如果分支現在進一步提交,兩個分支就會出現分歧。
  • 通俗地說,分支和特性分支都有提交,而另一個沒有。如果用雙向方法合併,就會丟失一個提交(很可能是主分支上的。)
  • 相反,Git 會從當前的分支和特性分支中建立一個新的合併提交。

一言以蔽之,Git 會檢視三個不同的快照來合併變更:分支的頭,特性分支的頭,以及共同祖先。這將是分支和特性分支共同的最終提交。

在實踐中,你不需要擔心某個合併策略是雙向的還是三向的。很多時候,你必須使用某種策略。無論如何,瞭解 Git 在合併分支和版本庫時是如何 “思考” 的,是很有幫助的。

快速合併

第一個策略可能不需要執行任何操作。快進合併會將指標移到分支的最新提交上,而不會產生額外的提交(這可能會造成混亂)。這是一種簡潔的方法,許多開發者會將其作為標準。

該技術從一個可能有提交也可能沒有提交的主分支開始。在這種情況下,您開啟一個新的分支,編寫程式碼並提交。此時,您還需要將這些變更合併回分支。快進合併有一個要求:

  • 您需要確保在新分支上工作時,分支上沒有其他提交。

這並不總是可能的,尤其是當你在一個大團隊中工作時。即便如此,如果您選擇將您的提交與當前的、沒有自己提交的主分支合併,這將執行一次快進合併。有幾種不同的方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge <branch>
git merge --ff-only
git merge <branch> git merge --ff-only
git merge <branch>
git merge --ff-only

很多時候,你不需要指定要執行快進合併。這種型別的合併發生在單人專案或小團隊專案中。在快節奏的環境中,這種合併很少見。因此,其他型別的合併更為常見。

遞迴合併

遞迴合併通常是預設的,因為它比其他型別的合併更常見。遞迴合併是指在一個分支上進行提交,但在分支上也進行提交。

當需要合併時,Git 會對分支進行遞迴,以完成最終提交。這意味著,一旦完成合並,就會有兩個父提交。

和快進合併一樣,通常不需要指定遞迴合併。不過,你可以用下面的命令和標誌確保 Git 不會選擇類似快進合併的方式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge --no-ff
git merge -s recursive <branch1> <branch2>
git merge --no-ff git merge -s recursive <branch1> <branch2>
git merge --no-ff
git merge -s recursive <branch1> <branch2>

第二行使用 -s 策略選項和顯式命名來執行合併。與快進合併不同,遞迴合併會建立一個專門的合併提交。對於雙向合併,遞迴策略是可靠的,並且執行良好。

我們的和他們的

開發過程中常見的一種情況是,你在專案中建立了一個新特性,但最終沒有獲得綠燈。在很多情況下,你會有很多程式碼需要合併,而這些程式碼又是相互依賴的。我們的合併是解決這些衝突的最佳方式。

這種型別的合併可以根據需要處理多個分支,並忽略其他分支上的所有變更。如果你想清除舊特性或不需要的開發,它是個不錯的選擇。下面是你需要的命令:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge -s ours <branch1> <branch2>
git merge -s ours <branch1> <branch2>
git merge -s ours <branch1> <branch2>

我們的合併本質上意味著當前分支包含了法律上的程式碼。這與 “他們的” 合併有關,後者將其他分支視為正確的。不過,您需要在這裡傳遞另一個策略選項:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge -X theirs <branch2>
git merge -X theirs <branch2>
git merge -X theirs <branch2>

使用 “我們的” 和 “他們的” 合併可能會引起混淆,但一般來說,堅持典型的使用情況(即保留當前分支中的所有內容並丟棄其餘部分)是安全的。

Octopus

處理多頭合併–即把多個分支合併到另一個分支–對於 git 合併來說是個棘手的問題。可以說您需要兩隻以上的手來解決衝突。這對於octopus合併來說再合適不過了。

octopus合併與我們和他們的合併截然相反。典型的用例是你想把多個類似功能的提交合併成一個。下面是如何傳遞的:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge -s octopus <branch1> <branch2>
git merge -s octopus <branch1> <branch2>
git merge -s octopus <branch1> <branch2>

但如果需要手動解決,Git會拒絕octopus合併。對於自動決議,如果需要將多個分支合併為一個,則預設使用octopus合併。

解決

決議合併是最安全的合併提交方式之一,如果遇到交叉合併的情況,決議合併是個不錯的選擇。這也是一種快速的解決方法。您可能還想用它來處理更復雜的合併歷史–但僅限於雙頭合併。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge -s resolve <branch1> <branch2>
git merge -s resolve <branch1> <branch2>
git merge -s resolve <branch1> <branch2>

由於解析合併使用一種三向演算法,同時處理您當前的分支和您從其中提取的分支,因此它可能不如其他合併方法靈活。不過,就您需要它完成的工作而言,解析合併近乎完美。

子樹

遞迴合併的同伴可能會讓你感到困惑。我們試著用一個清晰的例子來解釋:

  • 首先,考慮兩個不同的樹–X和Y。
  • 你想把這兩個樹合併成一個。
  • 如果Y樹與X中的一個子樹相對應,那麼Y樹就會被修改以匹配X的結構。

這意味著,如果您想將多個版本合併為一篇明確的文章,子樹合併就非常有用。它還會對兩個分支的共同 “ancestor” 樹進行必要的修改。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge -s subtree <branch1> <branch2>
git merge -s subtree <branch1> <branch2>
git merge -s subtree <branch1> <branch2>

簡而言之,如果您需要合併兩個版本庫,子樹合併就是您想要的。事實上,你可能很難理解哪種合併策略適合你。稍後,我們將討論一些可以提供幫助的工具。

在此之前,你必須知道如何解決一些高階的合併衝突。

如何處理更復雜的 Git 合併衝突

在 Git 中合併分支更像是管理和解決衝突。團隊和專案規模越大,發生衝突的機率就越大。有些衝突可能很複雜,很難解決。

考慮到衝突會侵蝕時間、金錢和資源,您需要想辦法把它們快速消滅在萌芽狀態。在大多數情況下,兩個開發人員會在同一套程式碼上工作,並且兩人都會決定提交。

這可能意味著你可能會因為待處理的變更而根本無法開始合併,或者在合併過程中出現故障,需要人工干預。一旦工作目錄 “乾淨” 了,就可以開始了。很多時候,Git 會在你開始合併後通知你有衝突:

終端視窗顯示Git中的合併衝突

終端視窗顯示Git中的合併衝突。

不過,您可以執行 git status 檢視詳細資訊:

顯示 git status 命令結果的終端視窗

顯示 git status 命令結果的終端視窗。

從這裡,您可以開始處理導致衝突的各種檔案。我們接下來討論的一些工具和技術會有所幫助。

中止和重置合併

有時,您需要完全停止合併,從一片淨土開始。事實上,我們提到的兩個命令都適用於你還不知道如何處理衝突的情況。

你可以用下面的命令中止或重置正在進行的合併:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge --abort
git reset
git merge --abort git reset
git merge --abort
git reset

這兩個命令類似,但在不同的情況下使用。例如,中止合併會簡單地將分支恢復到合併前的狀態。在某些情況下,這並不起作用。例如,如果你的工作目錄中包含了未提交和未合併的修改,你就無法中止合併。

然而,重置合併意味著將檔案恢復到已知的 “良好” 狀態。如果 Git 啟動合併失敗,可以考慮後者。需要注意的是,這條命令會刪除任何你沒有提交的改動,這意味著這是一個需要小心謹慎的行為。

檢查衝突

大多數合併衝突都可以直接確定和解決。然而,在某些情況下,你可能需要深入挖掘,以找出衝突發生的原因,以及如何開始修復它。

您可以在 git merge 後使用 checkout 獲取更多資訊:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git checkout --conflict=diff3 <filename>
git checkout --conflict=diff3 <filename>
git checkout --conflict=diff3 <filename>

這將使用檢出所提供的典型導航,並建立兩個檔案之間的比較,以顯示合併衝突:

檢查特定專案檔案中的衝突

檢查特定專案檔案中的衝突。

從技術意義上講,這將再次檢查檔案並替換衝突標記。在整個解決過程中,您可能會多次這樣做。在這裡,如果您通過 diff3 引數,它將給出基本版本和 “我們的” 和 “他們的” 版本的替代版本。

注意預設的引數選項是 merge,除非你改變了合併衝突的樣式,否則不必指定。

忽略Negative Space

負空格(Negative space)及其用法是一個常見的討論點。一些程式語言會使用不同型別的間距,甚至個別開發人員也會使用不同的格式。

空格與製表符是我們不會加入的戰場。不過,如果您遇到格式根據檔案和編碼實踐而變化的情況,您可能會遇到這個 Git 合併問題。

您會發現合併失敗的原因,因為當您檢視衝突時,會發現有一些行被移除或新增:

在編輯器中顯示衝突差異的檔案

在編輯器中顯示衝突差異的檔案。

這是因為 Git 會檢視這些行,並將負空格視為修改。

不過,您可以在 git merge 命令中新增特定引數,這樣就可以忽略相關檔案中的負空格:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git merge -Xignore-all-space
git merge -Xignore-space-change
git merge -Xignore-all-space git merge -Xignore-space-change
git merge -Xignore-all-space
git merge -Xignore-space-change

雖然這兩個引數看似相似,但它們有一個獨特的區別。如果您選擇忽略所有負空格,Git 會這樣做。這是一種一刀切的做法,但相比之下, -Xignore-space-change 只將一個或多個負空格字元的序列視為等價字元。因此,它會忽略行尾的單空格。

為了更安全起見,你也可以使用 --no-commit 命令檢視合併結果,以檢查你是否以正確的方式忽略和計算了負空格。

合併日誌

日誌對於幾乎所有傳遞資料的軟體都至關重要。對於 Git 來說,您可以使用日誌來確定合併衝突的更多細節。您可以使用 git log 訪問這些資訊:

在終端執行並檢視 Git 日誌

在終端執行並檢視 Git 日誌。

它本質上是一個文字檔案轉儲站,記錄 repo 中的每個操作。不過,您可以新增更多引數來細化檢視,只檢視您希望看到的提交:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git log --oneline --left-right <branch1>...<branch2>
git log --oneline --left-right <branch1>...<branch2>
git log --oneline --left-right <branch1>...<branch2>

它使用 “Triple Dot” 來提供合併過程中兩個分支所涉及的提交列表。它將過濾兩個分支共享的所有提交,留下一部分提交供進一步調查。

您也可以使用 git log --oneline --left-right --merge 來只顯示合併時兩邊 “觸及 “衝突檔案的提交。 -p 選項將顯示特定 “diff” 的確切改動,但注意這隻針對非合併提交。有一個解決方法,我們接下來介紹。

使用組合差分格式調查 Git 合併衝突

您可以利用 git log 的檢視進一步調查合併衝突。在通常情況下,Git 會合並程式碼,並將合併成功的程式碼分期。這將只留下有衝突的行,您可以使用 git diff 命令檢視它們:

在終端執行 git diff 命令

在終端執行 git diff 命令。

這種 “合併差異” 格式增加了兩列額外的資訊。第一列告訴您某行在您的分支(”我們的”)和工作拷貝之間是否不同;第二列給您 “他們的” 分支的相同資訊。

對於符號,加號表示該行是否是工作副本中的新增行,但不在合併的那一側;減號表示該行是否被移除。

在 Git 日誌中也可以看到這種組合的差異格式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git show
git log --cc -p
git show git log --cc -p
git show
git log --cc -p

第一個命令用於檢視合併提交的歷史。第二個命令使用 -p 的功能來顯示對非合併提交的修改,同時顯示合併的 diff 格式。

如何撤銷 Git 合併

錯誤可能會發生,你可能會進行一些需要收回的合併。在某些情況下,您可以使用 git commit --amend 簡單地修改最近的提交。這會開啟編輯器,讓您修改最近的提交資訊。

雖然您可以逆轉更復雜的合併衝突和由此產生的改動,但這可能很困難,因為提交通常是永久性的。

因此,你需要遵循很多步驟:

  • 首先,您需要檢視提交,找到您需要的合併的引用。
  • 其次,檢查分支,檢視提交歷史。
  • 一旦瞭解了所需的分支和提交,就可以根據需要執行特定的Git命令了。

讓我們詳細瞭解一下這些命令,從審查流程開始。接下來,我們將向您展示如何快速撤銷 Git 合併,然後針對更高階的用例介紹具體的命令。

稽覈提交

如果想檢視當前分支的修訂ID和提交資訊, git log --oneline 命令是個不錯的選擇:

在終端執行一行 git diff 命令

在終端執行一行 git diff 命令。

 git log --branches=* 命令會顯示同樣的資訊,但針對所有分支。無論如何,您可以在使用 git checkout 的同時使用引用 ID 來建立一個 “分離的 HEAD “狀態。這意味著從技術角度看,您不會在任何分支上工作,而一旦您切換回已建立的分支,您就成了修改的 “孤兒”。

因此,您幾乎可以把簽出當作一個無風險的沙箱來使用。不過,如果您想保留修改,可以簽出該分支,然後用 git checkout -b <branch-name> 給它起個新名字。這是撤銷 Git 合併的可靠方法,但對於高階用例,還有更細緻的方法。

使用 git 重置

許多合併衝突可能發生在本地倉庫。在這種情況下,您需要使用 git reset 命令。不過,這個命令有更多的引數。下面是該命令的實際使用方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git reset --hard <reference>
git reset --hard <reference>
git reset --hard <reference>

第一部分 – git reset --hard – 經過三個步驟:

  • 將參考分支移動到合併提交前的位置。
  • 硬重置使 “索引”(即下一個提交快照)看起來像參考分支。
  • 使工作目錄看起來像索引。

一旦你呼叫了這個命令,提交歷史就會刪除後來的提交,並重置歷史到引用的ID。這是撤銷 Git 合併的簡潔方法,但並非適用於所有情況。

例如,如果你試圖將本地重置的提交推送到包含該提交的遠端倉庫,就會導致錯誤。在這種情況下,您可以使用另一種命令。

使用 git revert

雖然 git resetgit revert 看起來很相似,但還是有一些重要的區別。在目前的例子中,撤銷過程涉及移動引用指標和 HEAD 到特定的提交。這類似於洗牌來建立一個新的順序。

相比之下, git revert 會根據回溯的改動建立一個新的提交,然後更新參考指標,使該分支成為新的 “頂端”。這也是為什麼您應該用這條命令來處理遠端倉庫合併衝突的原因。

您可以用 git revert <reference> 來撤銷 Git 合併。注意,您需要指定一個提交引用,否則命令無法執行。你也可以給命令傳遞 HEAD 來恢復到最新的提交。

不過,你可以讓 Git 更清楚地知道你想做什麼:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git revert -m 1 <reference>
git revert -m 1 <reference>
git revert -m 1 <reference>

當您呼叫合併時,新提交將有兩個 “父分支”。一個是你指定的引用,另一個是你要合併的分支的頂端。在這種情況下, -m 1 會告訴 Git 保留第一個父分支,即指定的引用,作為 “主線”。

git revert 的預設選項是 -e--edit 。這會開啟編輯器,以便在還原之前修改提交資訊。不過,您也可以通過 --no-edit ,它不會開啟編輯器。

也可以通過 -n--no-commit 。這會告訴 git revert 不建立新的提交,而是 “反向” 修改並將其新增到暫存索引和工作目錄中。

Git 合併與重定向的區別

除了使用 git merge 命令,您還可以使用 git rebase 。這也是一種將改動整合到一個目錄中的方法,但有所不同:

  • 當您使用 git merge 時,預設情況下是三路合併。它結合了兩個當前分支的快照,然後與兩個分支的共同祖先合併,建立一個新的提交。
  • 而重置則是將不同分支中的補丁應用到另一個分支中,而不需要祖先分支。這意味著不會有新的提交。要使用這個命令,先簽出到你想重定向的分支。

在那裡,你可以使用下面的命令:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git rebase -i <reference>
git rebase -i <reference>
git rebase -i <reference>

在很多情況下,您的參考分支就是您的主分支。 -i 選項啟動 “互動式重定向”。這使您有機會在提交移動時修改它們。您可以用它來清理提交歷史,這也是使用 git rebase 的一大好處。

執行該命令將在編輯器中顯示潛在的提交列表。這樣您就可以完全改變提交歷史的外觀。如果將 pick 命令改為 fixup ,您還可以合併提交。一旦您儲存了修改,Git 就會執行重置(rebase)。

總的來說,Git 合併可以解決很多衝突。不過,重置也有很多好處。例如,合併簡單易用,可以保留合併歷史的上下文,而重置則可以簡化提交歷史。

儘管如此,在重定向時你必須更加小心,因為出錯的可能性很大。此外,你不應該在公共分支上使用這種技術,因為rebasing只會影響你的repo。要修復由此產生的問題,您需要進行更多合併,並會看到多次提交。

幫助您更好地管理 Git 合併的工具

鑑於 Git 合併衝突的複雜性,你可能需要幫手。有很多工具可以幫助您成功合併,如果您使用的是Intellij IDEA,您可以使用 “Branches” 選單中的內建方法:

在Intellij IDEA中檢視分支

在Intellij IDEA中檢視分支。

VSCode在其使用者介面(UI)中也包含了類似的功能。Atom的老使用者會發現微軟在這裡繼承了其出色的Git整合,無需其他擴充套件或外掛即可連線GitHub。

您還可以通過命令面板(Command Palette)獲得更多選項。這甚至適用於基於VSCode開源框架的編輯器,如Onivim2

使用 "合併分支 "命令

在Onivim2的命令面板中使用 “合併分支 “命令。

和本列表中的所有工具一樣,這個命令的好處是你不需要命令列來執行合併。您通常需要從下拉選單中選擇源分支和目標分支,然後讓編輯器執行合併。即便如此,您也不必放手不管。你可以在合併後檢視修改,然後進行你需要的提交。

Sublime Text是一款為Git工作提供獨立圖形使用者介面(GUI)的編輯器。如果你使用的是Sublime Text,那麼Sublime Merge將是你工作流程的理想補充:

Sublime Merge應用程式

Sublime Merge應用程式。

無論你選擇哪種程式碼編輯器,通常都能在不使用命令列的情況下使用Git。Vim和Neovim甚至可以使用Tim Pope的Git Fugitive外掛。

不過,也有一些專門的第三方合併工具專注於這項任務。

專用的Git合併

例如,Mergify是一款企業級的程式碼合併工具,可以整合到持續整合/持續交付(CI/CD)管道和工作流中:

Mergify 網站

Mergify 網站。

這裡的一些功能可以幫助您在合併前自動更新您的拉取請求,根據優先順序重新排序,以及批處理它們。對於開源解決方案,Meld可能是有價值的:

Meld應用程式介面

Meld應用程式介面。

其穩定版本支援Windows和Linux,並在GPL許可下執行。它為您提供了比較分支、編輯合併等基本功能。您甚至可以進行雙向或三向比較,並支援Subversion等其他版本控制系統。

小結

Git是高效協作和管理程式碼變更的重要工具。然而,如果多個開發者在同一程式碼上工作,可能會產生衝突。Git 合併策略可以幫助您解決這些衝突,而且有很多方法。對於更復雜的 Git 合併策略,您需要使用高階策略。

這甚至可以簡單到忽略負空格或搜尋日誌。不過,您也不必總是使用命令列。有很多應用程式可以幫助你,你的程式碼編輯器通常也會使用內建介面。

評論留言