如何浅拉按分支名称跟踪的子模块

Mar*_*cky 5 git git-submodules

您好,我有一个包含子模块的超级项目。子模块通过分支名称而不是 sha 提交号进行跟踪。在我们的构建服务器上,我想尽可能少地拉动。所以我尝试了

git submodule update --remote --init 
Run Code Online (Sandbox Code Playgroud)

然而这并不肤浅。似乎拉出所有内容然后切换到分支

git submodule update --remote --init --depth 1
Run Code Online (Sandbox Code Playgroud)

这不起作用,它失败了:

git submodule update --remote --init --depth 1 ThirdParty/protobuf
Submodule 'ThirdParty/protobuf' (ssh://myrepo/thirdparty/protobuf.git) 
registered for path 'ThirdParty/protobuf'
Cloning into '/home/martin/jenkins/workspace/test_log_service/repo/ThirdParty/protobuf'...
fatal: Needed a single revision
Unable to find current origin/version/3.2.0-era revision in submodule path 'ThirdParty/protobuf'
Run Code Online (Sandbox Code Playgroud)

关于浅层子模块有一个不同的问题,但是我不认为它适用于分支,仅适用于 sha 提交

tor*_*rek 9

长话短说

\n

我认为您遇到了 Git 中的错误。要解决此问题,请--no-single-branch手动使用或配置分支。

\n

其他需要了解的事情:

\n
    \n
  • 如果您有递归子模块,请确保您的 Git 是最新的,并用于--recommend-shallow递归地启用浅层子模块,或--no-recommend-shallow禁用它们。

    \n
  • \n
  • 可能需要分两步执行此操作。我将在下面将其显示为两步序列。我知道这段代码在 Git 1.7 和当前(2.26 左右)Git 之间已经发生了很大的变化,我预计两步序列也适用于大多数旧版本。

    \n
  • \n
\n

这两个步骤是:

\n
N=...        # set your depth here, or expand it in the two commands\ngit submodule update --init --depth $N --no-single-branch\ngit submodule update --remote --depth $N\n
Run Code Online (Sandbox Code Playgroud)\n

Git 人员最近一直在修复各种浅克隆子模块错误,作为添加--recommend-shallow递归子模块的一部分,因此这可能全部作为一个命令来工作。根据下面的分析,在当前的 Git 中,它应该都是作为一个命令运行的。但是,--no-single-branch获取的对象比--single-branch.

\n

另一种选择可能是允许单分支模式,但修复fetch子模块中的 refspec。无论如何,这需要三个步骤\xe2\x80\x94well,三个单独的 Git 命令:

\n
branch=...   # set this to the branch you want\ngit submodule update --init --depth $N\n(cd path/to/submodule &&\n git config remote.origin.fetch +refs/heads/$branch:refs/remotes/origin/$branch)\ngit submodule update --remote --depth $N\n
Run Code Online (Sandbox Code Playgroud)\n

(您可以在所有子模块中使用 执行此操作git submodule foreach,但请记住为每个子模块选择正确的分支名称。)

\n

一般来说\xe2\x80\x94这不是特定于您的错误\xe2\x80\x94我建议避免浅层子模块:它们往往不能很好地工作。如果您确实想使用它们,请使用相当大的深度:例如,50、100 或更多。根据您自己的存储库和需求进行调整。--depth 1(如果您解决其他问题,您当前的设置确实允许。)

\n

Long:这可能是 Git 中的一个错误

\n

注意,下面的分析是基于源码的。我还没有实际测试过这个,所以我可能错过了一些东西。不过,这些原则都是合理的。

\n

所有子模块始终是“sha commits”,或者可能是“sha1”commits\xe2\x80\x94Git 过去这么称呼它们,但现在称它们为 OID,其中 OID 代表对象 ID。未来的 Git 可能会使用 SHA-2。1 因此,如果希望避免 TLA 综合症,“OID”或“哈希 ID” 2肯定是一个更好的术语。所以让我这样说:所有子模块都使用 OID / hash-ID 提交。

\n

“所有子模块始终使用 OID / 哈希 ID”是什么意思?嗯,这是浅层子模块的关键之一。浅层子模块本质上是脆弱的,让 Git 在所有情况下正确使用它们是很棘手的。此主张:

\n
\n

子模块通过分支名称而不是 sha 提交号进行跟踪。

\n
\n

在一个重要方面是错误的。无论您多么努力,子模块\xe2\x80\x94或更准确地说,子模块提交\xe2\x80\x94都会通过哈希ID进行跟踪。

\n

现在,子模块中确实存在涉及克隆和获取的分支名称。当您--shallow与子模块一起使用时,这可能变得非常重要,因为大多数服务器不允许 fetch-by-hash-ID (旁注,2021 年 1 月:这正在改变,因为 Git 中的一些新功能已经需要它\xe2\x80\x94GitHub允许通过 ID\xe2\x80\x94 获取,因此随着时间的推移,这种情况应该会有所改善)。您选择的深度 \xe2\x80\x94 和单个分支名称(因为--depth意味着--single-branch\xe2\x80\x94)必须足够深才能到达超级项目 Git 选择的提交。

\n

如果您使用子模块覆盖Git 的按哈希 ID 提交跟踪,则可以绕过一个脆弱性问题。这就是您正在做的事情,但是您遇到了一个错误。

\n
\n

1这不是很有趣吗?Git 在很大程度上依赖于每个提交都有一个唯一的 OID;引入新的 OID 命名空间,以便每个 Git 都有两个OID,每个 OID 在其命名空间内都是唯一的,这意味着提交不一定具有适当的OID。所有协议都变得更加复杂:任何仅支持旧方案的 Git 都需要(单个)OID 的 SHA-1 哈希,而任何使用新方案的 Git 都需要 SHA-2 哈希,也许还需要 SHA -1 哈希值给旧的 Git。一旦我们有了对象,我们就可以用它来计算其他哈希值,但如果我们只有两个哈希值之一,那么它必须是正确的。

\n

处理此问题的直接方法是,如果对象存在于使用不同 OID 命名空间的存储库中,则将计算“其他人的哈希值”的负担放在拥有该对象的 Git 上。但 SHA-1 Git 无法更改,因此我们不能使用该方法。负担必须由新的 SHA-2 Git 承担。

\n

2请注意,“SHA”本身是一个 TLA:三个字母的缩写。TLAS 代表 TLA 综合症,是 ETLA:扩展的三个字母缩写词。

\n
\n

超级项目 Git 如何选择子模块 Git 提交?

\n

git submodule命令目前仍然是一个大型 shell 脚本,但其大部分操作使用 C 语言帮助程序。虽然它是一个复杂的 shell 脚本,但其核心是运行:

\n
(cd $path && git $command)\n
Run Code Online (Sandbox Code Playgroud)\n

为了在每个子模块内做事情。是$path子模块的路径,$command也是在该子模块中运行的命令。

\n

不过,这里有一些鸡和蛋的东西,因为最初只是一个空目录:在克隆超级项目之后,还$path没有实际的克隆。克隆之前,任何 Git 命令都不起作用!嗯,除了git clone它本身,什么也没有。

\n

同时,每个超级项目提交都有两项:

\n
    \n
  • 一个.gitmodules文件,列出子模块的名称和任何配置数据,以及在需要时克隆它的说明;和
  • \n
  • 子模块的gitlink
  • \n
\n

gitlink 包含指令:此提交要求将子模块 S 作为提交哈希检出hash-value。在下面一个有趣的地方,我们有机会使用或忽略这个哈希值,但现在请注意,每个提交实际上都在说:我需要一个克隆,在该克隆中,我需要一个特定的提交,通过它的哈希 ID。

\n

克隆子模块存储库

\n

要克隆子模块,我们需要它的 URL。我们将运行:

\n
git clone $url $path\n
Run Code Online (Sandbox Code Playgroud)\n

或者可能:

\n
git clone --depth $N --no-single-branch $url $path\n
Run Code Online (Sandbox Code Playgroud)\n

或类似的。URL 和路径是最重要的部分。它们在文件中.gitmodules,但这不是 Git 想要它们的地方:Git 希望它们位于 Git 存储库的配置文件中。

\n

运行会将git submodule init数据从.gitmodules文件复制到 Git 想要的位置。否则这个命令实际上不会做任何有趣的事情。似乎没有人使用它,因为git submodule update --init每次都会为你这样做。init正如文档所说,存在单独的命令,以便您可以“自定义...子模块位置”(调整 URL)。

\n

运行git submodule update(带或不带--remote--init和/或--depth)将注意到克隆是否存在。它确实需要保存的信息git submodule init,所以如果您还没有这样做git submodule init,您需要选择--init来实现这一点。如果子模块本身丢失\xe2\x80\x94如果超级项目还没有子模块的克隆\xe2\x80\x94git submodule update现在将运行git clone。它实际上是运行的子模块助手git clone;参见第 558 行 以下。,尽管行号无疑会在未来的 Git 版本中发生变化。

\n

请注意以下事项git clone

\n
    \n
  1. --depth如果你使用 ,它会得到一个参数--depth
  2. \n
  3. 如果它确实获得了参数,则默认--depth设置,除非您使用.--single-branch--no-single-branch
  4. \n
  5. 它为子模块创建实际的存储库,但总是被告知,--no-checkout因此它永远不会执行任何提交的初始git checkout操作。
  6. \n
  7. 永远不会得到-b/--branch参数。这让我感到惊讶,而且可能是错误的,但请参阅clone_submodulesubmodule--helper.c代码
  8. \n
\n

现在,将第 2 项与第 4 项结合起来。使用 suggest 进行克隆--depth--single-branch这会将子模块存储库设置为:

\n
remote.origin.fetch=+refs/heads/<name>:refs/remotes/origin/<name>\n
Run Code Online (Sandbox Code Playgroud)\n

作为其预先配置的fetch设置。但是Git 在这里没有提供分支名称,因此默认值是另一个Git(即您正在克隆的 Git)name推荐的分支名称。它不是您在超级项目中自己配置的任何名称。

\n

使用--no-single-branchon line 会强制在没有git submodule update --init模式的情况下进行克隆。这可以让您从所有分支的提示提交中进行提交,并将该行配置为: --single-branch--depthfetch

\n
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*\n
Run Code Online (Sandbox Code Playgroud)\n

这样您的子模块存储库中就有所有分支名称(加上深度 50,或者您指定的深度,可以从这些名称访问提交)。或者,正如我在顶部提到的,git config此时您可以在子模块中使用来修复remote.origin.fetch设置。

\n

检查正确的提交

\n

一旦我们有了克隆,剩下的任务就是git checkout在子模块中运行正确的 或 (其他 Git 命令)。即:

\n
(cd $path; git $command)\n
Run Code Online (Sandbox Code Playgroud)\n

命令,我们现在有了子模块工作树的路径;我们所需要的只是找到一个哈希 ID 并git checkout在该哈希 ID 上运行。

\n

哈希 ID 存储在 gitlink 中。通常,这就是 Git 在这里使用的。不过,使用--remote时,git submodule脚本现在将运行子模块帮助程序来找出“正确的”分支名称。也就是说,子模块助手将找到您配置的名称(如果您配置了),或者使用超级项目的分支名称(如果您没有配置)。

\n

请注意,这是相当晚的:子模块已经被克隆,并且已经将其remote.origin.fetch设置为其他名称。(也许,除非你很幸运:也许其他 Git 推荐的名称与你在此处使用的名称相同--remote。但可能不是。)

\n

这是来自我上面链接的源代码行的有趣的代码:

\n
# enter here with:\n#    $sm_path: set to the submodule path\n#    $sha1: set to the hash from the gitlink\n#    $just_cloned: a flag set to 1 if we just ran `git clone`\n\nif test $just_cloned -eq 1\nthen\n    subsha1=    # i.e., set this to the empty string\nelse\n    subsha1=(...find hash ID that is currently checked out...)\nfi\n\nif test -n "$remote"\nthen\n    branch=(...find the branch you want...)\n    ... fetch_in_submodule "$sm_path" $depth ...\n    sha1=(...use git rev-parse to find the hash ID for origin/$branch...)\nfi\n\nif test "$subsha1" != "$sha1" || test -n "$force"; then\n    ... do stuff to the submodule ...\n    ... in this case, git checkout -q $sha1 ...\nfi\n
Run Code Online (Sandbox Code Playgroud)\n

(我省略了一些不相关的部分,并$(...)用其功能的描述替换了一些部分,而不是实际的代码)。

\n

所有这些工作的目的是:

\n
    \n
  • 子模块存储库通常处于分离的 HEAD模式,并通过哈希 ID 签出一个特定的提交。即使它在分支上处于另一种模式\xe2\x80\x94,或者附加 HEAD 模式以使用明显相反的\xe2\x80\x94,它仍然会签出一个特定的提交哈希 ID。

    \n

    (这里唯一真正的例外是在初始克隆之后,实际上没有检查任何内容。)

    \n
  • \n
  • 代码subsha1部分会找出哪个哈希 ID。

    \n
  • \n
  • 代码的其余部分确定检查哪个哈希 ID。使用该--remote选项,您可以告诉超级项目 Git:完全忽略 gitlink 设置。所有其他选项都使用 gitlink 设置,其中任何一个都可能导致--depth 1.

    \n
  • \n
\n

您的错误消息在这里被触发

\n

你用来--remote告诉你的超级项目 Git:忽略 gitlink hash ID。这使用branch=(...)然后sha1=(...)分配来覆盖 gitlink 哈希 ID。

\n

sha1=作业实际上就是这段代码:

\n
sha1=$(sanitize_submodule_env; cd "$sm_path" &&\n    git rev-parse --verify "${remote_name}/${branch}") ||\ndie "$(eval_gettext "Unable to find current \\${remote_name}/\\${branch} revision in submodule path \'\\$sm_path\'")"\n
Run Code Online (Sandbox Code Playgroud)\n

在这里您将看到收到的错误消息:

\n
\n
Unable to find current origin/version/3.2.0-era revision in submodule path \'...\'\n
Run Code Online (Sandbox Code Playgroud)\n
\n

现在,一个git fetch命令应该(人们可能希望)已获取由分支名称命名的提交。如果它确实获取了该提交,人们会希望它更新了正确的远程跟踪名称,在本例中为. version/3.2.0-eraorigin/version/3.2.0-era

\n

git fetch然而,唯一的候选命令是由以下命令调用的命令:

\n
fetch_in_submodule "$sm_path" $depth\n
Run Code Online (Sandbox Code Playgroud)\n

该命令git fetch使用--depth您提供的参数运行。它提供任何分支名称! 其他 fetch_in_submodule调用,特别是第 628 行上的这个调用,提供原始哈希 ID(仍然不是分支名称),但这仅--depth在您给出参数时提供参数。

\n

如果没有 refspec,例如分支名称,git fetch origin则仅获取remote.origin.fetch. 这是另一个Git 的名称。

\n

如果fetch=设置无法获取所需的分支名称\xe2\x80\x94并且使用单分支克隆,则很可能\xe2\x80\x94git fetch不会获取我们想要的提交,并且随后git rev-parse将远程跟踪名称转换origin/$branch为哈希 ID 将失败。这就是您看到的错误。

\n

我不会尝试准确地说出错误在哪里\xe2\x80\x94,因此,如何修复它,在设置正确的配置和/或使用git fetch适当的参数发出\xe2\x80\x94方面,但显然当前的Git设置不适合您的情况。但最终,Git 在这里尝试做的是找到正确的 OID,或者在这种情况下找不到它。

\n

找到git rev-parse origin/version/3.2.0-era适合您的特定情况的正确 OID\xe2\x80\x94\xe2\x80\x94 之后,您的超级项目 Git 将运行:

\n
(cd $path; git checkout $hash)\n
Run Code Online (Sandbox Code Playgroud)\n

在子模块中,留下一个独立的 HEAD 指向您通过分支名称请求的相同哈希 ID。当您解决问题时,您处于这种 commit-by-OID detached-HEAD 模式。摆脱它的唯一方法是手动:你必须自己(cd $path; git checkout branch-name)操作。

\n

如果您使用git submodule update --remote\xe2\x80\x94 如果您的 CI 系统构建了超级项目存储库所说的构建提交,而不是依赖于其他人控制下的某个分支名称\xe2 \x80\x94a 浅克隆必须git fetch. 这就是深度问题的脆弱之处:N 应该有多深?没有正确的答案,这就是为什么你必须自己设置它。

\n

如果您将originGit 配置为uploadpack.allowReachableSHA1InWantuploadpack.allowAnySHA1InWant设置为true,则git fetch-by-hash-ID 可以获取任意提交,从而允许--depth 1工作,但您需要控制Git 存储库才能执行此操作(并参阅文档有关这些的origin警告)设置)。git config

\n