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

How to shallow pull submodule that is tracked by branch name

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

git submodule update --remote --init 

但这并不肤浅。好像把所有东西都拉出来然后切换到 branch

git submodule update --remote --init --depth 1

这行不通,它失败了:

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'

关于浅层子模块有一个不同的问题,但是我没有看到它适用于分支,只适用于 sha 提交

TL;DR

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

其他需要知道的事情:

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

  • 可能需要分两步完成。我将在下面将其显示为 two-step 序列。我知道这段代码在 Git 1.7 和当前(2.26 左右)Git 之间发生了很大变化,我希望 two-step 序列也适用于大多数旧版本。

两步是:

N=...        # set your depth here, or expand it in the two commands
git submodule update --init --depth $N --no-single-branch
git submodule update --remote --depth $N

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

另一个选项可能是允许 single-branch 模式但修复子模块中的 fetch refspec。这需要三个步骤——嗯,三个单独的 Git 命令,无论如何:

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

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

总的来说——这不是你的错误所特有的——我建议避免浅层子模块:它们往往不能很好地工作。如果您真的想使用它们,请使用 pretty-big 深度:例如,50、100 或更多。根据您自己的存储库和需求调整它。 (您当前的设置确实允许 --depth 1,前提是您解决了其他问题。)

长:这可能是 Git

中的错误

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

所有 子模块总是“sha 提交”,或者可能是“sha1”提交——Git 用于称呼它们那,但现在称它们为 OID,其中 OID 代表对象 ID。未来 Git 可能会使用 SHA-2。1 因此,如果希望避免 TLA 综合症,请使用“OID”或“哈希 ID”,2当然是一个更好的术语。所以让我这样说吧:所有子模块都使用 OID / hash-ID commits.

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

The submodule is tracked by a branch name and not by a sha commit number.

在一个重要方面是错误的。无论您多么努力,子模块——或者更准确地说,子模块提交——都会被哈希 ID 跟踪。

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

如果您覆盖 Git 的hash-ID 跟踪子模块提交跟踪,您可以绕过一个脆弱性问题。这就是你正在做的,但你遇到了一个错误。


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

处理这个问题的直接方法是将计算“其他人的散列”的负担放在具有对象的 Git 上,如果对象存在于使用不同的存储库中OID 命名空间。但是 SHA-1 Gits 不能改变,所以我们不能使用那个方法。负担必须放在新的 SHA-2 Gits.

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


一个超级项目如何 Git ch设置一个子模块 Git commit?

git submodule 命令是 currently still a big shell script,但它的大部分操作使用 C 语言帮助程序。虽然它是一个复杂的 shell 脚本,但它的核心是 运行:

(cd $path && git $command)

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

虽然这里有一些 chicken-and-egg 东西,因为 $path 最初只是一个空目录:在克隆超级项目之后还没有实际的 clone .在 克隆之前,任何 Git 命令都不起作用!好吧,除了 git clone 本身,什么也没有,就是

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

  • 一个.gitmodules文件,列出子模块的名称和任何配置数据,以及克隆它的说明if/when;和
  • a gitlink 用于子模块。

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

克隆子模块存储库

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

git clone $url $path

或者也许:

git clone --depth $N --no-single-branch $url $path

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

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

运行git submodule update(有无--remote--init、and/or--depth)会注意克隆是否存在。它确实需要 git submodule init 会保存的信息,所以如果您还没有完成 git submodule init,您需要 --init 选项来实现它。如果子模块本身 丢失——如果超级项目还没有子模块的克隆——git submodule update 现在将 运行 git clone。它实际上是 运行s git clone 的子模块助手;参见 line 558 ff.,尽管行号无疑会在未来的 Git 版本中改变。

注意这些 git clone:

  1. 如果您使用 --depth,它会得到一个 --depth 参数。
  2. 如果它得到一个 --depth 参数,它默认设置 --single-branch,除非你使用 --no-single-branch.
  3. 它为子模块创建实际的存储库,但它总是被告知 --no-checkout 所以它从不执行任何提交的初始 git checkout
  4. 永远不会得到 -b / --branch 参数 。这让我感到惊讶,而且可能是错误的,但请参阅 clone_submodule in the submodule--helper.c source.

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

remote.origin.fetch=+refs/heads/<name>:refs/remotes/origin/<name>

作为其 pre-configured fetch 设置。但是 Git 没有在这里提供分支名称 所以默认的 name 是 [=254 推荐的=]other Git,即您正在克隆的 Git。这不是你自己配置的任何名字,在你的超级项目中。

