成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Git 的遴選和撤銷操作是如何利用三路合并的

開發 Linux
盡管本文的內容有些深入,但你并不需要全部理解才能有效地使用 Git。不過,如果你(和我一樣)對 Git 的內部運作感到好奇,那就跟我一起深入探討一下吧!

大家好!幾天前,我嘗試向其他人解釋 Git 遴選(git cherry-pick)的工作原理,結果發現自己反而更混淆了。

我原先以為 Git 遴選是簡單地應用一個補丁,但當我真正這樣嘗試時,卻未能成功!

因此,接下來我們將談論我原來以為的遴選操作(即應用一個補丁),這個理解為何不準確,以及實際上它是如何執行的(進行“三路合并”)。

盡管本文的內容有些深入,但你并不需要全部理解才能有效地使用 Git。不過,如果你(和我一樣)對 Git 的內部運作感到好奇,那就跟我一起深入探討一下吧!

遴選操作并不只是應用一個補丁

我先前理解的 git cherry-pick COMMIT_ID 的步驟如下:

  • 首先是計算 COMMIT_ID 的差異,就如同執行 git show COMMIT_ID --patch > out.patch 這個命令
  • 然后是將補丁應用到當前分支,就如同執行 git apply out.patch 這個命令

在我們詳細討論之前,我想指出的是,雖然大部分情況下這個模型是正確的,如果這是你的認知模型,那就沒有問題。但是在一些細微的地方,它可能會錯,我覺得這個疑惑挺有意思的,所以我們來看看它究竟是如何運作的。

如果我在存在合并沖突的情況下嘗試進行“計算差異并應用補丁”的操作,下面我們就看看具體會發生什么情況:

$ git show 10e96e46 --patch > out.patch
$ git apply out.patch
error: patch failed: content/post/2023-07-28-why-is-dns-still-hard-to-learn-.markdown:17
error: content/post/2023-07-28-why-is-dns-still-hard-to-learn-.markdown: patch does not apply

這一過程無法成功完成,它并未提供任何解決沖突或處理問題的方案。

而真正運行 git cherry-pick 時的實際情況卻大為不同,我遭遇到了一處合并沖突:

$ git cherry-pick 10e96e46
error: could not apply 10e96e46... wip
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".

因此,看起來 “Git 正在應用一個補丁”這樣的理解方式并不十分準確。但這里的錯誤信息確實標明了 “無法應用 10e96e46”,這么看來,這種理解又不完全是錯的。這到底是怎么回事呢?

那么,遴選到底是怎么執行的呢?

我深入研究了 Git 的源代碼,主要是想了解 cherry-pick 是如何工作的,最終我找到了 這一行代碼

res = do_recursive_merge(r, base, next, base_label, next_label, &head, &msgbuf, opts);

所以,遴選實際上就是一種……合并操作?這有些出乎意料。那具體都合并了什么內容?如何執行這個合并操作的呢?

我意識到我對 Git 的合并操作并不是特別理解,于是我上網搜索了一下。結果發現 Git 實際上采用了一種被稱為 “三路合并” 的合并方式。那這到底是什么含義呢?

Git 的合并策略:三路合并

假設我要合并下面兩個文件,我們將其分別命名為 v1.py 和 v2.py

def greet():
    greeting = "hello"
    name = "julia"
    return greeting + " " + name
def say_hello():
    greeting = "hello"
    name = "aanya"
    return greeting + " " + name

在這兩個文件間,存在兩處不同:

  • def greet() 和 def say_hello
  • name = "julia" 和 name = "aanya"

我們應該選擇哪個呢?看起來好像不可能有答案!

不過,如果我告訴你,原始的函數(我們稱之為 base.py)是這樣的:

def say_hello():
    greeting = "hello"
    name = "julia"
    return greeting + " " + name

一切似乎變得清晰許多!在這個基礎上,v1 將函數的名字更改為 greetv2 將 name = "aanya"。因此,合并時,我們應該同時做出這兩處改變:

def greet():
    greeting = "hello"
    name = "aanya"
    return greeting + " " + name

我們可以命令 Git 使用 git merge-file 來完成這次合并,結果正是我們預期的:它選擇了 def greet() 和 name = "aanya"

$ git merge-file v1.py base.py v2.py -p
def greet():
    greeting = "hello"
    name = "aanya"
    return greeting + " " + name?

這種將兩個文件與其原始版本進行合并的方式,被稱為 三路合并

如果你想在線上試一試,我在 jvns.ca/3-way-merge/ 創建了一個小實驗場。不過我只是草草制作,所以可能對移動端并不友好。

Git 合并的是更改,而非文件

