在所有子目录下运行git pull

341

如何在不进入每个仓库的根目录的情况下,从它们共享的父目录中更新多个 git 仓库?以下是我拥有的这些独立的 git 仓库(不是子模块):

/plugins/cms
/plugins/admin
/plugins/chart

我希望能够一次性更新它们,或者至少简化我的当前工作流程:

cd ~/plugins/admin
git pull origin master
cd ../chart
git pull

等等。


17
“find -name .git -execdir git pull ;”有什么问题? - jthill
git do pull 是什么意思? - Frode Akselsen
相同的问题已经为 hg mercurial 回答 - Serge Stroobandt
15
find . -name .git -print -execdir git pull \; 是正确的。-print 将会输出当前目录。 - DawnSong
另外,随着 Git 2.30(2020年第四季度)的发布,还有一个新的 git for-each-repo 命令,请参见 此处 - VonC
https://github.com/earwig/git-repo-updater — “gitup 是一个同时更新多个 git 仓库的工具。它足够智能,可以处理多个远程仓库、脏工作目录、分叉的本地分支、分离的 HEAD 等等。它最初是为管理大量项目并处理间歇性互联网访问而创建的。gitup 应该可以在 macOS、Linux 和 Windows 上运行。您应该安装最新版本的 git 和 Python 2.7 或 Python 3。” - sideshowbarker
16个回答

435

从父目录运行以下命令,在这种情况下是plugins

find . -type d -depth 1 -exec git --git-dir={}/.git --work-tree=$PWD/{} pull origin master \;

澄清一下:

  • find . 搜索当前目录
  • -type d 查找目录,而不是文件
  • -depth 1 最多深入一个子目录
  • -exec {} \; 对每个查找运行自定义命令
  • git --git-dir={}/.git --work-tree=$PWD/{} pull git 拉取各个目录

为了玩转 find 命令,建议在 -exec 后使用 echo 预览,例如:

find . -type d -depth 1 -exec echo git --git-dir={}/.git --work-tree=$PWD/{} status \;

注意:如果 -depth 1选项不可用,尝试使用-mindepth 1 -maxdepth 1


22
查找:警告:您在非选项参数“-type”之后指定了“-depth”选项,但选项不是位置相关的(-depth 影响在其之前和之后指定的测试)。请在其他参数之前指定选项。 - Yuri Astrakhan
40
我使用了find . -maxdepth 1 -type d -print -execdir git --git-dir={}/.git --work-tree=$PWD/{} pull origin master \;这个命令来输出在执行pull之前文件夹的名称,以消除警告并只对子文件夹进行pull操作。 - Rystraum
5
将'pull origin master'替换为fetch origin master:master会告诉git明确地使用origin的master分支更新你的'master'分支。这样做不会执行合并,如果这样做,将丢失对'master'分支的任何提交。 - ThorSummoner
104
自 Git 1.8.5 起,可以使用 -C 选项替代 --git-dir 和 --work-tree。有关详细信息,请参见此问题。我正在使用 find . -mindepth 1 -maxdepth 1 -type d -print -exec git -C {} pull \; - Zarat
5
@ZsoltSzilagy,如@Rystraum所提到的,您可以使用“-maxdepth 1”而不是“-depth 1”。 - jamiebarrow
显示剩余9条评论

300
ls | xargs -I{} git -C {} pull

要并行地执行它:

ls | xargs -P10 -I{} git -C {} pull

