58

次のbashスクリプトは、すべてのディレクトリを検索するため、.gitディレクトリをスキャンするときに低速です。大規模なリポジトリのコレクションがある場合、.gitを探して、すべてのディレクトリを検索するのに長い時間がかかります。.gitディレクトリが見つかったら、リポジトリ内のディレクトリを削除すると、はるかに高速になります。それを行う方法についてのアイデアはありますか、または同じことを達成する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

具体的には、これらのオプションをどのように使用しますか?この問題では、リポジトリのコレクションがすべて同じディレクトリにあると想定することはできません。それらはネストされたディレクトリ内にある可能性があります。

top
  repo1
  dirA

  dirB
     dirC
        repo1
4

8 に答える 8

54

find の -prune オプションに関するこの投稿の Dennis の回答を確認してください。

shで「find」の「-prune」オプションを使用するには?

find . -name .git -type d -prune

find は .git ディレクトリに降りないので、少しスピードアップしますが、他の .git フォルダを探して git リポジトリに降ります。そして、それはコストのかかる操作になる可能性があります。

フォルダーに .git というサブフォルダーがある場合、そのフォルダーをプルーニングする、ある種の検索先読みプルーニング メカニズムがあれば、すばらしいことです...

そうは言っても、他の人がコメントに投稿したように、あなたのボトルネックはfindコマンドではなく、ネットワーク操作の「git pull」にあると思います。

于 2012-08-17T02:11:22.860 に答える
14

最適化されたソリューションは次のとおりです。

#!/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 *
于 2012-08-17T17:58:26.750 に答える
12

時間をかけて質問のスクリプトをコピーして貼り付け、スクリプトと自分の回答を比較してください。ここにいくつかの興味深い結果があります:

その点に注意してください:

  • git pullプレフィックスを付けて無効にしましたecho
  • 色物も外しました
  • .ignoreソリューションのファイル テストも削除しましたbash
  • そして、あちこちの不要なものを取り除きました> /dev/null
  • pwd両方で呼び出しを削除しました。
  • -pruneに明らかに欠けている追加find
  • findこの例では、「for」の代わりに「while」を使用しましたが、これも非生産的でした
  • 要点に到達するために、2 番目の例をかなり解きほぐしました。
  • bashサイクルを回避し、検索ソリューションとして動作するために、シンボリック リンクをたどらないようにソリューションにテストを追加しました。
  • ソリューションの機能と一致するように、ドット付きディレクトリ名に展開shoptできるようにするために追加されました。*find

したがって、検索ベースのソリューションを比較しています。

#!/bin/bash

find . -name .git -type d -prune | while read d; do
   cd $d/..
   echo "$PWD >" git pull
   cd $OLDPWD
done

bash シェル ビルド ソリューションを使用する場合:

#!/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 *

注: ビルトイン (functionおよびfor) は、プロセスを起動するための MAX_ARGS OS 制限の影響を受けません。そのため*、非常に大きなディレクトリでも壊れません。

ソリューション間の技術的な違い:

検索ベースのソリューションでは、C 関数を使用してリポジトリをクロールします。

  • コマンドの新しいプロセスをロードする必要がありますfind
  • 「.git」コンテンツを回避しますが、git リポジトリの workdir をクロールし、それらの中でいくつかの時間を失います (最終的には、より多くの一致する要素を見つけます)。
  • chdir一致ごとにいくつかの深さのサブディレクトリを通過して戻る必要があります。
  • chdirfind コマンドで 1 回、bash 部分で 1 回実行する必要があります。

bash ベースのソリューションでは、ビルトイン (C に近い実装ですが、解釈されます) を使用してリポジトリをクロールします。次の点に注意してください。

  • 1 つのプロセスのみを使用します。
  • git workdir サブディレクトリを回避します。
  • chdir一度に 1 つのレベルのみを実行します。
  • chdirコマンドを検索して実行するために一度だけ実行されます。

ソリューション間の実際の速度の結果:

スクリプトを起動した git リポジトリの作業中の開発コレクションがあります。

  • 解決策を見つける: ~0.080 秒 (bash chdir には ~0.010 秒かかります)
  • bash ソリューション: ~0.017 秒

私は、bash ビルトインによるそのような勝利を見る準備ができていなかったことを認めなければなりません。何が起こっているのかを分析した後、それはより明白になり、正常になりました。怪我に侮辱を加えるために、シェルを から/bin/bashに変更すると/bin/sh(行をコメントアウトし、shoptドット付きディレクトリを解析しないように準備する必要があります)、 ~0.008s に落ちます。それを打ちます!

次を使用して、find ソリューションをより賢く使用できることに注意してください。

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

これにより、見つかった git リポジトリ内のすべてのサブリポジトリのクロールが効果的に削除されますが、クロールされたディレクトリごとにプロセスが生成されます。最終的な find ソリューションは約 0.030 秒で、以前の find バージョンよりも 2 倍以上高速ですが、bash ソリューションよりも 2 倍遅いままです。

時間がかかる/usr/bin/test検索を避けることが重要であることに注意してください。メインリポジトリ自体がgitサブリポジトリであったため、必要でした。$PATH-o -name .git -prune-a "{}" != "."

結論として、bash ビルトイン ソリューションは、私にはコーナー ケースが多すぎるため使用しません (そして、最初のテストで制限の 1 つにヒットしました)。しかし、場合によっては(はるかに)高速になる理由を説明することは重要でしたが、findソリューションははるかに堅牢で一貫しているように思えます。

于 2014-04-28T14:42:02.093 に答える
3

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^>
于 2015-12-18T18:53:29.420 に答える
2

locate コマンドを使用して答えを確認してください: Is there any way to list up git repositories in terminal?

カスタム スクリプトの代わりに locate を使用する利点は次のとおりです。

  1. 検索はインデックス化されているため、スケーリングされます
  2. カスタム bash スクリプトの使用 (およびメンテナンス) は必要ありません。

ロケートを使用することの欠点は次のとおりです。

  1. ロケートが使用するデータベースは毎週更新されるため、新しく作成された git リポジトリは表示されません

ロケート ルートに進むと、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 "{}"
于 2013-01-05T00:27:14.903 に答える