簡體   English   中英

如何更新 git 淺克隆?

[英]How to update a git shallow clone?

背景

(對於 tl;dr,請參閱下面的#questions)

我有多個 git 存儲庫淺克隆。 我使用的是淺克隆,因為與深度克隆相比,它要小得多。 每個都是關於git clone --single-branch --depth 1 <git-repo-url> <dir-name>

這工作正常,除了我不知道如何更新它。

當我通過標簽克隆時,更新沒有意義,因為標簽是即時凍結的(據我所知)。 在這種情況下,如果我想更新,這意味着我想通過另一個標簽克隆,所以我只需rm -rf <dir-name>並再次克隆。

當我克隆了主分支的 HEAD 之后想要更新它時,事情變得更加復雜。

我嘗試git pull --depth 1但雖然我不會將任何內容推送到遠程存儲庫,但它抱怨它不知道我是誰。

我試過git fetch --depth 1 ,但雖然它似乎更新了一些東西,但我檢查它不是最新的(遠程存儲庫上的某些文件與我的克隆上的文件內容不同)。

https://stackoverflow.com/a/20508591/279335之后,我嘗試git fetch --depth 1; git reset --hard origin/master git fetch --depth 1; git reset --hard origin/master ,但有兩件事:首先我不明白為什么需要git reset ,其次,雖然文件似乎是最新的,但仍然有一些舊文件,並且git clean -df不會刪除這些文件。

問題

讓一個使用git clone --single-branch --depth 1 <git-repo-url> <dir-name>創建的克隆。 如何更新它以達到與rm -rf <dir-name>; git clone --single-branch --depth 1 <git-repo-url> <dir-name> rm -rf <dir-name>; git clone --single-branch --depth 1 <git-repo-url> <dir-name> 還是rm -rf <dir-name>並再次克隆是唯一的方法?

筆記

這不是How to update a shallow cloned submodule without increase main repo size的副本,因為答案不符合我的期望,而且我使用的是簡單的存儲庫,而不是子模塊(我不知道)。

TL;博士

假設您有一個從分支B克隆的現有--depth 1存儲庫,並且您希望 Git 的行為就像您刪除並重新克隆一樣,您可以使用以下命令序列:

git fetch --depth 1
git reset --hard origin/B
git clean -dfx

(例如, git reset --hard origin/master —我不能在上面的代碼文字部分中使用斜體)。 您應該能夠在其他兩個命令之前或之后的任何時候執行git clean步驟,但git reset必須在git fetch之后。

[稍微改寫和格式化]給定一個使用git clone --single-branch --depth 1 url directory創建的克隆,我如何更新它以獲得與rm -rf directory ; git clone --single-branch --depth 1 url directory rm -rf directory ; git clone --single-branch --depth 1 url directory

請注意,使用--depth 1--single-branch默認設置 (單個)分支是您使用-b給出的分支。 這里有很長的一段關於使用-b和標簽的問題,但我將把它留到以后。 如果您使用-b ,您的 Git 會詢問“上游”Git(位於url的 Git)已簽出哪個分支,並假裝您使用了-b thatbranch 這意味着在使用-b的情況下使用--single-branch時務必小心,以確保上游存儲庫的當前分支是合理的,當然,當使用-b時,請確保您的分支參數give 確實命名了一個分支,而不是一個標簽。

簡單的答案基本上就是這個,有兩個細微的變化:

https://stackoverflow.com/a/20508591/279335之后,我嘗試git fetch --depth 1; git reset --hard origin/master git fetch --depth 1; git reset --hard origin/master ,但有兩件事:首先我不明白為什么需要git reset ,其次,雖然文件似乎是最新的,但仍然有一些舊文件,並且git clean -df不會刪除這些文件。

兩個細微的變化是:確保您使用origin/ branchname ,並將-xgit clean -d -f -xgit clean -dfx )添加到git clean步驟。 至於為什么,那就有點復雜了。

這是怎么回事