git submodule update --init 行上使用 --no-single-branch 会强制进行克隆 --single-branch 模式。这让你 --depth 所有 分支的提示提交中提交,并将 fetch 行配置为:

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

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

检查正确的提交

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

(cd $path; git $command)

命令,我们现在有了子模块的路径work-tree;我们只需要找到一个哈希 ID 和 运行 git checkout 在该哈希 ID 上。

哈希 ID 存储在 gitlink 中。通常,这就是 Git 在这里使用的内容。但是,使用 --remotegit submodule 脚本现在将 运行 子模块助手找出“正确的”分支名称。也就是说,子模块助手将找到您配置的名称,如果您配置了一个,或者使用超级项目的分支名称,如果您没有。

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

这是我在上面链接的那些源代码行中有趣的代码:

# enter here with:
#    $sm_path: set to the submodule path
#    $sha1: set to the hash from the gitlink
#    $just_cloned: a flag set to 1 if we just ran `git clone`

if test $just_cloned -eq 1
then
    subsha1=    # i.e., set this to the empty string
else
    subsha1=(...find hash ID that is currently checked out...)
fi

if test -n "$remote"
then
    branch=(...find the branch you want...)
    ... fetch_in_submodule "$sm_path" $depth ...
    sha1=(...use git rev-parse to find the hash ID for origin/$branch...)
fi

if test "$subsha1" != "$sha1" || test -n "$force"; then
    ... do stuff to the submodule ...
    ... in this case, git checkout -q $sha1 ...
fi

(我省略了一些不相关的部分并替换了一些 $(...) 部分,其中描述了它们所做的事情,而不是实际代码)。

所有这些工作的目的是:

  • 子模块存储库通常处于 分离 HEAD 模式,通过哈希 ID 检出一个特定的提交。即使它处于另一种模式——在一个分支上,或者使用明显相反的附加 HEAD 模式——它仍然有一个特定的提交哈希 ID 被检出。

    (这里唯一真正的例外是在初始克隆之后,实际上什么都没有签出。)

  • subsha1 代码部分计算出这是哪个哈希 ID。

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

    出现问题

这里触发了你的错误信息

您正在使用 --remote 告诉您的超级项目 Git:忽略 gitlink 哈希 ID。这使用 branch=(...) 然后 sha1=(...) 赋值来覆盖 gitlink 哈希 ID。

那个sha1=赋值字面上就是这段代码:

sha1=$(sanitize_submodule_env; cd "$sm_path" &&
    git rev-parse --verify "${remote_name}/${branch}") ||
die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '$sm_path'")"

在这里您会发现收到的错误消息:

Unable to find current origin/version/3.2.0-era revision in submodule path '...'

现在,一个 git fetch 命令 should,一个人可能希望,已经获取了 commit 命名的 branch-nameversion/3.2.0-era。如果它确实获取了该提交,人们会希望它会更新正确的 remote-tracking 名称,在本例中为 origin/version/3.2.0-era.

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

fetch_in_submodule "$sm_path" $depth

此命令 运行s git fetch 带有您提供的 --depth 参数。它提供任何分支名称! 其他 fetch_in_submodule 调用,特别是 this one on line 628,提供一个 原始散列 ID(仍然不是分支名称),但是这个仅在您提供 --depth 参数时提供。

没有 refspec,例如分支名称,git fetch origin 只获取 remote.origin.fetch 中配置的任何内容。这是来自 other Git.

的名称

如果 fetch= 设置 没有 获取所需的分支名称 - 并且使用 single-branch 克隆,这很可能在这里 - git fetch 不会获取我们想要的提交,随后 git rev-parse 将 remote-tracking 名称 origin/$branch 转换为哈希 ID 将失败。这就是您看到的错误。

我不会尝试说出 bug 的确切位置——因此,如何修复它,根据设置正确的配置 and/or 发出带有适当参数的 git fetch——在这里,但显然 current Git 设置不适用于您的情况。不过,最终 Git 在这里尝试做的是 找到正确的 OID,或者在这种情况下,找不到它。

找到正确的 OID——针对你的特定情况使用 git rev-parse origin/version/3.2.0-era——你的超级项目 Git 将 运行:

(cd $path; git checkout $hash)

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

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

如果将 origin Git 配置为 uploadpack.allowReachableSHA1InWantuploadpack.allowAnySHA1InWant 设置为 true,则 git fetch-by-hash-ID 可以获取任意提交,允许 --depth 1 工作,但您需要控制 origin Git 存储库才能执行此操作(请参阅 [=127= 中的注意事项) ] 关于这些设置)。