自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Git 分支:直覺與現(xiàn)實(shí)

系統(tǒng) Linux
這些說法在回顧時看似是顯而易見的,但實(shí)際上我花費(fèi)了大量時間去搞清楚一個更“直觀”的分支概念,這是因為我已經(jīng)習(xí)慣了技術(shù)性的定義,“分支是對某次提交的引用”。

你好!我一直在投入寫作一本關(guān)于 Git 的小冊,因此我對 Git 分支投入了許多思考。我不斷從他人那里聽說他們覺得 Git 分支的操作方式違反直覺。這使我開始思考:直覺上的分支概念可能是什么樣,以及它如何與 Git 的實(shí)際操作方式區(qū)別開來?

在這篇文章中,我想簡潔地討論以下幾點(diǎn)內(nèi)容:

  • 我認(rèn)為許多人可能有的一個直覺性的思維模型
  • Git 如何在內(nèi)部實(shí)現(xiàn)分支的表示(例如,“分支是對提交的指針”)
  • 這種“直覺模型”與實(shí)際操作方式之間的緊密關(guān)聯(lián)
  • 直覺模型的某些局限性,以及為何它可能引發(fā)問題

本文無任何突破性內(nèi)容,我會盡量保持簡潔。

分支的直觀模型

當(dāng)然,人們對分支有許多不同的直覺。我自己認(rèn)為最符合“蘋果樹的一個分支”這一物理比喻的可能是下面這個。

我猜想許多人可能會這樣理解 Git 分支:在下圖中,兩個紅色的提交就代表一個“分支”。

我認(rèn)為在這個示意圖中有兩點(diǎn)很重要:

  1. 分支上有兩個提交
  2. 分支有一個“父級”(main),它是這個“父級”的分支

雖然這個觀點(diǎn)看似合理,但實(shí)際上它并不符合 Git 對于分支的定義 — 最重要的是,Git 并沒有一個分支的“父級”的概念。那么,Git 又是如何定義分支的呢?

在 Git 里,分支是完整的歷史

在 Git 中,一個分支是每個過去提交的完整歷史記錄,而不僅僅是那個“分支”提交。因此,在我們上述的示意圖中,所有的分支(main 和 branch)都包含了 4 次提交。

我創(chuàng)建了一個示例倉庫,地址為:https://github.com/jvns/branch-example。它設(shè)置的分支方式與前圖一樣?,F(xiàn)在,我們來看看這兩個分支:

main 分支包含了 4 次提交:

$ git log --oneline main
70f727a d
f654888 c
3997a46 b
a74606f a

mybranch 分支也有 4 次提交。最后兩次提交在這兩個分支里都存在。

$ git log --oneline mybranch
13cb960 y
9554dab x
3997a46 b
a74606f a

因此,mybranch 中的提交次數(shù)為 4,而不僅僅是 2 次“分支”提交,即 13cb960 和 9554dab。

你可以用以下方式讓 Git 繪制出這兩個分支的所有提交:

$ git log --all --oneline --graph
* 70f727a (HEAD -> main, origin/main) d
* f654888 c
| * 13cb960 (origin/mybranch, mybranch) y
| * 9554dab x
|/
* 3997a46 b
* a74606f a

分支以提交 ID 的形式存儲

在 Git 的內(nèi)部,分支會以一種微小的文本文件的形式存儲下來,其中包含了一個提交 ID。這就是我一開始提及到的“技術(shù)上正確”的定義。這個提交就是分支上最新的提交。

我們來看一下示例倉庫中 main 和 mybranch 的文本文件:

$ cat .git/refs/heads/main
70f727acbe9ea3e3ed3092605721d2eda8ebb3f4
$ cat .git/refs/heads/mybranch
13cb960ad86c78bfa2a85de21cd54818105692bc

這很好理解:70f727 是 main 上的最新提交,而 13cb96 是 mybranch 上的最新提交。

這樣做的原因是,每個提交都包含一種指向其父級的指針,所以 Git 可以通過追蹤這些指針鏈來找到分支上所有的提交。

正如我前文所述,這里遺漏的一個重要因素是這兩個分支間的任何關(guān)聯(lián)關(guān)系。從這里能看出,mybranch 是 main 的一個分支——這一點(diǎn)并沒有被表明出來。

既然我們已經(jīng)探討了直觀理解的分支概念是如何不成立的,我接下來想討論的是,為何它在某些重要的方面又是如何成立的。

人們的直觀感覺通常并非全然錯誤

我發(fā)現(xiàn),告訴人們他們對 Git 的直覺理解是“錯誤的”的說法頗為流行。我覺得這樣的說法有些可笑——總的來說,即使人們關(guān)于某個題目的直覺在某些方面在技術(shù)上不精確,但他們通常會有完全合理的理由來支持他們的直覺!即使是“不正確的”模型也可能極其有用。

