如何创建浅层次的Git子模块?

198

是否可以有浅层次的子模块?我有一个超级项目,其中包含几个子模块,每个子模块都有很长的历史记录,因此拖拽所有历史记录会使其变得不必要地大。

我找到的只有这个未回答的帖子

我应该只是修改 git-submodule 以实现这一点吗?


1
现在"git submodule add/update"可以浅克隆子模块仓库了!请参见我的回答 - VonC
10个回答

205

简而言之;

git clone --recurse-submodules --shallow-submodules

(但是请注意 Ciro Santilli 答案 中的警告)
或者:记录子模块应该被浅克隆:

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

这意味着下一个 git clone --recurse-submodules 命令将浅克隆子模块 '<name>'(深度为 1),即使没有使用 --shallow-submodules 参数。
以下是关于浅克隆时 git submodule/git clone 的演变过程,从2013年的Git 1.8.4开始,并持续至今。

在即将发布的Git 1.8.4 (2013年7月)中新增功能:

"git submodule update" 可选择浅克隆子模块仓库。

(而在 git 2.10 Q3 2016 中,可以使用 git config -f .gitmodules submodule.<name>.shallow true 记录此操作。
详见本回答末尾的commit 275cd184d52b5b81cb89e4ec33e540fb2ae61c1f)

在 "git submodule" 的添加和更新命令中添加 --depth 选项,然后传递给克隆命令。当子模块很大且您仅对最新提交感兴趣时,这很有用。 添加了测试,并进行了一些缩进调整,以符合“子模块更新可以处理 pwd 中的符号链接”的测试文件的其余部分。签名:Fredrik Gustafsson <iveqy@iveqy.com>,确认:Jens Lehmann <Jens.Lehmann@web.de>。这意味着这个功能是可行的:
# 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,并且您想将 https://github.com/lorem/ipsum 添加为子模块,在存储库的 path/to/submodule 中。命令可能如下所示:
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

This option is valid for add and update commands.
Create a 'shallow' clone with a history truncated to the specified number of revisions.


atwyman在评论中添加了内容

据我所知,这个选项对于不太密切地跟踪master的子模块是无法使用的。如果你设置深度为1,则submodule update只能成功,如果子模块提交是最新的主提交。否则会出现“fatal:reference is not a tree”的错误

这是真的。
也就是说,在git 2.8(2016年3月)之前,submodule update --depth即使SHA1直接可达到远程库HEADs之一,也将失败。但从2.8开始,submodule update --depth有了更多的成功机会。

查看提交fb43e31 (2016年2月24日) 由Stefan Beller (stefanbeller)完成。
协助者:Junio C Hamano (gitster)
(由Junio C Hamano -- gitster --合并于提交9671a76,2016年2月26日)

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

在审查Gerrit中还更新子模块的更改时,常见的审查做法是下载并本地挑选补丁进行测试。然而,在本地测试时,“git submodule update”可能会失败,因为子模块中的相应提交尚未成为项目历史的一部分,而只是一个建议性的更改。 如果“$sha1”不是默认获取的一部分,则尝试直接获取“$sha1”。但是,有些服务器不支持按sha1直接获取,这会导致“git-fetch”快速失败。我们可以在此处自行失败,因为仍然缺少的sha1会在稍后的检出阶段导致失败,因此在此处失败与我们能够得到的一样好。

MVG指出在评论中提交fb43e31(git 2.9,2016年2月)

我认为commit fb43e31请求缺失的SHA1 id提交,因此服务器上的uploadpack.allowReachableSHA1InWantuploadpack.allowTipSHA1InWant设置可能会影响其是否有效。我今天写了一篇帖子发送到git列表中,指出如何更好地使用浅子模块以适应某些情况,即如果提交也是标签的情况下。让我们拭目以待。我猜这就是为什么fb43e31将针对特定SHA1的提取作为默认分支提取后备的原因。尽管如此,在“--depth 1”的情况下,我认为早点放弃是有意义的:如果没有列出的引用与所请求的引用匹配,并且服务器不支持通过SHA1进行查询,则没有获取任何内容的意义,因为无论如何我们都无法满足子模块要求。

2016年8月更新(3年后)

使用Git 2.10(2016年第3季度),您将能够执行以下操作

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

请查看 "不带额外负担的Git子模块" 以获取更多信息。


