Как быстро найти все репозитории 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
Ответы
Ответ 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, например.