現(xiàn)在,我們來討論三種情況,其中直覺上的“分支”概念與我們實(shí)際在操作中如何使用 Git 非常相符。

變基操作使用的是“直觀”的分支概念

現(xiàn)在,讓我們回到最初的圖片。

當(dāng)你在 main 上對 mybranch 執(zhí)行 變基rebase 操作時,它將取出“直觀”分支上的提交(只有兩個紅色的提交)然后將它們應(yīng)用到 main 上。

執(zhí)行結(jié)果就是,只有兩次提交(x 和 y)被復(fù)制。以下是相關(guān)操作的樣子:

$ git switch mybranch
$ git rebase main
$ git log --oneline mybranch
952fa64 (HEAD -> mybranch) y
7d50681 x
70f727a (origin/main, main) d
f654888 c
3997a46 b
a74606f a

在此,git rebase 創(chuàng)建了兩個新的提交(952fa64 和 7d50681),這兩個提交的信息來自之前的兩個 x 和 y 提交。

所以直覺上的模型并不完全錯誤!它很精確地告訴你在變基中發(fā)生了什么。

但因為 Git 不知道 mybranch 是 main 的一個分叉,你需要顯式地告訴它在何處進(jìn)行變基。

合并操作也使用了“直觀”的分支概念

合并操作并不復(fù)制提交,但它們確實(shí)需要一個“基礎(chǔ)base”提交:合并的工作原理是查看兩組更改(從共享基礎(chǔ)開始),然后將它們合并。

我們撤銷剛才完成的變基操作,然后看看合并基礎(chǔ)是什么。

$ git switch mybranch
$ git reset --hard 13cb960  # 撤銷 rebase
$ git merge-base main mybranch
3997a466c50d2618f10d435d36ef12d5c6f62f57

這里我們獲得了分支分離出來的“基礎(chǔ)”提交,也就是 3997a4。這正是你可能會基于我們的直觀圖片想到的提交。

GitHub 的拉取請求也使用了直觀的概念

如果我們在 GitHub 上創(chuàng)建一個拉取請求,打算將 mybranch 合并到 main,這個請求會展示出兩次提交:也就是 x 和 y。這完全符合我們的預(yù)期,也和我們對分支的直觀認(rèn)識相符。

我想,如果你在 GitLab 上發(fā)起一個合并請求,那顯示的內(nèi)容應(yīng)該會與此類似。

直觀理解頗為精準(zhǔn),但它有一定局限性

這使我們的對分支直觀定義看起來相當(dāng)準(zhǔn)確!這個“直觀”的概念和合并、變基操作以及 GitHub 拉取請求的工作方式完全吻合。

當(dāng)你在進(jìn)行合并、變基或創(chuàng)建拉取請求時,你需要明確指定另一個分支(如 git rebase main),因為 Git 不知道你的分支是基于哪個分支的。

然而,關(guān)于分支的直觀理解有一個比較嚴(yán)重的問題:你直覺上認(rèn)為 main 分支和某個分離的分支有很大的區(qū)別,但 Git 并不清楚這點(diǎn)。

所以,現(xiàn)在我們要來討論一下 Git 分支的不同種類。

主干和派生分支

對于人類來說,main 和 mybranch 有著顯著的區(qū)別,你可能針對如何使用它們,有著截然不同的意圖。

通常,我們會將某些分支視為“主干trunk”分支,同時將其他一些分支看作是“派生”。你甚至可能有派生的派生分支。

當(dāng)然,Git 自身并沒有這樣的區(qū)分(“派生”是我剛剛構(gòu)造的術(shù)語?。?,但是分支的種類確實(shí)會影響你如何處理它。

例如:

  • 你可能會想將 mybranch 變基到 main,但你大概不會想將 main 變基到 mybranch —— 那就太奇怪了!
  • 一般來說,人們在重寫“主干”分支的歷史時比短期存在的派生分支更為謹(jǐn)慎。

Git 允許你進(jìn)行“反向”的變基

我認(rèn)為人們經(jīng)常對 Git 感到困惑的一點(diǎn)是 —— 由于 Git 并沒有分支是否是另一個分支的“派生”的概念,它不會給你任何關(guān)于何時合適將分支 X 變基到分支 Y 的指引。這一切需要你自己去判斷。

例如,你可以執(zhí)行以下命令:

$ git checkout main
$ git rebase mybranch

或者

$ git checkout mybranch
$ git rebase main

Git 將會欣然允許你進(jìn)行任一操作,盡管在這個案例中 git rebase main 是極其正常的,而 git rebase mybranch 則顯得格外奇怪。許多人表示他們對此感到困惑,所以我提供了一個展示兩種變基類型的圖片以供參考:

相似地,你可以進(jìn)行“反向”的合并,盡管這相較于反向變基要正常得多——將 mybranch 合并到 main 和將 main 合并到 mybranch 都有各自的益處。

下面是一個展示你可以進(jìn)行的兩種合并方式的示意圖:

Git 對于分支之間缺乏層次結(jié)構(gòu)感覺有些奇怪

我經(jīng)常聽到 “main 分支沒什么特別的” 的表述,而這令我感到困惑——對于我來說,我處理的大部分倉庫里,main 無疑是非常特別的!那么人們?yōu)楹螘Q其為不特別呢?