如果沒有--depth 1git fetch步驟會調用另一個 Git 並從中獲取分支名稱和相應提交哈希 ID 的列表。 也就是說,它會找到所有上游分支及其當前提交的列表。 然后,因為您有一個--single-branch存儲庫,您的Git 會丟棄除單個分支之外的所有分支,並帶來 Git 將當前提交連接回您存儲庫中已有的提交所需的所有內容。

使用--depth 1 ,您的 Git 根本不會費心將新提交連接到舊的歷史提交。 相反,它只獲取一個提交和完成該提交所需的其他 Git 對象。 然后它會寫入一個額外的“淺嫁接”條目,以將該提交標記為新的偽根提交。

常規(非淺)克隆和獲取

這些都與當您使用普通(非淺層、非單分支)克隆時 Git 的行為方式有關: git fetch調用上游 Git,獲取所有內容的列表,然后帶上您沒有的任何內容t 已經有了 這就是為什么初始克隆如此緩慢,而獲取更新通常如此之快的原因:一旦你得到一個完整的克隆,更新很少會帶來很多東西:可能是幾個提交,可能是幾百個,並且大多數提交也不需要太多其他內容。

存儲庫的歷史是由提交形成的。 每個提交命名其父提交(或對於合並,父提交,復數),在從“最新提交”到前一個提交,再到一些更祖先的提交等等的鏈中。 當到達沒有父級的提交時,鏈最終會停止,例如在存儲庫中進行的第一次提交。 這種提交是提交。

也就是說,我們可以繪制提交圖。 在一個非常簡單的存儲庫中,該圖只是一條直線,所有箭頭都指向后方:

o <- o <- o <- o   <-- master

名稱master指向第四個和最新的提交,它指向第三個,又指向第二個,又指向第一個。

每個提交都帶有該提交中所有文件的完整快照。 完全沒有更改的文件在這些提交之間共享:第四次提交只是從第三次提交中“借用”未更改的版本,從第二次提交中“借用”它,依此類推。 因此,每個提交都會命名它需要的所有“Git 對象”,Git 要么在本地找到這些對象——因為它已經擁有它們——要么使用fetch協議從另一個上游 Git 中獲取它們。 有一種稱為“打包”的壓縮格式,以及一種稱為“瘦包”的網絡傳輸的特殊變體,它允許 Git 做得更好/更漂亮,但原理很簡單:Git 需要所有,而且只需要那些對象隨着新的提交,它正在接受。 你的 Git 決定它是否有這些對象,如果沒有,從他們的 Git 中獲取它們。

更復雜、更完整的圖通常有幾個分支點,一些點合並,多個分支名稱指向不同的分支提示:

        o--o   <-- feature/tall
       /
o--o--o---o    <-- master
    \    /
     o--o      <-- bug/short

這里分支bug/short被合並回master ,而分支feature/tall仍在開發中。 現在可以(可能)完全刪除名稱bug/short :如果我們完成了對它的提交,我們就不再需要它了。 master頂端的提交命名了兩個先前的提交,包括bug/short頂端的提交,因此通過獲取master我們將獲取bug/short提交。

請注意,簡單和稍微復雜的圖都只有一個根提交。 這很典型:所有有提交的存儲庫都至少有一個根提交,因為第一個提交始終是根提交; 但大多數存儲庫也只有一個根提交。 但是,您可以有不同的根提交,如下圖所示:

 o--o
     \
o--o--o   <-- master

或者這個:

 o--o     <-- orphan

o--o      <-- master

實際上,只有一個master的可能是通過將orphan合並為master ,然后刪除名稱orphan

移植物和替代物

很長一段時間以來,Git 一直(可能不穩定)支持Grafts ,它被替換為(更好,實際上是可靠的)對通用替換的支持。 為了具體掌握它們,我們需要在上面添加每個提交都有自己唯一 ID 的概念。 這些 ID 是又大又丑的 40 字符 SHA-1 哈希、 face0ff...等等。 事實上,每個Git 對象都有一個唯一的 ID,盡管出於圖形目的,我們只關心提交。

