Git子模块异步初始化

14
当我在一个拥有很多子模块的项目上首次运行git submodule update --init时,通常会花费很长时间,因为大多数子模块存储在缓慢的公共服务器上。是否有可能异步初始化子模块?

你说的异步是什么意思?这个命令可以吗:git submodule update --init & - rodrigo
1
我的意思是每个子模块在单独的进程中初始化的方法。 - Leksat
1
那是并行而不是顺序执行吗? - Bleeding Fingers
是的。子模块的并行克隆。 - Leksat
可能是如何使用git clone --recursive加速/并行下载git子模块?的重复问题。 - Ciro Santilli OurBigBook.com
显示剩余2条评论
4个回答

5

Linux:

cat .gitmodules | grep -Po '".*"' | sed 's/.\(.\+\).$/\1/' | while sleep 0.1 && read line; do git submodule update --init $line & done

Mac:

cat .gitmodules | grep -o '".*"' | cut -d '"' -f 2 | while sleep 0.1 && read line; do git submodule update --init $line & done

这对我有用!但由于许多后台进程,它会跳过一些子模块。因此,最终我必须再次运行git submodule update --init。 - Leksat
使用 wait 操作符,例如:https://dev59.com/sGox5IYBdhLWcg3wWjHQ - Karmazzin
以前出现了错误 error: could not lock config file .git/config: File exists,因为并行进程正在写入配置文件。 现在我已经在 while 循环中添加了 sleep 0.1,它完美地工作了。 - Leksat
这个很好用!我只有一个小问题,就是在克隆完成之前,shell 就退出了。为了解决这个问题,我修改了一下,使用了 xargs:`cat .gitmodules | grep -Po '".*"' | sed 's/.\(.\+\).$/\1/' | while sleep 0.1 && read line; do echo $line; done | xargs -L1 -P4 git submodule update --init;`这样可以防止 shell 在所有克隆完成之前退出,并且将并行进程的数量限制为 4。 - dmi_

5
自Git 2.8起,您可以这样做:
git submodule update --init --jobs 4

其中4是并行下载的子模块数目。


2

2016年1月更新:

使用Git 2.8(2016年第一季度),您将能够通过git fetch --recurse-submodules -j2并行地获取子模块。
请参见"如何使用git clone --recursive加速/并行下载git子模块?"


原始回答于2013年中期

您可以尝试:

  • 首先初始化所有子模块:

    git submodule init

然后,使用foreach语法

git submodule foreach git submodule update --recursive -- $path &

如果 '&' 应用于整行 (而不仅仅是 'git submodule update --recursive -- $path' 部分),那么您可以调用一个脚本,在后台进行更新。

git submodule foreach git_submodule_update

1
OP说是“第一次”,这意味着子模块尚未初始化以便运行foreach命令,对吧? - Bleeding Fingers
你可能也想加上递归标志。 - Bleeding Fingers
@hus787 首先尝试执行 git submodule init(这不是一个昂贵的操作),然后更新 'foreach' 子模块。 - VonC
@hus787 我已经添加了递归选项。 - VonC

1

这也可以用Python完成。在Python 3中(因为我们现在是2015年...),我们可以使用类似于以下的东西:

#!/usr/bin/env python3

import os
import re
import subprocess
import sys
from functools import partial
from multiprocessing import Pool

def list_submodules(path):
    gitmodules = open(os.path.join(path, ".gitmodules"), 'r')
    matches = re.findall("path = ([\w\-_\/]+)", gitmodules.read())
    gitmodules.close()
    return matches


def update_submodule(name, path):
    cmd = ["git", "-C", path, "submodule", "update", "--init", name]
    return subprocess.call(cmd, shell=False)


if __name__ == '__main__':
    if len(sys.argv) != 2:
        sys.exit(2)
    root_path = sys.argv[1]

    p = Pool()
    p.map(partial(update_submodule, path=root_path), list_submodules(root_path))

这种方法可能比 @Karmazzin 给出的一行代码更安全(因为那个方法只是不受控制地生成进程而没有任何关于生成进程数量的控制),但它仍然遵循相同的逻辑:读取 .gitmodules,然后使用进程池(最大进程数也可以设置)生成运行适当 git 命令的多个进程。克隆存储库的路径需要作为参数传递。在一个包含约 700 个子模块的存储库上进行了广泛测试。

请注意,在子模块初始化的情况下,每个进程都会尝试写入 .git/config,并且可能会发生锁定问题:

错误:无法锁定配置文件 .git/config: 文件已存在

注册子模块路径 '...' 的 URL 失败

这可以通过 subprocess.check_outputtry/except subprocess.CalledProcessError 块捕获,这比添加到 @Karmazzin 方法中的 sleep 更清晰。更新后的方法可以像这样:

def update_submodule(name, path):
    cmd = ["git", "-C", path, "submodule", "update", "--init", name]
    while True:
        try:
            subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=False)
            return
        except subprocess.CalledProcessError as e:
            if b"could not lock config file .git/config: File exists" in e.stderr:
                continue
            else:
                raise e

通过这种方式,在Travis构建期间成功运行了700个子模块的init/update,无需限制进程池的大小。我经常会看到一些锁定被捕获(最多3个)。

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