如何在目录下快速查找所有git repos

Mik*_*inn 40 bash

扫描.git目录时,以下bash脚本速度很慢,因为它会查看每个目录.如果我有一个大型存储库的集合,则需要很长时间才能查找每个目录,寻找.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
Run Code Online (Sandbox Code Playgroud)

具体来说,您将如何使用这些选项?对于这个问题,你不能假设repos的集合都在同一个目录中; 它们可能位于嵌套目录中.

top
  repo1
  dirA

  dirB
     dirC
        repo1
Run Code Online (Sandbox Code Playgroud)

Cla*_*ley 39

查看Dennis在这篇帖子中关于find的-prune选项的回答:

如何在sh中使用'find'的'-prune'选项?

find . -name .git -type d -prune
Run Code Online (Sandbox Code Playgroud)

将速度提高一点,因为find不会进入.git目录,但它仍会下降到git存储库,寻找其他.git文件夹.这可能是一项代价高昂的操作.

如果有某种查找前瞻修剪机制,如果某个文件夹有一个名为.git的子文件夹,则修剪该文件夹...

也就是说,我认为你的瓶颈在于网络操作'git pull',而不是在find命令中,正如其他人在评论中发布的那样.

  • 你提到的"先行"是由@vaab在他的回答中描述的,一个较短(但可能较弱)的版本是:`find -type d -execdir test -d {}/.git \; -print -prune`. (2认同)
  • 添加`-maxdepth 2`(1或2取决于您想要的深度)使其更快,尤其是在Windows下的git bash中:```find -maxdepth 2 -type d -execdir test -d {} /。 git \; -print -prune``` (2认同)
  • 为了建立在这个答案的基础上,我发现这对我的脚本非常有用。命令是特定于 bash 的。`找到。-name .git -type d -prune -exec 目录名 {} \;` (2认同)

Mik*_*inn 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 *
Run Code Online (Sandbox Code Playgroud)

  • 优化后的版本速度明显加快。亲自尝试每个版本并停止疑惑。 (2认同)

vaa*_*aab 8

我花时间在你的问题中复制粘贴脚本,用你自己的答案将它与脚本进行比较.这里有一些有趣的结果

请注意:

  • git pull通过在前面加上一个来禁用它echo
  • 我也删除了颜色的东西
  • 我已经删除.ignorebash解决方案中的文件测试.
  • 并在> /dev/null此处和那里删除了不必要的东西.
  • 删除pwd了两个电话.
  • 添加-prune了这个find例子中明显缺乏的内容
  • 使用"while"而不是"for",这在find示例中也是反效果的
  • 大大解开了第二个例子来达到目的.
  • bash解决方案上添加了一个测试,不遵循sym链接以避免循环并表现为查找解决方案.
  • 添加shopt以允许*扩展到虚线目录名称以匹配find解决方案的功能.

因此,我们正在比较,基于查找的解决方案:

#!/bin/bash

find . -name .git -type d -prune | while read d; do
   cd $d/..
   echo "$PWD >" git pull
   cd $OLDPWD
done
Run Code Online (Sandbox Code Playgroud)

使用bash shell构建解决方案:

#!/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 *
Run Code Online (Sandbox Code Playgroud)

注意:内置(functionfor)对启动过程的MAX_ARGS OS限制不敏感.因此,*即使在非常大的目录上也不会破坏.

解决方案之间的技术差

基于查找的解决方案使用C函数来爬行存储库,它:

  • 必须为find命令加载新进程.
  • 将避免".git"内容,但会抓取git存储库的workdir,并在那些中松散一些(并最终找到更多匹配的元素).
  • 必须chdir通过几个深度的子目录进行每场比赛然后回去.
  • 必须chdir在find命令中执行一次,在bash部分执行一次.

基于bash的解决方案使用内置(所以近C实现,但解释)来爬行存储库,请注意:

  • 将只使用一个过程.
  • 将避免使用git workdir子目录.
  • 只会一次执行chdir一个级别.
  • 只执行chdir一次查看和执行命令.

解决方案之间的实际速度:

我有一个git存储库的工作开发集合,我在其上启动了脚本:

  • 找到解决方案:~0.080s(bash chdir需要~0.010s)
  • bash解决方案:~0.017s

我不得不承认,我不准备从bash内置中看到这样的胜利.在分析正在发生的事情后,它变得更加明显和正常.要增加对伤害的侮辱,如果你将shell更改/bin/bash/bin/sh(你必须注释掉这一shopt行,并准备它不会解析虚线目录),你将跌到~0.008s.打败那个 !

请注意,使用find解决方案可以更加聪明:

find . -type d \( -exec /usr/bin/test -d "{}/.git" -a "{}" != "." \; -print -prune \
       -o -name .git -prune \)
Run Code Online (Sandbox Code Playgroud)

这将有效地删除已发现的git存储库中的所有子存储库的爬网,其代价是为每个已爬网目录生成进程.我带来的最终查找解决方案大约是0.030秒,比之前的查找版本快两倍以上,但仍然比bash解决方案慢2倍.

请注意,/usr/bin/test重要的是避免搜索$PATH花费时间,而且我需要-o -name .git -prune-a "{}" != "."因为我的主存储库本身就是一个git子存储库.

作为结论,我不会使用bash内置解决方案,因为它有太多的角落情况(我的第一次测试达到了限制之一).但是对我来说解释为什么它在某些情况下会更快(更快)是很重要的,但find解决方案对我来说似乎更加强大和一致.


Cha*_*ieB 7

答案首先取决于找到“.git”存储库。然而,并不是所有的 git repos 都有这些(例如,bare repos)。以下命令将遍历所有目录并询问 git 是否将每个目录视为一个目录。如果是这样,它会从树上修剪子目录并继续。

find . -type d -exec sh -c 'cd "{}"; git rev-parse --git-dir 2> /dev/null 1>&2' \; -prune -print
Run Code Online (Sandbox Code Playgroud)

它比其他解决方案慢很多,因为它在每个目录中执行一个命令,但它不依赖于特定的存储库结构。例如,对于查找裸 git 存储库可能很有用。


Gre*_*ett 6

我使用以下命令列出当前目录中任何位置的所有 git 存储库:

find . -type d -execdir test -d {}/.git \\; -prune -print
Run Code Online (Sandbox Code Playgroud)

这很快,因为一旦找到 git 存储库,它就会停止递归。(尽管它不处理裸存储库。)当然,您可以将 更改.为您想要的任何目录。如果需要,您可以将 更改-print-print0null 分隔值。

同时忽略包含.ignore文件的目录:

find . -type d \( -execdir test -e {}/.ignore \; -prune \) -o \( -execdir test -d {}/.git \; -prune -print \)
Run Code Online (Sandbox Code Playgroud)

我已将此别名添加到我的~/.gitconfig文件中:

[alias]
  repos =  !"find -type d -execdir test -d {}/.git \\; -prune -print"
Run Code Online (Sandbox Code Playgroud)

然后我只需要执行:

git repos
Run Code Online (Sandbox Code Playgroud)

获取当前目录中所有 git 存储库的完整列表。

  • 有多余的反斜杠吗?修剪前 (2认同)

Mik*_*inn 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 2> >(sed -e 's/X11 forwarding request failed on channel 0//')
  cd - > /dev/null
done
Run Code Online (Sandbox Code Playgroud)