對於繪制圖形,那些大的哈希 ID 使用起來太痛苦了,所以我們可以使用一個字母的名稱AZ來代替。 讓我們再次使用此圖,但輸入一個字母的名稱:

        E--H   <-- feature/tall
       /
A--B--D---G    <-- master
    \    /
     C--F      <-- bug/short

提交H指回提交EEH父級)。 提交G ,這是一個合並提交——意味着它至少有兩個父節點——指回DF ,依此類推。

請注意,分支名稱feature/tallmasterbug/short ,每個都指向一個提交 提交F的名稱bug/short點。 這就是為什么提交F在分支bug/short ...但提交C也是如此。 提交C處於bug/short狀態,因為它可以從名稱中訪問 這個名字把我們帶到了F ,而F把我們帶到了C ,所以C在分支bug/short上。

但是請注意,提交G ,即master的提示,讓我們提交F 這意味着提交F也在分支master上。 這是 Git 中的一個關鍵概念:提交可以在一個多個甚至沒有分支上。 分支名稱只是在提交圖中開始的一種方式。 還有其他方法,例如標簽名稱、 refs/stash (它可以讓您進入當前的存儲:每個存儲實際上是幾個提交)和 reflog(通常隱藏在視圖之外,因為它們通常只是雜亂無章)。

然而,這也讓我們進行移植和替換。 移植只是一種有限的替換,淺層存儲庫使用有限形式的移植。 1我不會在這里完全描述替換,因為它們有點復雜,但總的來說,Git 對所有這些所做的是將移植或替換用作“替代”。 對於commits的特定情況,我們在這里想要的是能夠改變——或者至少,假裝改變——任何提交的父 ID 或 ID ......對於淺層存儲庫,我們希望能夠假裝有問題的提交沒有父母。


1淺層存儲庫使用移植代碼的方式並不不穩定。 對於更一般的情況,我建議使用git replace代替,因為這也是並且不是不穩定的。 移植的唯一推薦用途是——或者至少是,幾年前——將它們放置在足夠長的時間以運行git filter-branch復制一個改變的——移植的——歷史,之后你應該完全丟棄移植的歷史。 您也可以為此目的使用git replace ,但與移植不同的是,您可以永久或半永久地使用git replace無需git filter-branch


做一個淺克隆

為了對上游存儲庫的當前狀態進行深度 1 的淺層克隆,我們將選擇三個分支名稱之一—— feature/tallmasterbug/short ——並將其轉換為提交 ID。 然后我們將編寫一個特殊的嫁接條目,上面寫着:“當你看到那個提交時,假裝它沒有父提交,即,它是一個根提交。”

假設我們選擇master 名稱master指向提交G ,因此要對提交G進行淺層克隆,我們照常從上游 Git 獲取提交G ,然后編寫一個特殊的嫁接條目,聲稱提交G沒有父級。 我們將它放入我們的存儲庫中,現在我們的圖表如下所示:

G   <-- master, origin/master

那些父 ID 實際上仍然在G中; 只是每次我們使用 Git 或向我們展示歷史記錄時,它都會立即“嫁接”什么都沒有,因此G似乎是根提交,用於歷史跟蹤目的。

更新我們之前制作的淺層克隆

但是,如果我們已經有一個(深度為 1 的淺)克隆,並且我們想要更新它怎么辦? 嗯,這不是一個真正的問題。 假設我們在master指向提交B時,在新分支和錯誤修復之前對上游進行了淺克隆。 這意味着我們目前有這個:

B   <-- master, origin/master

雖然B的真正父母是A ,但我們有一個淺克隆嫁接條目說“假裝B是根提交”。 現在我們git fetch --depth 1 ,它查找上游的master ——我們稱之為origin/master的東西——並看到提交G 我們從上游抓取提交G及其對象,但故意抓取提交DF 然后我們更新我們的淺克隆移植條目,說“假裝G也是根提交”:

B   <-- master

G   <-- origin/master

我們的存儲庫現在有兩個根提交:名稱master (仍然)指向提交B ,我們(仍然)假裝其父母不存在,名稱origin/master指向G ,我們假裝其父母不存在。