我對三路合并的理解是 —— Git 合并的是更改,而不是文件。我們對同一個文件做出兩種不同的更改,Git 試圖以合理的方式將這兩種更改結合到一起。當兩個更改都對同一行進行操作時,Git 可能會遇到困難,此時就會產生合并沖突。

Git 也可以合并超過兩處的更改:你可以對同一文件有多達 8 處不同的更改,Git 會嘗試將所有更改協調一致。這被稱為八爪魚合并,但除此之外我對其并不了解,因為我從未執行過這樣的操作。

Git 如何使用三路合并來應用補丁

接下來,讓我們進入到一個有些出乎意料的情境!當我們討論 Git “應用補丁”(如在變基 —— rebase、撤銷 —— revert 或遴選 —— cherry-pick 中所做的)時,其實并非是生成一個補丁文件并應用它。相反,實際執行的是一次三路合并。

下面是如何將提交 X 作為補丁應用到你當前的提交,并與之前的 v1v2 和 base 設置相對應:

  1. 在你當前提交中,文件的版本是 v1
  2. 在提交 X 之前,文件的版本是 base
  3. 在提交 X 中,文件的版本是 v2
  4. 執行 git merge-file v1 base v2 以合并它們(實際上,Git 并不直接執行 git merge-file,而是運行一個實現這個功能的 C 函數)。

總的來說,你可以將 base 和 v2 視為“補丁”,它們之間的差異就是你想要應用到 v1 上的更改。

遴選如何運作

假設我們有如下提交圖,并且我們打算在 main 分支上遴選提交 Y

A - B (main)
  \ 
   \ 
    X - Y - Z

那么,如何將此情景轉化為我們前面提過的 v1v2 和 base 組成的三路合并呢?

  • B 是 v1
  • X 是 base,而 Y 是 v2

所以,X 和 Y 共同構成了這個“補丁”。

其實,git rebase 無非就是重復多次執行 git cherry-pick 的過程。

撤銷如何運作

現在,假如我們希望在如下的提交圖上執行 git revert Y

X - Y - Z - A - B
  • B 是 v1
  • Y 是 base,而 X 是 v2

這個過程反映的實際上就是遴選的情況,不過 X 和 Y 的位置顛倒了。我們需要這樣做因為我們期望生成一個“反向補丁”。在 Git 中,撤銷和遴選關系如此的緊密,它們甚至在同一個文件中實現:revert.c

“三路補丁”是一個非常棒的技巧

使用三路合并將提交作為補丁應用的這個技巧非常巧妙且酷炫,我很驚訝之前從未聽說過!我并未聽過一個特定的名字來描述這種方法,但我更傾向于稱之為“三路補丁”。

“三路補丁”的理念在于,你可以通過兩個文件來定義補丁:在應用補丁前后的文件(在我們這篇文章中稱之為 base 和 v2)。

因此,總體來看有三個文件被涉及到:一個是原文件,另外兩個構成了補丁。

最重要的是,與普通補丁相比,三路補丁是一個更加高效的補丁方案,因為在有兩個完整文件的情況下,你擁有更豐富的上下文信息來進行合并。

以下是我們例子中的常規補丁的大致情況:

@@ -1,1 +1,1 @@:
- def greet():
+ def say_hello():
    greeting = "hello"

而下面這就是一個三路補丁。不過,需要提醒的是這個“三路補丁”并不是一個真正的文件格式,這只是我自己提出的一種概念。

BEFORE: (the full file)
def greet():
    greeting = "hello"
    name = "julia"
    return greeting + " " + name
AFTER: (the full file)
def say_hello():
    greeting = "hello"
    name = "julia"
    return greeting + " " + name

《Building Git》 中提到了這點

James Coglan 的書籍 《Building Git》 是我在 Git 源碼之外唯一找到的地方,他解釋了 git cherry-pick 是如何在底層運用三路合并的(我原以為《Pro Git》可能會提及這個,但我并沒能找到此話題的內容)。

我購買完這本書后發現,我早在 2019 年時就已經買過了,這對我來說真的是個很好的參考。

Git 中的合并實際上比這更復雜

在 Git 中,合并不限于三路合并 —— 還有一種我不太理解的叫做“遞歸合并”,還有許多具體處理文件刪除和移動的細節,同時也有多種合并算法。

如果想要了解更多相關知識,我最好的建議是閱讀《Building Git》,盡管我還未完全閱讀這本書。

Git 應用到底做了什么?

我也參閱了 Git 的源代碼,試圖理解 git apply 的功能。它似乎(不出意外地)在 apply.c 中實現。這段代碼解析了一個補丁文件,并通入目標文件來尋找應該在何處應用補丁。核心邏輯似乎在 這里:思路好像是從補丁建議的行數開始,然后向前向后找尋。

