并行执行“git submodule foreach”

10
有没有办法并行执行git submodule foreach命令,类似于--jobs 8参数与git submodule update一起使用的方式?
例如,我们工作中涉及近200个子组件(子模块)的一个项目,我们大量使用foreach命令对它们进行操作。我想加速这个过程。
PS:如果解决方案涉及脚本,请注意我在Windows上工作,并且大部分时间都在使用git-bash。

没有内置的方法,你必须使用外部工具,比如foreach_submodule.js或者git-deep。附注:我没有尝试过它们,不知道它们是否有效。 - phd
@phd 真遗憾没有内置的方法,我猜是因为保证操作之间的互斥性太复杂了,所以更安全的做法是不提供它。我会看看那些包,谢谢! - cbuchart
3个回答

4
我建议您使用基于Python等跨平台解释型语言的解决方案。

进程启动器


首先,您需要定义一个类来管理要启动的命令进程。

class PFSProcess(object):
    def __init__(self, submodule, path, cmd):
        self.__submodule = submodule
        self.__path = path
        self.__cmd = cmd
        self.__output = None
        self.__p = None

    def run(self):
        self.__output = "\n\n" + self.__submodule + "\n"
        self.__p = subprocess.Popen(self.__cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True,
                             cwd=os.path.join(self.__path, self.__submodule))
        self.__output += self.__p.communicate()[0].decode('utf-8')
        if self.__p.communicate()[1]:
            self.__output += self.__p.communicate()[1].decode('utf-8')
        print(self.__output)

多线程


下一步是实现多线程执行。Python中包含了一个非常强大的线程库,你可以通过导入以下包来使用它:

import threading

在创建线程之前,您需要创建一个工作器,即每个线程要调用的函数:

def worker(submodule_list, path, command):
    for submodule in submodule_list:
        PFSProcess(submodule, path, command).run()

如您所见,工作人员会收到一个子模块列表。为了清晰起见,并且因为它超出了我们的范围,我建议您查看 .gitmodules,从那里您可以读取文件并生成子模块列表。


<提示>

作为基本指南,您可以在每个子模块中找到以下行:

path = relative_path/project

为此,您可以使用以下正则表达式:

用于此目的,您可以使用此正则表达式:

'path ?= ?([A-za-z0-9-_]+)(\/[A-za-z0-9-_]+)*([A-za-z0-9-_])'

如果正则表达式匹配成功,您可以在同一行中使用以下代码获取相对路径:

' ([A-za-z0-9-_]+)(\/[A-za-z0-9-_]+)*([A-za-z0-9-_])'

请注意,最后一个正则表达式返回相对路径并在第一个位置带有空格字符。

</提示>


然后将子模块列表分成尽可能多的块,以适应所需的作业数:

num_jobs = 8

i = 0
for submodule in submodules:
    submodule_list[i % num_jobs].append(submodule)
    i += 1

最后将每个数据块(任务)分发给每个线程,并等待所有线程完成:
for i in range(num_jobs):
    t = threading.Thread(target=worker, args=(list_submodule_list[i], self.args.path, self.args.command,))
    self.__threads.append(t)
    t.start()

for i in range(num_jobs):
    self.__threads[i].join()

显然,我已经介绍了基本概念,但您可以访问GitHub上的parallel_foreach_submodule(PFS)项目以获取完整实现。请参考parallel_foreach_submodule (PFS)

1
非常感谢!我已经在使用它了!顺便问一下,i += 1 不应该在 for submodule in submodules 循环内部吗? - cbuchart

3
一个简单的、仅使用bash的解决方案是这样做(将<command with your command>替换为您的命令):
IFS=$'\n'
for DIR in $(git submodule foreach -q sh -c pwd); do
    cd $DIR && <command> &
done
wait

作为一个通用指令(创建名为“ git-foreach-parallel ”的文件):
#!/bin/bash

if [ -z "$1" ]; then
    echo "Missing Command" >&2
    exit 1
fi

IFS=$'\n'
for DIR in $(git submodule foreach -q sh -c pwd); do
    cd "$DIR" && "$@" &
done
wait

也许我没有正确阅读,但我无法在任何时候看到并行化。 - cbuchart
2
请注意命令末尾有 & 符号,它使命令并行执行。 - Ivan Ivanyuk
哦!非常感谢,我需要一副新眼镜。 - cbuchart

0
如果有人正在寻找一种纯Bash的方法来实现它(而不是在Docker容器中安装Python之类的东西),这就是帮助我的方法。
用法示例。
bash git-submodule-foreach-parallel.sh "git fetch && git checkout master"

bash git-submodule-foreach-parallel.sh "git fetch && git pull"

bash git-submodule-foreach-parallel.sh "git fetch && git push"

COMMAND="git clean -dfx -e \"**/.idea\""
# Running command in parent repository
eval "$COMMAND"
# Running command in submodules
bash git-submodule-foreach-parallel.sh "$COMMAND"

git-submodule-foreach-parallel.sh(使用示例运行它)

#!/bin/bash

if [ -z "$1" ]; then
    echo "Missing Command" >&2
    exit 1
fi

COMMAND="$@"

IFS=$'\n'
for DIR in $(git submodule foreach --recursive -q sh -c pwd); do
    printf "\nStarted running command \"${COMMAND}\" in directory \"${DIR}\"\n" \
    && \
    cd "$DIR" \
    && \
    eval "$COMMAND" \
    && \
    printf "Finished running command \"${COMMAND}\" in directory \"${DIR}\"\n" \
    &
done
wait

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