這就是你需要git reset的原因

在普通存儲庫中,您可能會使用git pull ,這實際上是git fetch后跟git merge 但是git merge需要歷史,而我們沒有:我們用假的 root 提交偽造了 Git,而他們背后沒有歷史。 所以我們必須使用git reset代替。

git reset所做的有點復雜,因為它最多可以影響三個不同的東西:分支名稱索引工作樹 我們已經看到了分支名稱是什么:它們只是指向一個(一個,特定的)提交,我們稱之為分支的尖端 這留下了索引和工作樹。

工作樹很容易解釋:它是所有文件所在的位置。 就是這樣:不多也不少。 它的存在是為了讓你可以實際使用Git:Git 就是要永遠存儲每一個提交,以便它們都可以被檢索。 但它們的格式對凡人無用。 使用,一個文件——或者更典型地,整個提交的文件價值——必須被提取成它的正常格式。 工作樹就是發生這種情況的地方,然后您可以對其進行處理並使用它進行新的提交。

指數有點難以解釋。 這是 Git 特有的東西:其他版本控制系統沒有,或者如果他們有類似的東西,他們不會公開它。 吉特可以。 Git 的索引本質上是你保存下一次提交的地方,但這意味着它開始保存你提取到工作樹中的當前提交,Git 使用它來使 Git 更快。 稍后我們將對此進行更多說明。

git reset --hard所做的是影響所有三個:分支名稱、索引和工作樹。 移動分支名稱,使其指向(可能不同的)提交。 然后它更新索引以匹配該提交,並更新工作樹以匹配新索引。

因此:

git reset --hard origin/master

告訴 Git 查找origin/master 由於我們運行了git fetch ,現在指向提交G Git 然后讓我們的主分支——我們當前的(也是唯一的)分支——也指向提交G ,然后更新我們的索引和工作樹。 我們的圖表現在看起來像這樣:

B   [abandoned - but see below]

G   <-- master, origin/master

現在masterorigin/master都命名為 commit G ,而 commit G是簽出到工作樹中的那個。

為什么需要git clean -dfx

這里的答案有點復雜,但通常是“你不需要”(需要git clean )。

當你確實需要git clean時,那是因為你——或者你運行的東西——向你的工作樹中添加了你沒有告訴 Git 的文件。 這些是未跟蹤和/或忽略的文件。 使用git clean -df將刪除未跟蹤的文件(和空目錄); 添加-x也將刪除被忽略的文件。

有關“未跟蹤”和“忽略”之間區別的更多信息,請參閱此答案

為什么不需要git clean :索引

