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

时间:2012-08-16 06:23:58

标签: bash

以下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

8 个答案:

答案 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命令加载新进程。
  • 将避免" .git"内容,但将抓取git存储库的workdir,并松散一些 在那些时间(并最终找到更多匹配的元素)。
  • 每次比赛必须经过几个子目录深度chdir并返回。
  • 必须在find命令中使用chdir一次,在bash部分使用一次。

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

  • 只会使用一个流程。
  • 将避免使用git workdir子目录。
  • 一次只能执行chdir个级别。
  • 只会执行/bin/bash一次查看和执行命令。

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

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

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

我必须承认,我还没有准备好从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而不是自定义脚本的优点是:

  1. 搜索已编入索引,因此会缩放
  2. 它不需要使用(和维护)自定义bash脚本
  3. 使用locate的缺点是:

    1. 定位使用的数据库每周更新一次,因此新创建的git存储库不会显示
    2. 进入定位路线,以下是如何列出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