我覺得,重點(diǎn)在于:盡管分支確實(shí)存在彼此間的關(guān)系(main 通常是非常特別的?。?Git 并不知情這些關(guān)系。

每當(dāng)你執(zhí)行如 git rebase 或 git merge 這樣的 git 命令時,你都必須明確地告訴 Git 分支間的關(guān)系,如果你出錯,結(jié)果可能會相當(dāng)混亂。

我不知道 Git 在此方面的設(shè)計究竟“對”還是“錯”(無疑它有利有弊,而我已對無休止的爭論感到厭倦),但我認(rèn)為,這對于許多人來說,原因在于它有些出人意料。

Git 關(guān)于分支的用戶界面也同樣怪異

假設(shè)你只想查看某個分支上的“派生”提交,正如我們之前討論的,這是完全正常的需求。

下面是用 git log 查看我們分支上的兩次派生提交的方法:

$ git switch mybranch
$ git log main..mybranch --oneline
13cb960 (HEAD -> mybranch, origin/mybranch) y
9554dab x

你可以用 git diff 這樣查看同樣兩次提交的合并差異:

$ git diff main...mybranch

因此,如果你想使用 git log 查看 x 和 y 這兩次提交,你需要用到兩個點(diǎn)(..),但查看同樣的提交使用 git diff,你卻需要用到三個點(diǎn)(...)。

我個人從來都記不住 .. 和 ... 的具體用意,所以我通常雖然它們在原則上可能很有用,但我選擇盡量避免使用它們。

在 GitHub 上,默認(rèn)分支具有特殊性

同樣值得一提的是,在 GitHub 上存在一種“特殊的分支”:每一個 GitHub 倉庫都有一個“默認(rèn)分支”(在 Git 術(shù)語中,就是 HEAD 所指向的地方),具有以下的特別之處:

  • 初次克隆倉庫時,默認(rèn)會檢出這個分支
  • 它作為拉取請求的默認(rèn)接收分支
  • GitHub 建議應(yīng)該保護(hù)這個默認(rèn)分支,防止被強(qiáng)制推送,等等。

很可能還有許多我未曾想到的場景。

總結(jié)

這些說法在回顧時看似是顯而易見的,但實(shí)際上我花費(fèi)了大量時間去搞清楚一個更“直觀”的分支概念,這是因為我已經(jīng)習(xí)慣了技術(shù)性的定義,“分支是對某次提交的引用”。

同樣,我也沒有真正去思索過如何在每次執(zhí)行 git rebase 或 git merge 命令時,讓 Git 明確理解你分支之間的層次關(guān)系——對我而言,這已經(jīng)成為第二天性,并沒有覺得有何困擾。但當(dāng)我反思這個問題時,可以明顯看出,這很容易導(dǎo)致某些人混淆。

責(zé)任編輯:龐桂玉 來源: Linux中國
相關(guān)推薦

2011-03-30 10:50:55

GitLinux 版本控制

2014-08-08 10:20:23

Git版本管理系統(tǒng)

2023-10-09 08:39:33

Git Flow分支管理模型

2022-11-07 08:01:18

Git分支管理

2022-10-26 09:22:19

git命令Linux

2022-05-25 16:51:41

Git 分支重命名開發(fā)者

2020-07-09 08:00:25

Git分支模式

2014-05-29 09:22:57

大數(shù)據(jù)

2018-06-08 09:27:08

GitLinux開源

2021-03-28 17:21:15

Git分支策略

2024-10-14 08:35:29

2020-05-28 10:45:31

Git分支合并

2022-08-11 15:45:13

Git

2023-09-06 15:27:00

混合現(xiàn)實(shí)架構(gòu)

2020-09-06 09:55:13

git分支命令

2013-10-09 10:04:20

LinuxGit

2024-04-18 09:12:58

Git分支代碼

2025-01-13 00:00:15

分支gitmaster

2019-07-12 04:56:16

加密數(shù)據(jù)安全數(shù)據(jù)泄露

2014-08-27 09:51:13

Hadoop
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號