Git 2.13(2017年第二季度)添加了commit 8d3047c(于2017年4月19日由Sebastian Schuberth(sschuberth添加)。
(由Sebastian Schuberth -- sschuberth --于2017年4月20日合并到commit 8d3047c中)

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

然而,Ciro Santilli在评论中添加了细节详见他的回答

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


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

请查看提交 2b1257e, 提交 76e9bdc (2018年10月25日),以及提交 b5c259f, 提交 23dd8f5, 提交 b2faad4, 提交 2502ffc, 提交 996df4d, 提交 d1b13df, 提交 45f5ef3, 提交 bcbc780 (2018年10月05日) ,作者为Antonio Ospite (ao2)
(由Junio C Hamano -- gitster --提交 abb4824合并,2018年11月13日)

submodule:支持在工作树中不存在 .gitmodules 文件时读取它

当工作树中没有可用的.gitmodules文件时,尝试使用索引和当前分支的内容。这适用于文件是仓库的一部分但由于某种原因未被检出的情况,例如因为稀疏检出。这使得至少可以使用“git submodule”命令来读取gitmodules配置文件而不完全填充工作树。写入.gitmodules仍然需要检出文件,因此在调用config_set_in_gitmodules_file_gently之前,请检查文件是否已检出。在git-submodule.sh::cmd_add()中添加类似的检查,以预测“git submodule add”命令失败时.gitmodules不安全可写的情况;这可以防止命令使仓库处于虚假状态(例如,子模块仓库已克隆但.gitmodules未更新,因为config_set_in_gitmodules_file_gently失败)。此外,由于config_from_gitmodules()现在访问全局对象存储,因此有必要保护调用该函数的所有代码路径,以防止对全局对象存储的并发访问。目前,这仅会在builtin/grep.c::grep_submodules()中发生,因此在涉及config_from_gitmodules()的代码之前调用grep_read_lock()。注意:存在一种罕见情况,这种新功能尚未正常工作:没有.gitmodules的嵌套子模块在其工作树中。
注意:Git 2.24(2019年第四季度)修复了克隆子模块浅层时可能出现的段错误。
请参见提交记录ddb3c85(由Ali Utku Selen(auselen于2019年9月30日提交。(由Junio C Hamano -- gitster --合并于提交记录678a9ca,2019年10月9日)
Git 2.25 (2020年第一季度)澄清了有关git submodule update文档的内容。
请参见提交记录f0e58b3(由Philippe Blain (phil-blain)于2019年11月24日提交)。 (由Junio C Hamano -- gitster --提交记录ef61045中合并,于2019年12月5日)

doc:提到“git submodule update”会获取丢失的提交

由Junio C Hamano协助
由Johannes Schindelin协助
由Philippe Blain签署

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


警告:在 Git 2.25(2020 年第一季度)中,“git clone --recurse-submodules”与备用对象存储之间的交互设计不当。
文档和代码已经被修改以在用户遇到故障时提供更明确的建议。

请看提交4f3e57e提交10c64a0(2019年12月2日),作者是Jonathan Tan (jhowtan)
(由Junio C Hamano -- gitster --提交5dd1d59中合并,2019年12月10日)

submodule--helper:建议处理致命的备选错误

Signed-off-by: Jonathan Tan(由Jonathan Tan签署)
Acked-by: Jeff King(由Jeff King确认)

When recursively cloning a superproject with some shallow modules defined in its .gitmodules, then recloning with "--reference=<path>", an error occurs. For example:

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

fails with:

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

When a alternate computed from the superproject's alternate cannot be added, whether in this case or another, advise about configuring the "submodule.alternateErrorStrategy" configuration option and using "--reference-if-able" instead of "--reference" when cloning.

以下是详细内容:

在 Git 2.25 (2020 年第一季度) 中,“git clone --recurse-submodules” 和替代对象存储之间的交互设计存在问题。

文档:解释 submodule.alternateErrorStrategy

签名者:Jonathan Tan
认可者:Jeff King

提交31224cbdc7(“clone: recursive and reference option triggers submodule alternates”,2016年8月17日,Git v2.11.0-rc0 -- merge listed in batch #1)教会了Git支持在超级项目上配置选项“submodule.alternateLocation”和“submodule.alternateErrorStrategy”。

如果在超级项目中将“submodule.alternateLocation”配置为“superproject”,则每当克隆该超级项目的子模块时,它会计算该子模块的类似备用路径,并从超级项目的“$GIT_DIR/objects/info/alternates”引用它。 “submodule.alternateErrorStrategy”选项确定如果无法引用该备用项会发生什么情况。但是,当该选项未设置为“die”时(如31224cbdc7中的测试所示),不清楚克隆是否会像没有指定备用项一样进行。因此,请相应地记录文档。配置子模块文档现在包括:
submodule.alternateErrorStrategy::

Specifies how to treat errors with the alternates for a submodule as computed via submodule.alternateLocation.
Possible values are ignore, info, die.
Default is die.
Note that if set to ignore or info, and if there is an error with the computed alternate, the clone proceeds as if no alternate was specified.


注意:"git submodule update --quiet"(man)没有将安静选项传递到底层的 git fetch(man),这已经在 Git 2.32 (Q2 2021) 中得到了纠正。

查看 提交 62af4bd (2021年4月30日) 由 Nicholas Clark (nwc10) 进行。
(合并自 Junio C Hamano -- gitster --提交 74339f8, 2021年5月11日)

submodule update: 使用 "--quiet" 静默底层获取

签名作者:Nicholas Clark

Commands such as

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

involving shallow clones, call the shell function fetch_in_submodule, which in turn invokes git fetch.
Pass the --quiet option onward there.


2
哇,那真是太快了!顺便感谢你的回答。哦,--depth也应该带一个参数;) - bric3
3
在我看来,commit fb43e31 通过SHA1标识来请求缺失的提交,因此服务器上的 uploadpack.allowReachableSHA1InWantuploadpack.allowTipSHA1InWant 设置可能会影响其是否有效。我今天写了一篇帖子发布到git列表中,指出如何更好地使用浅子模块来适用某些情况,特别是如果该提交也是一个标记。让我们等待并观察。 - MvG
2
最近在.gitmodules中添加了浅层选项,那么--depth 1选项是否适用于没有紧密跟踪主分支的分支? - CMCDragonkai
1
@CMCDragonkai 不确定:你可以提出一个新问题让其他人测试一下。 - VonC
3
从这个回答中不清楚目前的做法是什么。另外,不清楚每次有人克隆新副本时是否都需要这些内容,或者这些稀疏子模块设置是否成为引用这些子模块的存储库的一部分(例如,每次新的克隆和子模块更新会导致稀疏子模块检出)。 - Pavel P
显示剩余28条评论

40

Git 2.9.0 支持子模块浅克隆,现在您只需要调用:

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

2
这个选项是最有前途的,但在git 2.14.1上失败了,子模块提交没有被任何分支或标签跟踪:https://dev59.com/k3I95IYBdhLWcg3wzRXv#47374702 - Ciro Santilli OurBigBook.com
谢谢,我已经在本地进行了测试,没有使用服务器,并且在GitHub上进行了测试,但是我无法更新 :-) - Ciro Santilli OurBigBook.com
1
我使用git 2.20时遇到了同样的问题,当子模块不在分支的最新提交时它就无法工作。 - Zitrax

17

参考 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

每个子模块都会出现“fatal: reference is not a tree: 88fb67b07621dfed054d8d75fd50672fb26349df”的错误。 - knocte
@knocte,你看过https://dev59.com/k3I95IYBdhLWcg3wzRXv#2169914?noredirect=1#comment25262609_2166716吗? - Mauricio Scheffer
1
@knocte :我在2010年写下了我的答案。事情已经发生了变化。你不能期望每个人都去维护他们所有的答案。我已经将当前有效的答案标记为已接受。 - Mauricio Scheffer
14
这就是我停止在 Stackoverflow 上贡献的原因之一。人们有这些不切实际的期望。维护我1637个答案中的每一个需要全职工作。还有评论,我想我也得维护它们吧?看看日期,那就是它们的意义所在。如果你阅读了一个使用 ArrayList 而非 List 的 .NET 博客(文章)并且发表于2002年,你会使用它吗?你会要求作者更新他的帖子吗?同样的原则适用于这里。 - Mauricio Scheffer
1
s/statusquo/progress/ - knocte
显示剩余3条评论

9

Git 2.14.1版本存在的错误/意外/烦人行为总结

  1. shallow = true in .gitmodules only affects git clone --recurse-submodules if the HEAD of the remote submodule points to the required commit, even if the target commit is pointed to by a branch, and even if you put branch = mybranch on the .gitmodules as well.

    Local test script. Same behaviour on GitHub 2017-11, where HEAD is controlled by the default branch repo setting:

    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
    

    I also asked on the mailing list: https://marc.info/?l=git&m=151863590026582&w=2 and the reply was:

    In theory this should be easy. :)

    In practice not so much, unfortunately. This is because cloning will just obtain the latest tip of a branch (usually master). There is no mechanism in clone to specify the exact sha1 that is wanted.

    The wire protocol supports for asking exact sha1s, so that should be covered. (Caveat: it only works if the server operator enables uploadpack.allowReachableSHA1InWant which github has not AFAICT)

    git-fetch allows to fetch arbitrary sha1, so as a workaround you can run a fetch after the recursive clone by using "git submodule update" as that will use fetches after the initial clone.

TODO test: allowReachableSHA1InWant.


似乎没有简单的方法可以检出子模块的分离 HEAD 提交哈希,并让下游用户 git clone --recursive 仅获取该特定提交。 - CMCDragonkai

8

阅读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...

您需要确保所需的提交已包含在子模块存储库中,请确保设置适当的--depth。

编辑:您可以通过多次手动子模块克隆,然后进行单个更新来实现。

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

6
从 Git 1.8.0 版本开始,您不能再在一个仓库里克隆另一个仓库了。因此,这个解决方案不再可行。 - Bohr

2

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

我编写了一个简单的脚本,当您的子模块引用离开主分支时,它没有问题。

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

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

它很快,但你不能提交对子模块的编辑(你必须在提交前将其取回完整版本 https://dev59.com/6mw15IYBdhLWcg3wJ4UR#17937889)。

完整版:

#!/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

2
你的子模块的规范位置是远程的吗?如果是,你是否愿意克隆它们一次?换句话说,你是否想要浅克隆只是因为你正在遭受频繁的子模块(重新)克隆所浪费的带宽?
如果你想要浅克隆来节省本地磁盘空间,那么 Ryan Graham 的回答似乎是一个不错的方法。手动克隆这些仓库,使它们成为浅克隆。如果你认为有用,可以适应 git submodule 来支持它。发送电子邮件到列表询问(关于实现建议、界面建议等)。在我看来,那里的人非常支持那些真诚地想以建设性方式增强 Git 的潜在贡献者。
如果您能接受对每个子模块进行一次完整的克隆(以及后续的获取以保持其最新状态),您可以尝试使用 git submodule update--reference 选项(它在 Git 1.6.4 及更高版本中可用)来引用本地对象存储(例如,制作规范子模块仓库的 --mirror 克隆,然后在子模块中使用 --reference 指向这些本地克隆)。但是,在使用 --reference 之前,请务必阅读有关 git clone --reference/git clone --shared 的内容。引用镜像可能唯一的问题是,如果它们最终获取了非快进更新(虽然您可以启用 reflogs 并扩展其过期时间窗口,以帮助保留任何可能导致问题的废弃提交),则不会有任何问题,只要:
  • 您不进行任何本地子模块提交,或者
  • 规范仓库可能发布的任何非快进提交所留下的任何提交都不是您本地子模块提交的祖先,或者
  • 您勤于使您本地子模块提交重新基于规范子模块仓库发布的任何非快进提交。
如果您使用类似的方法,并且有可能在您的工作树中承载本地子模块提交,那么最好创建一个自动化系统,确保被检出的子模块引用的关键对象不会悬空在镜像仓库中(如果发现任何这样的情况,则将其复制到需要它们的仓库中)。
并且,就像 `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

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

1
我创建了一个稍微不同的版本,适用于没有运行在最前沿的情况下,而并非所有项目都是如此。标准子模块添加和上面的脚本都不起作用。因此,我为标签引用添加了哈希查找,如果没有找到,则回退到完全克隆。
#!/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 

0

浅克隆子模块非常完美,因为它们快照在特定的修订/变更集上。从网站下载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来下载,需要路径来关联.gitmodules中的url

给定典型的子模块条目:

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

myurl 键在 path = 后面的 2 行中查找值。这种方法可能不一致,需要改进。url grep 通过匹配最后一个 / 和任何一个 . 来剥离任何剩余的 .git 类型的引用。

mydirmysub 减去最后一个 /name,这将是子模块名称前导的目录。

接下来是一个可下载 zip 归档文件 url 的 wget 格式。这可能会在未来发生变化。

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

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

mv 重命名包含我们 sha1 的提取文件夹到其正确的子模块路径。

删除已下载的 zip 文件。

子模块初始化。

这更像是一个概念验证的工作正在进行,而不是一个解决方案。当它起作用时,结果是在指定的变更集上浅克隆子模块。

如果存储库将子模块重新定位到不同的提交,请重新运行脚本以进行更新。

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


-1

我需要一种解决方案,可以在无法对主存储库进行克隆的情况下浅克隆子模块。 基于上面的一个解决方案:

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

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