以下bash脚本在扫描.git目录时速度很慢,因为它会查看每个目录。如果我有一个大型存储库的集合,则需要很长时间才能查找每个目录,寻找.git。一旦找到.git目录,它会更快地修剪repos中的目录。关于如何做到这一点的任何想法,还是有另一种方法来编写一个完成同样事情的bash脚本?
#!/bin/bash
# Update all git directories below current directory or specified directory
HIGHLIGHT="\e[01;34m"
NORMAL='\e[00m'
DIR=.
if [ "$1" != "" ]; then DIR=$1; fi
cd $DIR>/dev/null; echo -e "${HIGHLIGHT}Scanning ${PWD}${NORMAL}"; cd ->/dev/null
for d in `find . -name .git -type d`; do
cd $d/.. > /dev/null
echo -e "\n${HIGHLIGHT}Updating `pwd`$NORMAL"
git pull
cd - > /dev/null
done
具体来说,您将如何使用这些选项?对于这个问题,你不能假设repos的集合都在同一个目录中;它们可能位于嵌套目录中。
top
repo1
dirA
dirB
dirC
repo1
答案 0 :(得分:35)
查看Dennis在这篇文章中关于find的-prune选项的答案:
How to use '-prune' option of 'find' in sh?
find . -name .git -type d -prune
会加快速度,因为find不会进入.git目录,但它仍然会进入git存储库,寻找其他.git文件夹。这可能是一项代价高昂的行动。
如果有某种查找前瞻修剪机制会有什么好处,如果文件夹中有一个名为.git的子文件夹,那么修剪该文件夹......
那就是说,我认为你的瓶颈在于网络操作'git pull',而不是在find命令中,正如其他人在评论中发布的那样。
答案 1 :(得分:11)
这是一个优化的解决方案:
#!/bin/bash
# Update all git directories below current directory or specified directory
# Skips directories that contain a file called .ignore
HIGHLIGHT="\e[01;34m"
NORMAL='\e[00m'
function update {
local d="$1"
if [ -d "$d" ]; then
if [ -e "$d/.ignore" ]; then
echo -e "\n${HIGHLIGHT}Ignoring $d${NORMAL}"
else
cd $d > /dev/null
if [ -d ".git" ]; then
echo -e "\n${HIGHLIGHT}Updating `pwd`$NORMAL"
git pull
else
scan *
fi
cd .. > /dev/null
fi
fi
#echo "Exiting update: pwd=`pwd`"
}
function scan {
#echo "`pwd`"
for x in $*; do
update "$x"
done
}
if [ "$1" != "" ]; then cd $1 > /dev/null; fi
echo -e "${HIGHLIGHT}Scanning ${PWD}${NORMAL}"
scan *
答案 2 :(得分:7)
我已花时间在您的问题中复制粘贴脚本,并将其与您自己的答案与脚本进行比较。这里有一些有趣的结果:
请注意:
git pull
echo
停用了.ignore
bash
解决方案中的> /dev/null
文件测试。pwd
。-prune
次来电。find
明显缺少find
示例bash
示例shopt
解决方案上添加了一个测试,不遵循sym链接以避免循环并且表现为查找解决方案。*
以允许find
扩展为点缀目录名称,以匹配#!/bin/bash
find . -name .git -type d -prune | while read d; do
cd $d/..
echo "$PWD >" git pull
cd $OLDPWD
done
解决方案的功能。因此,我们正在比较基于查找的解决方案:
#!/bin/bash
shopt -s dotglob
update() {
for d in "$@"; do
test -d "$d" -a \! -L "$d" || continue
cd "$d"
if [ -d ".git" ]; then
echo "$PWD >" git pull
else
update *
fi
cd ..
done
}
update *
使用 bash shell构建解决方案:
function
注意:builtins(for
和*
)对启动进程的MAX_ARGS OS限制不敏感。所以find
在非常大的目录上不会收支平衡。
解决方案之间的技术差异:
基于查找的解决方案使用C函数来爬行存储库,它:
chdir
命令加载新进程。chdir
并返回。chdir
一次,在bash部分使用一次。基于bash的解决方案使用内置(所以近C实现,但解释)来抓取存储库,请注意:
chdir
个级别。/bin/bash
一次查看和执行命令。解决方案之间的实际速度结果:
我有一个git存储库的工作开发集合,我在其上启动了脚本:
我必须承认,我还没有准备好从bash内置中看到这样的胜利。它成了
在分析正在进行的事情之后,更加明显和正常。如果你将shell从/bin/sh
更改为shopt
(你必须注释掉find . -type d \( -exec /usr/bin/test -d "{}/.git" -a "{}" != "." \; -print -prune \
-o -name .git -prune \)
行,并准备好它不会解析虚线目录) ,你会堕落
~0.008英尺。打败了!
请注意,使用以下命令可以更加聪明地使用:
/usr/bin/test
它将有效地删除已发现的git存储库中的所有子存储库的爬网,其代价是为每个已爬网目录生成进程。我带来的最终查找解决方案大约是0.030秒,比之前的查找版本快两倍以上,但仍然比bash解决方案慢2倍。
请注意,$PATH
对于避免-o -name .git -prune
中的搜索非常重要,因为我需要-a "{}" != "."
和find
,因为我的主存储库本身就是一个git子存储库。< / p>
作为结论,我不会使用bash内置解决方案,因为它对我来说有太多的角落情况(我的第一次测试达到了限制之一)。但是对我来说解释为什么它在某些情况下可以(更快)更快是很重要的,但{{1}}解决方案对我来说似乎更加强大和一致。
答案 3 :(得分:3)
以上答案都依赖于找到“.git”存储库。然而,并非所有的git repos都有这些(例如裸回购)。以下命令将遍历所有目录并询问git是否认为每个目录都是目录。如果是这样的话,它会从树上修剪子树并继续。
find . -type d -exec sh -c 'cd "{}"; git rev-parse --git-dir 2> /dev/null 1>&2' \; -prune -print
它比其他解决方案慢得多,因为它在每个目录中执行命令,但它不依赖于特定的存储库结构。例如,可以用于查找裸git存储库。
答案 4 :(得分:2)
使用locate命令查看答案: Is there any way to list up git repositories in terminal?
使用locate而不是自定义脚本的优点是:
使用locate的缺点是:
进入定位路线,以下是如何列出OS X下目录下的所有git存储库:
启用定位索引(在Linux上会有所不同):
sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist
索引完成后运行此命令(可能需要对Linux进行一些调整):
repoBasePath=$HOME
locate '.git' | egrep '.git$' | egrep "^$repoBasePath" | xargs -I {} dirname "{}"
答案 5 :(得分:2)
对于Windows,您可以将以下内容放入名为gitlist.bat的批处理文件中,并将其放在PATH上。
@echo off
if {%1}=={} goto :usage
for /r %1 /d %%I in (.) do echo %%I | find ".git\."
goto :eof
:usage
echo usage: gitlist ^<path^>
答案 6 :(得分:1)
我使用以下命令列出当前目录中任何位置的所有git存储库:
find . -type d -execdir test -d {}/.git \\; -prune -print
这是快速的,因为一旦找到git存储库,它就会停止递归。 (尽管它不能处理裸仓库。)当然,您可以将.
更改为所需的任何目录。如果需要,您可以将-print
更改为-print0
以获取以空值分隔的值。
还要忽略包含.ignore
文件的目录:
find . -type d \( -execdir test -e {}/.ignore \; -prune \) -o \( -execdir test -d {}/.git \; -prune -print \)
我已将此别名添加到我的~/.gitconfig
文件中:
[alias]
repos = !"find -type d -execdir test -d {}/.git \\; -prune -print"
然后我只需要执行:
git repos
要获取当前目录中任何位置的所有git存储库的完整列表。
答案 7 :(得分:0)
此答案将@Greg Barrett提供的部分答案与我上面优化的答案结合在一起。
#!/bin/bash
# Update all git directories below current directory or specified directory
# Skips directories that contain a file called .ignore
HIGHLIGHT="\e[01;34m"
NORMAL='\e[00m'
export PATH=${PATH/':./:'/:}
export PATH=${PATH/':./bin:'/:}
#echo "$PATH"
DIRS="$( find "$@" -type d \( -execdir test -e {}/.ignore \; -prune \) -o \( -execdir test -d {}/.git \; -prune -print \) )"
echo -e "${HIGHLIGHT}Scanning ${PWD}${NORMAL}"
for d in $DIRS; do
cd "$d" > /dev/null
echo -e "\n${HIGHLIGHT}Updating `pwd`$NORMAL"
git pull
cd - > /dev/null
done