Подтвердить что ты не робот

Как быстро найти все репозитории git в каталоге

Следующий bash script медленный при сканировании каталогов .git, потому что он смотрит на каждый каталог. Если у меня есть коллекция больших репозиториев, для поиска потребуется много времени, чтобы найти все каталоги, ища .git. Это будет намного быстрее, если оно будет обрезать каталоги в репозиториях, как только будет найден каталог .git. Любые идеи о том, как это сделать, или есть ли другой способ написать bash script, который выполняет одно и то же?

#!/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
4b9b3361

Ответ 1

Вот оптимизированное решение:

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

Посмотрите ответ Денниса в этом сообщении о опции find -prune:

Как использовать параметр -prune 'find' в sh?

find . -name .git -type d -prune

Будет немного ускоряться, поскольку find не будет входить в каталоги .git, но он все же спускается в хранилища git, ища другие .git-папки. И это может быть дорогостоящей операцией.

Что было бы здорово, если бы существовал какой-то механизм обрезки look look, который, если в папке есть подпапка под названием .git, затем обрезайте эту папку...

Тем не менее, я делаю ставку на то, что ваше узкое место находится в сетевой операции "git pull", а не в команде find, поскольку другие сообщения размещены в комментариях.

Ответ 3

Я потратил время, чтобы скопировать script в ваш вопрос, сравнить его с script с вашим собственным ответом. Вот несколько интересных результатов:

Обратите внимание:

  • Я отключил git pull, префикс их с помощью echo
  • Я также удалил цветные вещи.
  • Я также удалил тестирование файла .ignore в решении bash.
  • И удалил ненужный > /dev/null здесь и там.
  • удалены pwd вызовы обоих.
  • добавлен -prune, который, очевидно, отсутствует в примере find
  • использовал "while" вместо "for", который также был встречным продуктом в примере find
  • значительно распутывал второй пример, чтобы добраться до точки.
  • добавил тест на решение 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 "[email protected]"; 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" контента, но будет обходить workdir репозиториев git и потерять некоторые раз в этих (и в итоге найти более подходящие элементы).
  • потребуется chdir через несколько глубин sub-dir для каждого совпадения и вернуться назад.
  • будет иметь chdir один раз в команде find и один раз в части bash.

Решение на основе bash использует встроенный (так что почти C-реализация, но интерпретируется) для обхода репозитория, обратите внимание, что это:

  • будет использовать только один процесс.
  • будет избегать подкаталога git workdir.
  • будет выполнять только chdir один уровень за раз.
  • будет выполнять только chdir один раз для поиска и выполнения команды.

Фактические результаты скорости между решениями:

У меня есть рабочая коллекция разработки репозитория git, на котором я запустил скрипты:

  • найти решение: ~ 0.080s (bash chdir принимает ~ 0.010s)
  • bash решение: ~ 0.017s

Я должен признать, что я не был готов увидеть такую ​​победу от bash встроенных. Стало более очевидным и нормальным после анализа того, что происходит. Чтобы добавить оскорбление к травмам, если вы изменили оболочку от /bin/bash до /bin/sh (вы должны прокомментировать строку shopt и быть готовым, чтобы она не разобрала точечные каталоги), вы попадете в ~ 0,008 с. Убей это!

Обратите внимание, что вы можете быть более умными с помощью решения find, используя:

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

который эффективно удалит обход всего субрепозитария в найденном репозитории git по цене размножения процесса для каждого обхода каталога. Окончательное решение для поиска, с которым я пришел, составило около 0.030s, что более чем в два раза быстрее, чем предыдущая версия для поиска, но остается в 2 раза медленнее, чем решение bash.

Обратите внимание, что /usr/bin/test важно избегать поиска в $PATH, который стоит времени, и мне нужны были -o -name .git -prune и -a "{}" != ".", потому что мой основной репозиторий был сам по себе git.

В качестве вывода я не буду использовать встроенное решение bash, потому что у меня слишком много угловых случаев для меня (и мой первый тест попал в одно из ограничений). Но для меня было важно объяснить, почему в некоторых случаях это может быть (намного) быстрее, но решение find кажется гораздо более надежным и последовательным для меня.

Ответ 4

Проверьте ответ, используя команду locate: Есть ли способ перечислить репозитории git в терминале?

Преимущества использования locate вместо пользовательского script:

  • Поиск индексируется, поэтому он масштабируется
  • Это не требует использования (и обслуживания) пользовательского bash script

Недостатками использования локализации являются:

  • db, который находит использование, обновляется еженедельно, поэтому свежеприготовленные репозитории git не отображаются

Переместив маршрут локации, перечислите все репозитории git в каталоге, для OS X:

Включить индексирование локализации (будет отличаться в 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

Для окон вы можете поместить следующее в пакетный файл 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

Ответы, прежде всего, основаны на поиске репозитория ".git". Однако не все репозитории git имеют эти (например, голые репозитории). Следующая команда будет проходить через все каталоги и спросить git, если она считает, что каждая из них является каталогом. Если это так, он вырезает субдиры с дерева и продолжается.

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

Это намного медленнее, чем другие решения, потому что он выполняет команду в каждом каталоге, но не полагается на определенную структуру репозитория. Может оказаться полезным для поиска голых репозиториев git, например.