63
不错!我已将其作为别名加入到我的.gitconfig文件中:all = "!f() { ls | xargs -I{} git -C {} $1; }; f"现在我可以执行git all pullgit all "checkout master"等命令。 - borisdiakur
11
稍作整理,将递归搜索所有目录以查找仅限于git仓库,并在命令中去除颜色(以防您已经为ls设置了别名)ls -R --directory --color=never */.git | sed 's/\/.git//' | xargs -P10 -I{} git -C {} pull - cchamberlain
9
我将一些答案组合起来,创建了这个在macOS上过滤包含.git文件夹的文件夹,并允许您运行任意命令(如“ git all fetch --prune”)的git命令:git config --global alias.all '!f() { ls -R -d */.git | sed 's,\/.git,,' | xargs -P10 -I{} git -C {} $1; }; f' - Courtney Faulkner
4
@AWrightIV实际上,ls -R -d */.git返回的是当前文件夹中包含.git目录的子文件夹列表。这样,当我运行类似于git all fetch的命令时,它只会执行对包含.git文件夹的子文件夹的操作。这是对原始问题的回答,但它尝试更加有效率,不假设所有子文件夹都是git存储库。 - Courtney Faulkner
16
对于borisdiakur命令的轻微改进,以避免在“。”上运行,并在每个瞬间打印它正在运行的目录列表: git config --global alias.all '!f() { ls -R -d */.git | xargs -I{} bash -c "echo {} && git -C {}/../ $1"; }; f' - Jose Luis Blanco
显示剩余7条评论

124

比Leo的解决方案略微低科技:

for i in */.git; do ( echo $i; cd $i/..; git pull; ); done

这将更新你的工作目录中的所有Git仓库。无需显式列出它们的名称(“cms”,“admin”,“chart”)。"cd"命令仅影响子Shell(使用括号生成)。


1
正是我所需要的。 - GameSalutes
你鼓舞了我。非常感谢。 - DawnSong
1
我喜欢这个解决方案,因为它只拉取了作为git仓库的子目录,谢谢! - Cory Robinson
3
交替使用 git -Cfor i in */.git; do git -C $i pull; done - Jon-Eric
有没有办法自动输入每个子文件夹仓库的用户名和密码? - palacetrading
显示剩余6条评论

58

实际上,如果你不知道子文件夹是否有git仓库,最好让find为你获取仓库:

find . -type d -name .git -exec git --git-dir={} --work-tree=$PWD/{}/.. pull origin master \;

PowerShell 的等效命令为:

Get-ChildItem -Recurse -Directory -Hidden -Filter .git | ForEach-Object { & git --git-dir="$($_.FullName)" --work-tree="$(Split-Path $_.FullName -Parent)" pull origin master }

1
这在我的 macOS 上可以运行。 - Jin Kwon
确保你所有的 git 仓库都在 master 分支上,然后按照这样的方式执行。否则你可能会无意中将 master 合并到当前分支。 - Jacob Archambault
1
非常感谢提供 PowerShell 版本,仍然可在 PS 7.10 中使用。 - Jean-Sébastien Gervais
对于 PS 2.0,以下命令可以正常工作:gci | where {$_.Attributes -match'Directory'} | foreach { write-host $_.fullname; push-location $_; & git pull; & cd ..} - jlo
注意 - 我找不到一个名为 hidden 的参数,但似乎 Force 可以工作,例如 Get-ChildItem -Path "C:\Repos" -Recurse -Directory -Force -Filter ".git" -Depth 1 - sommmen
有没有办法让它根据使用的是主分支还是主要分支来执行操作? - ss7

33

我使用这个:

find . -name ".git" -type d | sed 's/\/.git//' |  xargs -P10 -I{} git -C {} pull

通用:更新当前目录下所有Git仓库。


3
您可以使用 --git-dir 替代 -C 直接传递 .git 路径,从而省去了 sed 替换的步骤!find . -name ".git" -type d | xargs -P10 -I{} git --git-dir={} pull - Chris
1
在没有 --work-tree 的情况下使用 --git-dir 是很危险的,这就是为什么创建了 -C 快捷方式。我之前因此搞乱了我的主目录。我建议只需执行 find . -maxdepth 8 -type d -name .git | xargs -P8 -I{} git -C {}/../ fetch --all 即可。 - jaksco

16

前五个答案对我都无效,而且问题是关于目录的。

这个方法有效:

for d in *; do pushd $d && git pull && popd; done

1
对于Windows系统,请参见我的答案:https://dev59.com/9WUo5IYBdhLWcg3w3yqi#51016478。它与上面非常相似。 - Shital Shah
接受的答案曾经对我有用,但在去年某个时候它停止了工作。最终我决定寻找另一个解决方案,并找到了你的答案。感谢分享。它完美地运行。 - Barry
不需要推送和拉取,只需使用git的-C选项。 - Travis Stevens
另一个选项是使用子shell:for d in *; do (cd $d && git pull --ff-only); done。这种方法比-C更具普适性,适用于没有此选项的程序。请注意,在自动更新的情况下,--ff-only是很好的选择;如果使用本地vim包管理,则可以使用它来更新vim插件。 - nponeccop

