如何更新git浅层克隆?

57

背景

(如果想了解简短版,请参阅下面的#问题)

我有多个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://dev59.com/PnnZa4cB1Zd3GeqPw_kE#20508591 ,我尝试了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>并再次进行克隆是唯一的方法吗?
注意:这不是重复的问题如何在不增加主存储库大小的情况下更新浅克隆的子模块,因为该答案不符合我的期望,并且我使用的是简单存储库,而不是子模块(我不了解)。
3个回答

125

简而言之

如果你已经克隆了一个从分支B克隆的--depth 1仓库,并且希望Git表现得像你已经删除并重新克隆了一样,可以使用以下命令序列:

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

(e.g., 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相同的结果?

请注意,使用--depth 1时,默认情况下是仅有一个分支。你可以用-b指定一个分支,否则Git会向"upstream" Git(url处的Git)询问其当前所检出的分支,并假装你使用了-b thatbranch。因此,在使用--single-branch 无需 -b时,一定要确保该上游存储库的当前分支是合理的;当你使用-b时,确保所给的参数确实是一个分支名而不是标签名。
简单来说,答案基本上就是这个,只有两个细微的变化:
我在https://dev59.com/PnnZa4cB1Zd3GeqPw_kE#20508591后尝试了git fetch --depth 1; git reset --hard origin/master,但有两个问题:首先我不理解为什么需要git reset,其次,尽管文件似乎是最新的,但仍然存在一些旧文件,并且git clean -df无法删除这些文件。两个小改变是:确保使用origin/branchname,并在git clean步骤中添加-xgit clean -d -f -xgit clean -dfx)。至于why,那就有点复杂了。

发生了什么

没有使用 --depth 1 参数时,git fetch 步骤会调用其他 Git 并从中获取分支名称和相应的提交哈希 ID 列表。也就是说,它会找到上游的所有分支及其当前提交的列表。然后,由于您有一个 --single-branch 存储库,您的 Git 会丢弃除单个分支以外的所有内容,并将 Git 需要连接该当前提交与您已经在存储库中拥有的提交的所有内容带过来。
使用 --depth 1 参数时,您的 Git 不再连接新提交到旧历史提交。相反,它仅获取一个提交和其他 Git 对象,以完成该提交。然后,它会写入一个额外的“浅嫁接”条目,将该提交标记为一个新的伪根提交。

常规(非浅层)克隆和提取

这些都与在使用普通(非浅层、非单分支)克隆时Git的行为有关:git fetch调用上游Git,获取所有内容,然后带回您没有的任何内容。这就是为什么初始克隆如此缓慢,而更新通常很快的原因:一旦您获得完整的克隆,更新很少有很多要传输的内容:也许只有几个提交,也许只有几百个,而且大多数提交也不需要其他内容。

存储库的历史记录由提交形成。每个提交命名其提交(或对于合并,是父提交,复数),在从“最新提交”到上一个提交,再到某个更祖先的提交的链中向后移动。当它达到没有父项的提交时,链最终停止,例如在存储库中首次进行的第一个提交。这种提交是一个提交。

也就是说,我们可以绘制提交图。在一个非常简单的存储库中,图形只是一条直线,所有箭头都指向后面:

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

名称master指向第四个也是最新的提交,它指向第三个提交,第三个提交指向第二个提交,第二个提交指向第一个提交。
每个提交都携带了一个包含所有文件的完整快照。未发生任何改变的文件在这些提交之间是共享的:第四个提交只是从第三个提交中“借用”未更改的版本,而第三个提交又从第二个提交中“借用”,依此类推。因此,每个提交都命名了它所需的所有“Git对象”,Git要么在本地找到这些对象(因为它已经拥有它们),要么使用fetch协议从上游Git获取它们。有一种被称为“packing”的压缩格式和一种专门用于网络传输的特殊变体被称为“thin packs”,它使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 长期以来一直支持(可能不稳定的)移植,后来用通用的替换(实际上非常可靠)来代替。为了具体理解它们,我们需要添加一个概念,即每个提交都有自己独特的 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,每个名称都指向 一个单独的提交。 名称 bug/short 指向提交 F。 这就是为什么提交 F 在分支 bug/short 上...但提交 C 也在上面。 提交 Cbug/short 上,因为它可以从该名称到达。 该名称将我们带到 FF 将我们带到 C,因此 C 在分支 bug/short 上。

请注意,提交G,即master的顶端,将我们带到提交F。这意味着提交F也在分支master上。Git中的一个关键概念是:提交可以在一个、多个或甚至没有分支上。分支名称只是在提交图中开始的一种方式。还有其他方法,例如标签名称、refs/stash(它可以让您进入当前的stash:每个stash实际上是一对提交)和reflogs(通常被隐藏起来,因为它们通常只是杂乱无章的)。
这也涉及到移植和替换。移植只是一种有限的替换方式,而1浅层存储库使用了一种有限的移植形式。我不会在这里详细描述替换,因为它们有点更加复杂,但总的来说,Git对所有这些都使用移植或替换作为“代替”。对于提交的特定情况,我们想要的是能够更改或至少假装更改任何提交的父ID或ID... 对于浅层存储库,我们希望能够假装该提交没有父级

1浅层存储库使用嫁接代码的方式并不稳定。对于更一般的情况,我建议使用git replace,因为它也是稳定的。嫁接的唯一推荐用途是——或者至少在多年前是——将它们放在那里只用足够长的时间来运行git filter-branch复制已更改的历史记录,之后您应该完全丢弃嫁接的历史记录。您也可以使用git replace来达到此目的,但与嫁接不同的是,您可以永久性地或半永久性地使用git replace无需使用git filter-branch


创建浅克隆

要创建一个深度为1的当前上游存储库状态的浅克隆,我们将选择三个分支名称之一 - feature/tall, masterbug/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需要历史记录,而我们没有:我们用虚假的根提交欺骗了Git,并且它们背后没有历史记录。因此,我们必须使用git reset代替。

git reset的操作有点复杂,因为它可以影响到三个不同的事物:一个分支名称索引工作目录。 我们已经看到了分支名称是什么:它们只是指向(一个、特定的)提交,我们称之为该分支的tip。 这就剩下索引和工作目录了。

工作树很容易解释:它是您的所有文件所在的地方。这就是全部,没有更多也没有更少。它存在的意义在于让您实际使用Git:Git的全部内容都存储在其中,永远不会丢失,因此可以检索到所有提交。但是它们以一种对普通人无用的格式存在。为了被使用,文件(或更常见的是一个完整的提交文件)必须被提取成正常的格式。工作树就是这样的地方,然后您可以在其上工作并使用它来创建新的提交。
索引有点难以解释。它是Git特有的东西:其他版本控制系统没有它,或者如果它们有类似的东西,它们不会暴露出来。Git有。Git的索引基本上是您要进行的下一个提交,但这意味着它开始时保存了您已经提取到工作树中的当前提交,并且Git使用它来使Git更快。我们稍后会详细介绍这个。

git reset --hard 命令会影响分支名称、索引和工作区这三个部分。它将分支名称“移动”到指向另一个(可能不同的)提交。然后,它会更新索引以匹配该提交,并更新工作区以匹配新的索引。

因此:

git reset --hard origin/master

告诉Git查找origin/master。由于我们运行了git fetch,现在它指向提交G。然后Git将我们的master——我们当前(也是唯一的)分支——也指向提交G,然后更新我们的索引和工作树。我们的图现在看起来像这样:

B   [abandoned - but see below]

G   <-- master, origin/master

现在,masterorigin/master都指向提交G,提交G是被检出到工作目录中的。

为什么需要使用命令git clean -dfx

答案有点复杂,但通常情况下是“不需要”(使用git clean)。

当您确实需要git clean时,这是因为您或者您运行的某些程序已经添加了一些您尚未告诉Git的文件。这些文件是未跟踪和/或已忽略的文件。git clean -df将删除未跟踪的文件(和空目录);添加-x也会删除已忽略的文件。

关于“未跟踪”和“已忽略”的区别,请参见this answer

为什么不需要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时,我们最终会放弃先前的深度为1的浅层图提交。(在我绘制的图中,这是提交B。)但是,在Git中,被放弃的提交很少真正被放弃-至少不是立即放弃。相反,一些特殊名称(如ORIG_HEAD)会暂时保留它们,并且每个引用-分支和标签都是引用的形式-都携带着“先前值”的日志。
您可以使用git reflog refname显示每个reflog。例如,git reflog master不仅显示master现在指向哪个提交,还显示它过去指向的提交。还有一个HEAD本身的reflog,这是git reflog默认显示的内容。
Reflog条目最终会过期。它们的确切持续时间因情况而异,但默认情况下,在某些情况下,它们在30天后可以过期,在其他情况下则在90天后可以过期。一旦它们过期,这些reflog条目将不再保护已弃用的提交(或对于带注释的标记引用,带注释的标记对象 - 标记不应该移动,因此不应该发生这种情况,但如果确实发生了 - 如果您强制Git移动标记,则以与所有其他引用相同的方式处理)。
一旦任何Git对象 - 提交、带注释的标记、“树”或“blob”(文件) - 真正地未被引用,Git就允许真正地将其删除。2 只有在这一点上,提交和文件的底层存储库数据才会消失。即使在这种情况下,只有当运行git gc时,它才会发生。因此,使用git fetch --depth 1更新的浅存储库与使用--depth 1的新克隆不完全相同:浅存储库可能仍具有原始提交的某些残留名称,并且在这些名称过期或被清除之前,不会删除额外的存储库对象。
除了参考检查,对象在过期之前也需要最少的时间。默认为两周。这可以防止git gc删除Git正在创建但尚未建立引用的临时对象。例如,在进行新的提交时,Git首先将索引转换为一系列相互引用但没有顶级引用的tree对象。然后它创建一个新的commit对象,该对象引用顶级树,但是还没有任何对象引用该提交。最后,它更新当前分支名称。在此最后一步完成之前,树和新提交是不可访问的!

--single-branch和/或浅克隆的特殊考虑事项

我在上面提到,git clone -b给出的名称可以是一个tag。对于普通(非浅克隆或非单分支)克隆来说,这可以按照预期工作:你得到一个常规克隆,然后Git通过tag名称进行git checkout。结果是通常的游离HEAD,位于完全普通的克隆中。

然而,使用浅克隆或单分支克隆时,会有一些不寻常的后果。这些后果在某种程度上都是Git让实现显示出来的结果。

首先,如果您使用--single-branch,Git将修改新仓库中的正常fetch配置。正常的fetch配置取决于您选择的remote的名称,但默认值为origin,因此我在这里只使用origin。它的读取方式如下:

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

再次强调,这是一个普通(非单分支)克隆的正常配置。此配置告诉git fetch要获取什么,即“所有分支”。然而,当您使用--single-branch时,您将得到一个仅涉及一个分支的获取行:

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

如果你正在克隆zorg分支,无论你克隆哪个分支,都会进入fetch行。每次将来的git fetch都会遵守这个行,所以你不会获取其他分支。如果你想以后获取其他分支,你需要修改这行或添加更多行。
其次,如果你使用--single-branch并且你要克隆的是一个标签,Git将会放置一个相当奇怪的fetch行。例如,使用git clone --single-branch -b v2.1 ...我得到:
fetch = +refs/tags/v2.1:refs/tags/v2.1

这意味着您将不会得到任何分支,除非有人移动了标签4git 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。(这很像在每个人都叫布鲁斯的团体。)
标签不需要经过这一系列的步骤。标签v2.1只是被命名为refs/tags/v2.1。这意味着无法区分“他们”的标签和“你的”标签。你只能拥有你的标签或者他们的标签。只要没有人移动标签,这并不重要:如果你们两个都有相同的标签,它必须指向相同的对象。(如果有人开始移动标签,情况会变得很丑陋。)
无论如何,Git通过一个简单的规则实现标签的“正常”获取:6当Git已经有一个提交时,如果某个标签名称指向该提交,Git也会复制该标签。对于普通克隆来说,第一个克隆会获取所有标签,然后随后的git fetch操作获取新标签。然而,浅层克隆根据定义省略了一些提交,即图中任何嫁接点以下的所有内容。这些提交不会获取标签。他们无法:要获取标签,您需要拥有提交。Git不允许(除了浅层嫁接之外)在没有实际提交的情况下获得提交ID。

3您可以在命令行上为git fetch提供一些refspec,这些将覆盖默认设置。这仅适用于默认获取。您还可以在配置中使用多个fetch =行,例如,仅获取特定的一组分支,尽管“取消限制”最初单个分支克隆的常规方法是恢复通常的+refs/heads/*:refs/remotes/origin/*获取行。

4由于标签不应该移动,因此我们可以说“这没有任何作用”。但是,如果它们确实移动了,则refspec中的+表示强制标志,因此标签最终会移动。

5别这样做。这很令人困惑。Git会处理它得很好——本地分支位于本地名称空间中,而远程跟踪分支位于远程跟踪名称空间中——但真的很令人困惑。

这个规则与文档不符。我测试的是Git版本2.10.1;更旧的Git可能使用不同的方法。自Git 2.26以来,可能会使用不同的规则,因为现在有一个更新、更高级的协议用于git fetchgit push。如果你关心标签的精确行为,你可能需要在你特定的Git版本上进行测试。

3
好答案!您能否在顶部发布一个 tl;dr,以突出浅克隆主分支所需运行的命令? - takanuva15
有没有推荐的命令来删除任何剩余的git文件,例如 git reflog expire --all,或者 git gc --prune=all? - racitup
@racitup:我不会建议在这里做任何事情,因为这有点像建议您剥离汽车的所有安全和舒适设备,以便它可以更快地行驶(这确实有效——轻量化的汽车会更快,但显然有缺点...)。尽管如此,您实际上可以运行那些命令。为无法访问的reflog条目添加短暂的过期时间(例如“现在”)。这应该会在一定程度上缩小.git目录,具体取决于很多因素。然而,有时重新打包只会使.git目录变得更大。(这不是可能发生的,但确实会发生。) - torek
@torek 我所说的情况是更新生产服务器,您不需要存储库,因为没有进行任何开发的原因。您只想要最新的稳定代码。 - racitup
@racitup:对于这种情况,我宁愿使用一些不需要在服务器上保留Git存储库的部署软件。(哪种部署软件更好呢...这个就比较难说了。) - torek
显示剩余2条评论

3

关于浅克隆更新过程本身,请参见Git 2.12(2017年第一季度)的提交649b0c3
该提交是以下内容的一部分:

提交 649b0c3, 提交 f2386c6, 提交 6bc3d8c, 提交 0afd307 (2016年12月6日) by Nguyễn Thái Ngọc Duy (pclouds)。 请参见提交 1127b3c, 提交 381aa8e (2016年12月6日) by Rasmus Villemoes (ravi-prevas)(由Junio C Hamano -- gitster --提交 3c9979b中合并,于2016年12月21日)

shallow.c


这个paint_down()58babff (shallow.c: the 8 steps to select new commits for .git/shallow - 2013-12-05)的第6步的一部分。当我们从一个浅仓库中拉取时,我们需要知道是否有新的/更新的引用需要在.git/shallow中添加新的“浅提交”(因为我们没有足够的这些引用的历史记录),以及需要哪些引用。
第6步的问题是,为了在不缩短我们的历史记录的情况下维护整个仓库的可达性,需要哪些(新的)浅层提交?为了回答这个问题,我们使用UNINTERESTING ("rev-list --not --all")标记所有可通过现有引用到达的提交,使用BOTTOM标记浅层提交,然后对于每个新/更新引用,在遍历提交图形时,直到我们命中UNINTERESTING或BOTTOM,将引用标记在提交上。完成所有遍历后,我们检查新的浅层提交。如果我们没有看到任何在新浅层提交上标记的新引用,那么我们知道所有新的/更新的引用都可以使用我们的历史记录和.git/shallow到达。此时,不需要该浅层提交并且可以将其丢弃。因此,这就是代码。遍历提交的循环基本上是:
  1. 从队列中获取一个提交
  2. 如果它是SEEN或UNINTERESTING则忽略
  3. 标记它
  4. 遍历所有父提交,并..
    • 5.a. 如果它以前从未被标记过,则标记它
    • 5.b. 把它放回队列中
在这个补丁中我们删除了第5a步,因为它是不必要的。在步骤5a中被标记的提交将会被放回队列中,并且将在下一次迭代中在步骤3中被标记。唯一不会被标记的情况是当提交已经被标记为UNINTERESTING(5a没有检查此项),这将在步骤2中被忽略。

更新带有子模块的浅仓库时需要小心:

使用Git 2.37(2022年第三季度),更新嫁接信息会使以前在嫁接文件中的内核提交对象的父列表无效。

请参见提交4d4e49f(由Jonathan Tan(jhowtan于2022年6月6日发布)。
(由Junio C Hamano -- gitster --提交eef985e中合并,2022年6月13日)

commit,shallow: 如果嫁接改变了,则取消解析提交

Signed-off-by: Jonathan Tan

当解析提交时,如果该提交有嫁接信息,则假装它具有不同(可能为空)的父级列表。
但是当解析提交时可能会出现错误,即在更新嫁接信息(例如重写浅层文件)时,随后再次使用相同的提交:提交的父级不符合更新后的嫁接信息,而是在解析时的信息。
通常情况下,这不是问题,因为提交通常与其嫁接信息同时引入到存储库中。也就是说,当我们尝试解析该提交时,我们已经拥有了它的嫁接信息。
但是,在将浅点直接提取到具有子模块的存储库中时,这是一个问题。
函数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

0
如果目标是在不获取整个历史记录的情况下更新浅克隆(但允许获取短历史记录),则可以使用现代版本的 git(>= 2.11.1)采用以下替代方法:
  • --shallow-since=... 只获取早于给定日期的提交
  • --shallow-exclude=... 获取而不获取给定提交的祖先提交

你的回答忽略了问题的关键部分:一旦我有一个浅克隆,如何使用你的标志获取更新的克隆。 - usr1234567
1
显然,您需要再次运行 git fetch 命令,并带上这些标志。 - tkruse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接