我在上面提到你通常不需要運行git clean 這是因為索引。 正如我之前所說,Git 的索引主要是“下一次提交”。 如果您從不添加自己的文件——如果您只是使用git checkout來檢查您一直擁有的各種現有提交,或者您使用git fetch添加的提交; 或者如果您使用git reset --hard移動分支名稱並將索引和工作樹切換到另一個提交,那么現在索引中的任何內容都在那里,因為早期的git checkout (或git reset它在索引中,也進入工作樹。

換句話說,索引有一個簡短的——Git 可以快速訪問的——描述當前工作樹的摘要清單 Git 使用它來知道現在工作樹中的內容。 當您通過git checkoutgit reset --hard讓 Git 切換到另一個提交時,Git 可以快速將現有索引與新提交進行比較。 任何已更改的文件,Git 必須從新提交中提取(並更新索引)。 任何新添加的文件,Git 也必須提取(並更新索引)。 任何已經消失的文件——在現有索引中,但不在新提交中——Git 必須刪除……這就是 Git 所做的。 Git 根據當前索引和新提交之間的比較來更新、添加和刪除工作樹中的這些文件。

這意味着如果你確實需要git clean ,你必須在 Git 之外做了一些添加文件的事情。 這些添加的文件不在索引中,因此根據定義,它們未被跟蹤和/或忽略。 如果它們只是未被跟蹤, git clean -f會刪除它們,但如果它們被忽略,只有git clean -fx會刪除它們。 (您希望-d只是刪除在清理過程中為空或變為空的目錄。)

放棄的提交和垃圾收集

我提到並繪制了更新的淺圖,當我們git fetch --depth 1然后git reset --hard時,我們最終放棄了之前的 depth-1 淺圖提交。 (在我繪制的圖表中,這是提交B 。)然而,在 Git 中,被放棄的提交很少被真正放棄——至少,不是馬上被放棄。 取而代之的是,像ORIG_HEAD這樣的特殊名稱會在它們身上保留一段時間,並且每個引用(分支和標簽是引用的形式)都帶有“先前值”的日志

您可以使用git reflog refname顯示每個 reflog。 例如, git reflog master不僅會顯示哪些提交master的名稱now ,還會顯示它在過去命名的提交。 HEAD本身也有一個 reflog,這是git reflog默認顯示的。

Reflog 條目最終會過期。 它們的確切持續時間各不相同,但默認情況下,在某些情況下,它們有資格在 30 天后到期,在其他情況下則在 90 天后到期。 一旦它們過期,這些 reflog 條目就不再保護放棄的提交(或者,對於帶注釋的標記引用,帶注釋的標記對象——標記不應該移動,所以這種情況不應該發生,但如果它確實發生了——如果你強制Git 來移動標簽——它的處理方式與所有其他引用相同)。

一旦任何 Git 對象——提交、帶注釋的標簽、“樹”或“blob”(文件)——真正未被引用,Git 就可以真正刪除它。 2只有在這一點上,提交和文件的底層存儲庫數據才會消失。 即使那樣,它也只會在運行git gc時發生。 因此,使用git fetch --depth 1更新的淺存儲庫與使用 --depth 1 更新的新克隆--depth 1 淺存儲庫可能對原始提交有一些揮之不去的名稱,並且不會刪除額外的存儲庫對象直到這些名稱過期或以其他方式被清除。


2除了引用檢查之外,對象在過期之前也有最短時間 默認為兩周。 這可以防止git gc刪除 Git 正在創建但尚未建立引用的臨時對象。 例如,當進行新的提交時,Git 首先將索引轉換為一系列相互引用但沒有頂級引用的tree對象。 然后它創建一個新的commit對象,該對象引用頂級樹,但還沒有任何內容引用該提交。 最后,它更新當前分支名稱。 在最后一步完成之前,樹和新提交是無法訪問的!


--single-branch和/或淺克隆的特殊注意事項

我在上面提到,你給git clone -b的名字可以引用一個標簽 對於普通的(非淺層或非單分支)克隆,這和預期的一樣:你得到一個普通的克隆,然后 Git 通過標簽名進行git checkout 結果是通常分離的 HEAD,在一個完全普通的克隆中。

然而,對於淺層或單分支克隆,會產生一些不尋常的后果。 在某種程度上,這些都是 Git 讓實現顯示出來的結果。

首先,如果您使用--single-branch ,Git 會更改新存儲庫中的正常fetch配置。 正常的fetch配置取決於您為remote選擇的名稱,但默認是origin ,所以我將在這里使用origin 上面寫着:

fetch = +refs/heads/*:refs/remotes/origin/*

同樣,這是正常(非單分支)克隆的正常配置。 此配置告訴git fetch要獲取什么,即“所有分支”。 但是,當您使用--single-branch時,您會得到一個僅引用一個分支的 fetch 行:

fetch = +refs/heads/zorg:refs/remotes/origin/zorg

如果你正在克隆zorg分支。

無論您克隆哪個分支,它都是進入fetch行的分支。 每個未來的git fetch都將遵循這一行, 3所以你不會獲取任何其他分支。 如果您以后確實想獲取其他分支,則必須更改此行或添加更多行。

其次,如果你使用--single-branch並且你克隆的是一個 tag ,Git 會放入一個相當奇怪的fetch行。 例如,使用git clone --single-branch -b v2.1 ...我得到:

fetch = +refs/tags/v2.1:refs/tags/v2.1

這意味着你不會得到任何分支,除非有人移動了標簽,否則4 git fetch將什么也不做!

第三,由於git clonegit fetch獲取標簽的方式,默認的標簽行為有點奇怪。 請記住,標簽只是對一個特定提交的引用,就像分支和所有其他引用一樣。 但是,分支和標簽之間有兩個關鍵區別:分支應該會移動(標簽不會移動),分支會被重命名(標簽不會)。

請記住,在上述所有內容中,我們不斷發現另一個(上游)Git 的master成為我們的origin/master ,依此類推。 這是重命名過程的示例。 我們還通過fetch =行簡要地了解了重命名的工作原理:我們的 Git 獲取他們的refs/heads/master並將其更改為我們的refs/remotes/origin/master 這個名字不僅看起來不同( origin/master ),而且實際上不能與我們的任何分支相同。 如果我們創建一個名為origin/master的分支, 5這個分支的“全名”實際上是refs/heads/origin/master ,這與另一個全名refs/remotes/origin/master不同。 只有當 Git 使用較短的名稱時,我們才有一個(常規的、本地的)分支名為origin/master和另一個不同的(遠程跟蹤)分支名為origin/master (這很像在一個每個人都叫 Bruce 的小組中。)

標簽不會經歷這一切。 標簽v2.1只是命名為refs/tags/v2.1 這意味着無法將“他們的”標簽與“您的”標簽分開。 您可以擁有自己的標簽,也可以擁有他們的標簽。 只要沒有人移動標簽,這無關緊要:如果你們都有標簽,它必須指向同一個對象 (如果有人開始移動標簽,事情就會變得丑陋。)

在任何情況下,Git 通過一個簡單的規則實現“正常”的標簽獲取: 6當 Git 已經有提交時,如果某些標簽名稱提交,Git 也會復制該標簽。 使用普通克隆,第一個克隆獲取所有標簽,然后后續的git fetch操作獲取標簽。 然而,根據定義,淺層克隆會省略一些提交,即圖中任何移植點以下的所有內容。 這些提交不會獲取標簽。 他們不能:要擁有標簽,您需要提交。 不允許 Git(除了通過淺層移植)在沒有實際提交的情況下擁有提交的 ID。


3您可以在命令行上給git fetch一些 refspec(s),這些將覆蓋默認值。 這僅適用於默認提取。 您也可以在配置中使用多個fetch =行,例如,僅獲取一組特定的分支,盡管“取消限制”初始單分支克隆的正常方法是放回通常的+refs/heads/*:refs/remotes/origin/*獲取線。

4由於標簽不應該移動,我們可以說“這什么都不做”。 但是,如果它們確實移動了,則 refspec 中的+表示強制標志,因此標簽最終會移動。

5不要這樣做。 這很令人困惑。 Git會很好地處理它——本地分支在本地名稱空間中,而遠程跟蹤分支在遠程跟蹤名稱空間中——但這確實令人困惑。

6此規則與文檔不符。 我針對 Git 2.10.1 版本進行了測試; 較舊的 Git 可能使用不同的方法。 從 2.26 開始的 Git 也可能使用不同的規則,因為git fetchgit push有一個更新、更高級的協議可供使用。 如果您關心標簽的精確行為,您可能需要在您的特定 Git 版本上對其進行測試。

關於淺層克隆更新過程本身,請參閱 Git 2.12(2017 年第一季度)的提交 649b0c3
該提交是以下內容的一部分:

提交 649b0c3提交 f2386c6提交 6bc3d8c提交 0afd307 (2016 年 12 月 6 日),作者Nguyễn Thai Ngọc Duy ( pclouds ) 請參閱Rasmus Villemoes ( ravi-prevas )提交 1127b3c提交 381aa8e (2016 年 12 月 6 日)。 (由Junio C Hamano -- gitster --提交 3c9979b中合並,2016 年 12 月 21 日)

shallow.c

paint_down()是 58babff 的第 6 步的一部分(shallow.c:為 .git/shallow - 2013-12-05 選擇新提交的 8 個步驟)
當我們從淺存儲庫中獲取時,我們需要知道新的/更新的 ref 之一是否需要.git/shallow中的新“淺提交”(因為我們沒有足夠的這些 ref 的歷史記錄)以及哪個。

第 6 步的問題是,在縮短我們的歷史記錄的情況下,其他需要哪些(新的)淺提交來保持整個存儲庫的可訪問性?
為了回答,我們用 UNINTERESTING (" rev-list --not --all ") 標記從現有 refs 可訪問的所有提交,用 BOTTOM 標記淺提交,然后對於每個新/更新的 refs,遍歷提交圖,直到我們點擊UNINTERESTING 或 BOTTOM,在我們走路時在提交上標記 ref。

在所有遍歷完成后,我們檢查新的淺提交。 如果我們沒有看到在新的淺提交上標記了任何新的 ref,我們知道所有新的/更新的 ref 都可以僅使用我們的歷史記錄和.git/shallow來訪問。
有問題的淺提交是不需要的,可以丟棄。

所以,代碼。

這里的循環(遍歷提交)基本上是:

  1. 從隊列中獲取一個提交
  2. 如果它被看到或不感興趣,請忽略
  3. 標記它
  4. 通過所有的父母和..
    • 5.aa 如果之前從未標記,請標記它
    • 5.b 把它放回隊列中

我們在這個補丁中所做的是刪除步驟 5a,因為它不是必需的。
在 5a 標記的提交被放回隊列中,並將在下一次迭代的第 3 步標記。 唯一不會被標記的情況是提交已經被標記為 UNINTERESTING(5a 不檢查這個),這將在步驟 2 中被忽略。


使用子模塊更新淺層存儲庫時需要小心:

在 Git 2.37(2022 年第 3 季度)中,更新嫁接信息會使過去位於嫁接文件中的核心提交對象的父級列表無效。

請參閱Jonathan Tan ( jhowtan )提交 4d4e49f (2022 年 6 月 6 日)。
(由Junio C Hamano -- gitster --提交 eef985e中合並,2022 年 6 月 13 日)

commit,shallow :如果嫁接發生了變化,則不解析提交

簽字人:Jonathan Tan

解析提交時,如果有該提交的移植信息,它會假裝具有不同的(可能為空的)父級列表。
但是當一個commit被解析,graft信息被更新(例如,一個淺層文件被重寫時),並且隨后使用相同的commit時,可能會出現一個bug:commit的parents不符合更新的嫁接信息,而是解析時的信息。

這通常不是問題,因為提交通常與移植信息同時引入存儲庫。
這意味着當我們嘗試解析那個提交時,我們已經有了它的嫁接信息。

但是當使用 submodules 將淺點直接提取到存儲庫中時,這是一個問題

函數assign_shallow_commits_to_refs()解析所有尋找的對象(包括我們直接獲取的淺點)。
fetch-pack.c update_shallow()中, assign_shallow_commits_to_refs()commit_shallow_file()之前調用,這意味着在更新嫁接信息之前會解析淺點。
一旦提交被解析,它就不再對任何移植信息更新敏感。
當我們進行修訂遍歷以搜索要獲取的子模塊時,隨后會使用此已解析的提交,這意味着即使提交是一個淺點(因此應該被視為沒有父節點),該提交也被認為具有父節點。

因此,無論何時更新嫁接信息,都將先前嫁接的提交和新嫁接的提交標記為未解析。

這應該在帶有子模塊的存儲庫中工作:

SHALLOW=$(cat shallow/.git/shallow) && \
git -C repo-with-sub fetch --update-shallow ../shallow/.git "$SHALLOW":refs/heads/a-shallow

如果目標是在不獲取整個歷史記錄的情況下更新淺克隆(但允許獲取較短的歷史記錄),那么使用現代版本 git (>= 2.11.1) 的替代方法可以使用:

  • --shallow-since=...僅獲取早於給定日期的提交
  • --shallow-exclude=...獲取而不獲取作為給定提交的祖先的提交

暫無
暫無

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

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