如何制作浅 Git 子模块?

有没有可能有浅层子模块?我有一个包含几个子模块的超级项目,每个子模块都有很长的历史,所以它拖累了所有的历史,这是不必要的。

我只找到 这个悬而未决的问题

我应该只用 黑进 git 子模块来实现这个吗?

78559 次浏览

通过阅读 git-submodule“ source”,看起来 git submodule add可以处理已经存在存储库的子模块。这样的话..。

$ git clone $remote1 $repo
$ cd $repo
$ git clone --depth 5 $remotesub1 $sub1
$ git submodule add $remotesub1 $sub1
#repeat as necessary...

您需要确保所需的提交在子模块 repo 中,因此请确保设置了适当的—— deep。

编辑: 您可能能够摆脱多个手动子模块克隆,然后进行一次更新:

$ git clone $remote1 $repo
$ cd $repo
$ git clone --depth 5 $remotesub1 $sub1
#repeat as necessary...
$ git submodule update

子模块的规范位置是远程的吗?如果是这样,你可以克隆它们一次吗?换句话说,仅仅因为频繁的子模块(re)克隆浪费了带宽,您就希望使用浅克隆吗?

如果您希望通过浅克隆来节省本地磁盘空间,那么 Ryan Graham 的答案似乎是一个不错的选择。手动克隆存储库,使它们变得很浅。如果您认为它有用,可以调整 git submodule以支持它。向 名单发送一封电子邮件询问它(关于实现它的建议,关于接口的建议等等)。在我看来,那里的人们非常支持那些真心希望以建设性的方式增强 Git 的潜在贡献者。

如果你可以对每个子模块做一个完整的克隆(加上稍后的获取来保持它们的最新状态) ,你可以尝试使用 git submodule update--reference选项(在 Git 1.6.4和更高版本中)来引用本地对象存储(例如使用典型子模块存储库的 --mirror克隆,然后在你的子模块中使用 --reference来指向这些本地克隆)。在使用 --reference之前,请务必阅读有关 git clone --reference/git clone --shared的资料。引用镜像的唯一可能的问题是,它们是否会最终获取非快进更新(尽管您可以启用 relog 并扩展它们的到期窗口,以帮助保留任何可能导致问题的放弃提交)。你不应该有任何问题,只要

  • 不执行任何本地子模块提交,或者
  • 规范存储库可能发布的任何非快进提交都不是本地子模块提交的祖先,或者
  • 您非常注意保持本地子模块的提交重新基于任何可能在规范子模块存储库中发布的非快进(non-fast-forward)。

如果你使用这种方法,并且有可能在工作树中带有本地子模块提交,那么创建一个自动化系统可能是一个好主意,这个系统可以确保被检出子模块引用的关键对象不会悬挂在镜像存储库中(如果有的话,将它们复制到需要它们的存储库中)。

并且,正如 git clone手册所说,如果您不理解这些含义,请不要使用 --reference

# Full clone (mirror), done once.
git clone --mirror $sub1_url $path_to_mirrors/$sub1_name.git
git clone --mirror $sub2_url $path_to_mirrors/$sub2_name.git


# Reference the full clones any time you initialize a submodule
git clone $super_url super
cd super
git submodule update --init --reference $path_to_mirrors/$sub1_name.git $sub1_path_in_super
git submodule update --init --reference $path_to_mirrors/$sub2_name.git $sub2_path_in_super


