Git подмодуль init async

Когда я запускаю git submodule update --init в первый раз в проектах с большим количеством подмодулей, это обычно занимает много времени, потому что большинство подмодулей хранится на медленных публичных серверах.

Есть ли возможность инициализировать подмодули асинхронно?

Ответы

Ответ 1

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

Ответ 2

Что касается Git 2.8, вы можете это

git submodule update --init --jobs 4

где 4 - количество подмодулей, загружаемых параллельно.

Ответ 3

Обновление в январе 2016 года:

С Git 2.8 (Q1 2016) вы сможете получить подмодули параллельно (!) с помощью git fetch --recurse-submodules -j2.
См. "Как ускорить/распараллелить загрузки подмодулей Git с помощью Git clone --recursive?"


Оригинальный ответ в середине 2013 года

Вы можете попробовать:

  • для инициализации первых всех подмодулей:

    git подмодуль init

Затем синтаксис foreach:

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

Если "&" применяется ко всей строке (вместо части "git submodule update --recursive -- $path" ), вы можете вызвать script, который сделает обновление в фоновом режиме.

git submodule foreach git_submodule_update

Ответ 4

Это также можно сделать в 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_output и try/except subprocess.CalledProcessError, который является более чистым, чем спящий режим, добавленный в метод @Karmazzin. Обновленный метод может выглядеть так:

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

При этом мне удалось запустить init/update из 700 подмодулей во время сборки Travis без необходимости ограничивать размер пула процессов. Я часто вижу несколько зацепов, пойманных таким образом (~ 3 макс.).