14

只要cms、admin和chart都是仓库的一部分,这个应该会自动发生。

可能的问题是每个插件都是git子模块。

运行git help submodule获取更多信息。

编辑

在bash中执行:

cd plugins
for f in cms admin chart
do 
  cd $f && git pull origin master && cd ..
done

不好意思,你误解了。这些目录中的每一个都是一个单独的 git 存储库。/plugins 不是一个存储库。 - Petah
啊,我的错。我马上给你一个 Bash 的解决方案。 - Jamie Wong
这就是了。如果你想返回到父目录,只需再运行另一个 cd .. 即可。 - Jamie Wong
或者使用 pushdpopd 命令,或将一组命令放在子 shell 中(当子 shell 退出时,您将留在原始目录中)。(cd dir; for ... done) - Dennis Williamson
很好,这个可行。现在有没有办法将ssh密码传递给bash脚本,并让脚本将其传递给git/ssh,而不是每次提示我输入密码? - Petah
5
出于好奇,为什么你不使用SSH密钥呢? - Jamie Wong

12

假设所有子目录都是 Git 仓库,则最紧凑的方法为:

ls | parallel git -C {} pull

1
找不到命令“parallel”。参考资料? - balanceglove2
@LeonTepe 这个工具通常包含在 moreutils 包中。 - cmcginty

12

mr实用程序(又名myrepos)为此问题提供了一个杰出的解决方案。使用您喜欢的软件包管理器安装它,或者直接从GitHub 获取mr脚本并将其放在$ HOME / bin或其他PATH上的位置。然后,cd到这些仓库共享的父级plugins文件夹,并创建一个基本的.mrconfig文件,其内容类似于以下内容(根据需要调整URL):

# File: .mrconfig
[cms]
checkout = git clone 'https://<username>@github.com/<username>/cms' 'cms'

[admin]
checkout = git clone 'https://<username>@github.com/<username>/admin' 'admin'

[chart]
checkout = git clone 'https://<username>@github.com/<username>/chart' 'chart'

之后,您可以从顶层plugins文件夹中运行mr up以拉取每个存储库的更新。 (请注意,如果目标工作副本尚不存在,则这也将执行初始克隆。)您可以执行的其他命令包括mr stmr pushmr logmr diff等,运行mr help查看可能性。还有一个mr run命令作为一个通行证,允许您访问不直接受mr支持的VCS命令(例如,mr run git tag STAGING_081220015)。您甚至可以创建自己的定制命令,执行针对所有存储库的任意位shell脚本!

mr是处理多个存储库的极其有用的工具。由于plugins文件夹在您的主目录中,因此您可能还对vcsh感兴趣。与mr一起,它提供了管理所有配置文件的强大机制。请参阅Thomas Ferris Nicolaisen的博客文章进行概述。


11

这是我的简单的构造:

  • 使用Python展示当前路径,方便且可靠,详情请见如何获取文件完整路径?
  • 直接查找.git子文件夹,减少在非Git子文件夹中执行Git命令的可能性
  • 去除一些find的警告

如下所示:

find . \
    -maxdepth 2 -type d \
    -name ".git" \
    -execdir python -c 'import os; print(os.path.abspath("."))' \; \
    -execdir git pull \;
当然,你可以在find命令中添加其他带有额外-execdir选项的git命令,例如显示分支:
find . \
    -maxdepth 2 -type d \
    -name ".git" \
    -execdir python -c 'import os; print(os.path.abspath("."))' \; \
    -execdir git branch \;
    -execdir git pull \;

1
不确定为什么这个答案会有负评。在我看来,这是最有帮助的,因为我可以去到更大的深度,而不会一直遇到非 Git 存储库。我使用它在我的“开发者”存储库中运行 git gc 来清理所有存储库。 - Harshita Gupta
1
我也喜欢这个答案,因为它易于阅读并且非常容易添加多个命令。 - Top Cat

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