/*
	 * There's probably some smart way to do this, but I'll leave
	 * that to the smart and beautiful people. I'm simple and stupid.
	 */
	backwards = current;
	backwards_lno = line;
	forwards = current;
	forwards_lno = line;
	current_lno = line;
for (i = 0; ; i++) {
     ...

這個處理過程不禁讓人覺得非常直白、與之前的期望相符。

Git 三路應用的工作方式

git apply 命令中也有一個 --3way 參數,可以實現三路合并。因此,我們實際上可以通過如下方式,使用 git apply 來大體實現 git cherry-pick 的功能:

$ git show 10e96e46 --patch > out.patch
$ git apply out.patch --3way
Applied patch to 'content/post/2023-07-28-why-is-dns-still-hard-to-learn-.markdown' with conflicts.
U content/post/2023-07-28-why-is-dns-still-hard-to-learn-.markdown

但要注意,參數 --3way 并不只用到了補丁文件的內容!補丁文件開始的部分是:

index d63ade04..65778fc0 100644

d63ade04 和 65778fc0 是舊/新文件版本在 Git 對象數據庫中的 ID,因此 Git 可以用這些 ID 來執行三路補丁操作。但如果有人將補丁文件通過郵件發送給你,而你并沒有新/舊版本的文件,就無法執行這個操作:如果你缺少 blob,將會出現如下錯誤:

$ git apply out.patch
error: repository lacks the necessary blob to perform 3-way merge.

三路合并有點歷史了

有一部分人指出,三路合并比 Git 的歷史還要久遠,它起源于 70 年代末期左右。有一篇 2007 年的 論文 對此進行了討論。

就說這么多!

我真的對于我對于 Git 內部應用補丁的核心方法其實理解得并不深入這一點感到非常吃驚——學習這一點真的很酷!

雖然我對 Git 用戶界面存在 諸多不滿,但是這個特定問題并不包含在內。三路合并似乎是統一解決一系列不同問題的優雅方式,它對于人們來說也很直觀(“應用一個補丁”這個想法是許多編程者都習以為常的思考模式,而它底層實現為三路合并的細節,實際上沒有人真正需要去思考)。

我順便快速推薦一下:我正在寫一部有關 Git 的 zine,如果你對它的發布感興趣,你可以注冊我非常不頻繁的 公告郵件列表

責任編輯:龐桂玉 來源: Linux中國
相關推薦

2021-04-14 14:58:35

Git遴選軟件開發

2015-06-24 09:54:38

Git撤銷

2020-10-13 11:15:31

三路快排

2022-10-27 09:34:06

git撤銷

2023-10-10 14:03:47

swap排序解法

2017-11-13 12:44:30

Git命令工具

2023-02-24 21:14:24

新華三

2015-06-25 10:26:41

YelpMesosDocker

2023-05-30 18:13:59

Git代碼

2009-09-24 11:16:22

CCNA和CCNP

2009-07-09 10:15:23

PHP DOM

2016-04-05 16:10:13

2020-05-28 10:45:31

Git分支合并

2016-08-08 11:13:40

2021-09-23 10:40:13

安全工具漏洞

2024-09-14 14:18:43

2024-06-20 08:06:30

2011-03-30 10:50:55

GitLinux 版本控制

2016-10-11 14:09:33

2023-08-24 22:13:31

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 在线观看免费av网站 | 久久精品无码一区二区三区 | 91xh98hx 在线 国产 | 亚洲高清免费 | 一级毛片大全免费播放 | 成人激情视频 | 7777在线视频 | 日韩一区二区在线视频 | 国产精品久久久 | 亚洲天堂久久 | 天堂中文av | 天天玩天天干天天操 | 剑来高清在线观看 | 国产精品久久久久国产a级 欧美日本韩国一区二区 | 精品免费 | 大象视频一区二区 | 亚洲视频在线观看免费 | 日韩成人中文字幕 | 日韩插插| 黄色男女网站 | 国产99久久精品一区二区300 | 亚洲精品国产第一综合99久久 | 久久综合九色综合欧美狠狠 | 欧美一区二区在线 | 亚洲国产视频一区 | 国产一区高清 | av中文字幕在线观看 | 国产日韩欧美一区二区在线播放 | 成人免费视频久久 | 99re在线视频 | 精品九九九 | 999久久久国产精品 欧美成人h版在线观看 | 久久精彩| 香蕉久久a毛片 | 国产成人精品一区二区三区网站观看 | 免费看a | 91精品一区二区三区久久久久 | 91在线视频免费观看 | 国产日韩一区二区三免费 | 精品国产青草久久久久福利 | 精品欧美一区二区在线观看 |