# To avoid extra packs in each of the superprojects' submodules,
#   update the mirror clones before any pull/merge in super-projects.
for p in $path_to_mirrors/*.git; do GIT_DIR="$p" git fetch; done


cd super
git pull             # merges in new versions of submodules
git submodule update # update sub refs, checkout new versions,
#   but no download since they reference the updated mirrors

或者,您可以不使用 --reference,而是通过使用本地镜像作为子模块的源,将镜像克隆与 git clone的默认硬链接功能结合使用。在新的超级项目克隆中,执行 git submodule init,编辑 .git/config中的子模块 URL 以指向本地镜像,然后执行 git submodule update。您需要重新克隆任何现有的签出子模块以获得硬链接。通过只向镜像下载一次,然后从这些镜像获取本地内容到签出子模块,可以节省带宽。硬链接可以节省磁盘空间(尽管读取可能会累积并在多个检出子模块对象存储的实例之间重复; 您可以定期从镜像中重新克隆检出子模块,以重新获得硬链接节省的磁盘空间)。

Ryan 的回答之后,我想出了这个简单的脚本,它遍历所有的子模块,并对它们进行浅层克隆:

#!/bin/bash
git submodule init
for i in $(git submodule | sed -e 's/.* //'); do
spath=$(git config -f .gitmodules --get submodule.$i.path)
surl=$(git config -f .gitmodules --get submodule.$i.url)
git clone --depth 1 $surl $spath
done
git submodule update

新的 Git1.8.4(二零一三年七月):

git submodule update”可以选择性地克隆子模块存储库。

(Git 2.102016年第三季度允许用 git config -f .gitmodules submodule.<name>.shallow true记录。
看这个答案的结尾)

提交275cd184d52b5b81cb89e4ec33e540fb2ae61c1f:

--depth选项添加到“ git submodule”的 Add 和 update 命令中,然后将其传递给 clone 命令。当子模块非常庞大并且除了最新的提交之外您对其他任何事情都不感兴趣时,这是非常有用的。

添加了测试,并对缩进进行了一些调整,以符合“子模块更新可以处理 pwd 中的符号链接”上的测试文件的其余部分。

签字: Fredrik Gustafsson <iveqy@iveqy.com>
Acked-by: Jens Lehmann <Jens.Lehmann@web.de>编剧: Jens Lehmann

这意味着这个方法可行:

# add shallow submodule
git submodule add --depth 1 <repo-url> <path>
git config -f .gitmodules submodule.<path>.shallow true


# later unshallow
git config -f .gitmodules submodule.<path>.shallow false
git submodule update <path>

这些命令可以按任何顺序运行。git submodule命令执行实际的克隆(这次使用深度1)。而且 git config命令使这个选项对于以后将递归地克隆回购的其他人来说是永久的。

作为一个例子,假设您有一个回购 https://github.com/foo/bar,并且您希望在 path/to/submodule的回购中添加 https://github.com/lorem/ipsum作为子模块。这些命令可能看起来像下面这样:

git submodule add --depth 1 git@github.com:lorem/ipsum.git path/to/submodule
git config -f .gitmodules submodule.path/to/submodule.shallow true

下面的结果也是一样的(顺序相反) :

git config -f .gitmodules submodule.path/to/submodule.shallow true
git submodule add --depth 1 git@github.com:lorem/ipsum.git path/to/submodule

下一次有人运行 git clone --recursive git@github.com:foo/bar.git时,它将引入 https://github.com/foo/bar的整个历史,但是它只会像预期的那样对子模块进行浅克隆。

配合:

--depth

此选项对 addupdate命令有效。
创建一个“浅”克隆,其历史记录被截断为指定的修订次数。


阿特维曼补充说:

据我所知,这个选项不适用于不能非常紧密地跟踪 master的子模块。如果您设置深度1,那么只有当您想要的子模块提交是最新的主模块时,submodule update才能成功。否则你会得到“ fatal: reference is not a tree.

那倒是真的。
也就是说,直到 git 2.8(2016年3月)。有了2.8,submodule update --depth又多了一次成功的机会,即使 SHA1可以直接从远程回购头部之一到达。

犯法,第43季,第31集(2016年2月24日) by 斯蒂芬 · 贝勒(stefanbeller)
帮助者: 滨野俊男(gitster)
(由 朱尼奥 · C · 哈马诺 gitster于2016年2月26日在 提交9671a76合并)

子模块: 通过直接获取 sha1来更努力地获取所需的 sha1

在 Gerrit,当检查一个子模块的更新时,一个常见的检查实践是在本地下载并挑选修补程序来测试它。
然而,当在本地测试它时,‘ git submodule update’可能无法获取正确的子模块 sha1,因为子模块中相应的提交还不是项目历史的一部分,而只是一个提议的更改。

如果 $sha1不是默认提取的一部分,我们尝试直接 提取 $sha1。然而,有些服务器不支持 sha1的直接读取,这导致 git-fetch很快失败。
我们可能会在这里失败,因为仍然缺少的 sha1将导致稍后在检出阶段的失败,所以在这里失败是我们所能得到的最好结果。


MVG 指出 在评论中犯法,第43季,第31集(git 2.9,2016年2月)

在我看来,犯法,第43季,第31集通过 SHA1 id 请求缺少的提交,因此服务器上的 uploadpack.allowReachableSHA1InWantuploadpack.allowTipSHA1InWant设置可能会影响这种方法是否有效。
我编写了一个 今天就发布到 Git 名单上,指出如何使用浅层子模块以便在某些场景中更好地工作,也就是说,如果提交也是一个标记。
让我们拭目以待。

我想这就是为什么 fb43e31将特定 SHA1的提取作为默认分支提取之后的回退的原因。
尽管如此,在“—— deep 1”的情况下,我认为提前终止是有意义的: 如果列出的参考文献中没有一个与请求的参考文献匹配,并且服务器不支持 SHA1的请求,那么获取任何东西都没有意义,因为我们无论如何都不能满足子模块的要求。


2016年8月更新(三年后)

使用 Git 2.10(Q32016) ,您将能够

 git config -f .gitmodules submodule.<name>.shallow true

更多信息请参见“ 没有额外重量的 Git 子模块”。


Git 2.13(2017年第二季度)确实在 提交8d3047c中添加了 塞巴斯蒂安 · 舒伯特(sschuberth)(2017年4月19日)。
(由 塞巴斯蒂安 · 舒伯特 sschuberth于2017年4月20日在 提交8d3047c合并)

这个子模块的克隆将作为浅克隆 (历史深度为1)执行

然而,西罗 · 桑蒂利增加了 在评论中(和详细信息 在他的回答中)

.gitmodules上的 shallow = true仅在使用 --recurse-submodules时影响远程的 HEAD 跟踪的引用,即使目标提交被分支指向,即使您将 branch = mybranch也放在 .gitmodules上。


Git 2.20(2018年第4季度)改进了子模块支持,当工作树中缺少 .gitmodules文件时,子模块支持已经更新为从 HEAD:.gitmodules的 blob 读取。

参见 犯下2b1257e76e9bdc(2018年10月25日)和 提交 b5c259f提交23dd8f5提交 b2faad4承诺2502ffc提交996df4d提交 d1b13df犯45f5ef3犯下2b1257e0(2018年10月5日)。
(由 朱尼奥 · C · 哈马诺 gitster于2018年11月13日在 犯下 abb4824合并)

.gitmodules不在工作树中时,支持读取 .gitmodules

当工作树中的 .gitmodules文件不可用时,请尝试 使用来自索引和当前分支的内容。
这涵盖了文件是存储库的一部分,但是对于某些情况 未签出的原因,例如因为一个稀疏的签出。

这使得至少使用“ git submodule”命令成为可能 其中 gitmodules配置文件没有完全填充 工作树。

写入 .gitmodules仍然需要签出文件, 所以在打电话给 config_set_in_gitmodules_file_gently之前检查一下。

git-submodule.sh::cmd_add()中也添加一个类似的检查,以预测当 .gitmodules不能安全地编写时“ git submodule add”命令的最终失败; 这可以防止命令以虚假的状态离开存储库(例如,子模块存储库被克隆,但 .gitmodules没有更新,因为 config_set_in_gitmodules_file_gently失败)。

此外,由于 config_from_gitmodules()现在访问全局对象 存储,则有必要保护调用该函数的所有代码路径 防止对全局对象存储区的并发访问。
目前这只发生在 builtin/grep.c::grep_submodules()中,因此调用 在调用涉及 config_from_gitmodules()的代码之前。

注意: 有一个罕见的情况下,这个新特性不工作 正确的: 嵌套的子模块没有 .gitmodules在他们的工作树。


注意: Git 2.24(2019年第四季度)修复了克隆一个浅表子模块时可能出现的 Segfault 错误。

提交 ddb3c85(2019年9月30日) by 阿里 · 乌特库 · 赛伦(auselen)
(由 朱尼奥 · C · 哈马诺 gitster于2019年10月9日在 犯下678a9ca合并)


Git 2.25(Q12020) ,澄清了 git submodule update文档。

承认吧(2019年11月24日) by 菲利普 · 布莱恩(phil-blain)
(由 朱尼奥 · C · 哈马诺 gitster于2019年12月5日在 提交 E61045合并)

doc : 提到“ git 子模块更新”获取缺少的提交

帮助者: Junio C Hamano
帮助者: Johannes Schindelin
签字人: 菲利普 · 布莱恩

git submodule更新”如果没有找到超级项目中记录的 SHA-1,将从子模块远程获取新的提交。这在文档中没有提到。


警告: 对于 Git 2.25(Q12020) ,“ git clone --recurse-submodules”和备用对象存储之间的交互设计得很糟糕。

文档和代码已经被教导在用户看到故障时提供更清晰的建议。

犯下第四季,第57集10c64a0(2019年12月2日) by 谭(jhowtan)
(由 朱尼奥 · C · 哈马诺 gitster于2019年12月10日在 犯下5dd1d59合并)

submodule--helper : 关于致命的交替错误的建议

签名: Jonathan Tan
编剧: Jeff King

当递归地克隆一个超级项目时,它的 .gitmodules中定义了一些浅模块,然后用“ --reference=<path>”重新克隆,就会发生一个错误。例如:

git clone --recurse-submodules --branch=master -j8 \
  https://android.googlesource.com/platform/superproject \
master
git clone --recurse-submodules --branch=master -j8 \
https://android.googlesource.com/platform/superproject \
--reference master master2

未能符合以下条件:

fatal: submodule '<snip>' cannot add alternate: reference repository
'<snip>' is shallow

如果不能添加从超级项目的替代计算得到的替代计算,无论是在这种情况下还是在另一种情况下,建议配置“ submodule.alternateErrorStrategy”配置选项并在克隆时使用“ --reference-if-able”而不是“ --reference”。

详情见:

对于 Git 2.25(Q12020) ,“ Git 克隆——递归子模块”和备用对象存储之间的交互设计得很糟糕。

解释子模块

签名: Jonathan Tan
编剧: Jeff King

Commit 31224cbdc7(“ clone: 递归和引用选项触发子模块替换”,2016-08-17,Git v2.11.0-rc0—— 第一批中列出的 合并)教会 Git 在超级项目中支持配置选项“ submodule.alternateLocation”和“ submodule.alternateErrorStrategy”。

如果“ submodule.alternateLocation”在超级项目中被配置为“ superproject”,那么无论何时克隆该超级项目的子模块,它都会从超级项目的 $GIT_DIR/objects/info/alternates中计算该子模块的类似备用路径,并引用它。

submodule.alternateErrorStrategy”选项决定如果不能引用该备选项会发生什么情况。
但是,当选项没有设置为“ die”时(可以在 31224cbdc7中的测试中看到) ,克隆是否继续进行,就好像没有指定替代选项一样,这一点并不清楚。
因此,相应地记录它。

配置子模块文档现在包括:

submodule.alternateErrorStrategy::

指定如何处理通过 submodule.alternateLocation计算的子模块的备用错误。
可能的值是 ignoreinfodie
默认值是 die
请注意,如果设置为 ignoreinfo,以及 如果计算出的替代方案出错,克隆将继续执行,就好像没有指定替代方案一样


注意: “ git submodule update --quiet(< a href = “ https://git-scm.com/docs/git-submodule # Document/git-submodule.txt-——static”rel = “ noReferrer”> man )没有将安静选项传播到底层的 git fetch(< a href = “ https://git-scm.com/docs/git-get”rel = “ norefrer”> man ),这已经用 Git 2.32(Q22021)进行了纠正。

每天62小时(2021年4月30日) by Nicholas Clark (nwc10)
(由 朱尼奥 · C · 哈马诺 gitster于2021年5月11日在 提交74339f8合并)

submodule update : 带有“ --quiet”的默认读取

由 Nicholas Clark 签名

诸如

$ git submodule update --quiet --init --depth=1

涉及浅克隆时,调用 shell 函数 fetch_in_submodule,,该函数反过来调用 git fetch
在那里传递 --quiet选项。

我创建了一个稍微不同的版本,当它不在最前沿运行时,不是所有的项目都这样做。标准的子模块添加不起作用,上面的脚本也不起作用。因此,我为标记引用添加了一个散列查找,如果它没有,那么它将返回到完整的克隆。

#!/bin/bash
git submodule init
git submodule | while read hash name junk; do
spath=$(git config -f .gitmodules --get submodule.$name.path)
surl=$(git config -f .gitmodules --get submodule.$name.url)
sbr=$(git ls-remote --tags $surl | sed -r "/${hash:1}/ s|^.*tags/([^^]+).*\$|\1|p;d")
if [ -z $sbr ]; then
git clone $surl $spath
else
git clone -b $sbr --depth 1 --single-branch $surl $spath
fi
done
git submodule update

参考 如何用特定的修订/变更集克隆 git 存储库?

我已经编写了一个简单的脚本,当您的子模块引用远离主模块时,这个脚本没有问题

git submodule foreach --recursive 'git rev-parse HEAD | xargs -I {} git fetch origin {} && git reset --hard FETCH_HEAD'

此语句将获取子模块的引用版本。

它很快,但是你不能在子模块上提交你的编辑(你必须在 https://stackoverflow.com/a/17937889/3156509之前取出它)

全文:

#!/bin/bash
git submodule init
git submodule foreach --recursive 'git rev-parse HEAD | xargs -I {} git fetch origin {} && git reset --hard FETCH_HEAD'
git submodule update --recursive

Git 2.9.0支持子模块浅层克隆,所以现在你可以直接调用:

git clone url://to/source/repository --recursive --shallow-submodules

从 Git 2.14.1开始的 bug/意外/恼人的行为总结

  1. 如果远程子模块的 HEAD指向所需的提交,即使分支指向目标提交,甚至将 branch = mybranch也放在 .gitmodules上,那么 .gitmodules中的 shallow = true也只会影响 git clone --recurse-submodules

    本地测试脚本 在 GitHub 2017-11上同样的行为,其中 HEAD由默认的分支回购设置控制:

    git clone --recurse-submodules https://github.com/cirosantilli/test-shallow-submodule-top-branch-shallow
    cd test-shallow-submodule-top-branch-shallow/mod
    git log
    # Multiple commits, not shallow.
    
  2. git clone --recurse-submodules --shallow-submodules fails if the commit is neither referenced by a branch or tag with a message: error: Server does not allow request for unadvertised object.

    Local test script. Same behaviour on GitHub:

    git clone --recurse-submodules --shallow-submodules https://github.com/cirosantilli/test-shallow-submodule-top-sha
    # error
    

    我还在邮件列表上问到: https://marc.info/?l=git&m=151863590026582&w=2,答案是:

    从理论上讲,这应该很容易。 :)

    不幸的是,在实践中没有这么多。这是因为克隆只会获得 树枝的最新尖端(通常是主枝)。克隆中没有机械装置 指定所需的确切 sha1。

    有线协议支持询问确切的 sha1,因此应该涵盖这一点。 (注意: 只有当服务器操作员启用 AllowReachableSHA1InWant,github 没有 AFAICT)

    Git-get 允许获取任意的 sha1,因此作为一种解决方案,您可以运行一个获取 在递归克隆之后使用“ git 子模块更新” 取代最初的克隆。

待办测试: allowReachableSHA1InWant

子模块的浅克隆是完美的,因为它们在特定的修订/变更集中进行快照。从网站上下载一个 zip 很容易,所以我尝试了一个脚本。

#!/bin/bash
git submodule deinit --all -f
for value in $(git submodule | perl -pe 's/.*(\w{40})\s([^\s]+).*/\1:\2/'); do
mysha=${value%:*}
mysub=${value#*:}
myurl=$(grep -A2 -Pi "path = $mysub" .gitmodules | grep -Pio '(?<=url =).*/[^.]+')
mydir=$(dirname $mysub)
wget $myurl/archive/$mysha.zip
unzip $mysha.zip -d $mydir
test -d $mysub && rm -rf $mysub
mv $mydir/*-$mysha $mysub
rm $mysha.zip
done
git submodule init

git submodule deinit --all -f清除允许脚本可重用的子模块树。

git submodule检索40个字符 sha1,后跟与 .gitmodules中相同的路径。我使用 perl 连接这些信息,用冒号分隔,然后使用变量转换将值分隔为 myshamysub

这些是关键的键,因为我们需要 sha1下载和路径来关联 url。Gitmodule.

给定一个典型的子模块条目:

[submodule "label"]
path = localpath
url = https://github.com/repository.git

path =上的 myurl键然后查看2行以获得值。这种方法可能不一致,需要细化。Url grep 通过匹配最后一个 /和任何直到 .的内容来去除所有剩余的 .git类型引用。

mydirmysub减去最后一个 /name,后者将由通向子模块名称的目录生成。

接下来是一个 wget,其格式为可下载的 zip 归档 URL。这在将来可能会改变。

将文件解压缩到 mydir,这将是子模块路径中指定的子目录。合成的文件夹将是 url-sha1的最后一个元素。

检查子模块路径中指定的子目录是否存在,并删除它以允许重命名提取的文件夹。

mv将包含 sha1的解压缩文件夹重命名为其正确的子模块路径。

删除下载的压缩文件。

子模块启动

这更多的是一个在制品的概念证明,而不是一个解决方案。当它工作时,结果是在指定的变更集中的子模块的浅克隆。

如果存储库将一个子模块重新放置到另一个提交,则重新运行脚本以进行更新。

这样的脚本只有在源项目的非协作性本地构建时才会有用。

我需要一个解决方案,浅克隆子模块时,我不能克隆的主要回购。 基于以上一个解决方案:

#!/bin/bash
git submodule init
for i in $(git submodule | sed -e 's/.* //'); do
git submodule update --init --depth 1 -- $i
done