Каков наилучший способ написать хук обновления git, который отклоняет недействительные компиляции подмодулей?
Я пытаюсь написать крюк update
для git, который отскакивает, если подмодуль обновляется до идентификатора фиксации, который не существует в репозитории восходящего субмодуля. Чтобы сказать это по-другому, я хочу заставить пользователей вносить изменения в репозитории подмодулей, прежде чем они нажимают изменения на указатели подмодулей.
Одно предупреждение:
- Я хочу только протестировать подмодули, чьи голые, восходящие репозитории существуют на том же сервере, что и родительский репозиторий. В противном случае мы начинаем делать сумасшедшие вещи, такие как call 'git clone' или 'git fetch' из-за крюка git, который не будет забавным.
Я играю с идеей, но, похоже, должен быть лучший способ сделать это. Вот что я планировал сделать в крючке обновления:
- Проверьте, что имя refname передано в hook, чтобы узнать, обновляем ли мы что-либо в
refs/heads/
. Если нет, выйдите рано.
- Используйте
git rev-list
, чтобы получить список исправленных изменений.
- Для каждой ревизии:
- Вызовите
git show <revision_id>
и используйте регулярное выражение, которое проверяет, обновлен ли подмодуль (путем поиска `+ Subproject commit [0-9a-f] +).
- Если это коммитирование изменило подмодуль, получите содержимое файлов
.gitmodules
, как видно из этого конкретного commit (git show <revision_id>:.gitmodules
).
- Используйте результаты 3.1 и 3.2, чтобы получить список URL-подмодулей и их обновленные идентификаторы фиксации.
- Проверьте этот список, созданный в 3.3, на внешний файл, который отображает URL подмодулей в локальные голые репозитории git в файловой системе.
-
cd
к путям, найденным в 3.4, и выполните git rev-parse --quiet --verify <updated_submodule_commit_id>
, чтобы узнать, существует ли эта фиксация в этом репозитории. Если это не так, выйдите с ненулевым статусом.
(Примечание. Я считаю, что результаты 3.2 могут быть кэшированы по версиям до тех пор, пока вывод на git rev-parse --quiet --verify <revision_id>:.gitmodules
не изменится с одной версии на следующую. Я оставил эту часть для упрощения решения.)
Итак, это кажется довольно сложным, и я не могу не задаться вопросом, есть ли какие-то внутренние команды git, которые могли бы сделать мою жизнь намного проще. Или, может быть, есть другой способ подумать о проблеме?
Ответы
Ответ 1
Изменить, намного позже: Начиная с Git 1.7.7, git-push
теперь имеет параметр --recurse-submodules=check
, который отказывается выдвигать родительский проект, если какой-либо компилятор субмодуля не был нажат на свои пульты. Не похоже, что добавлен соответствующий параметр конфигурации push.recurseSubmodules
. Это, конечно, не полностью решает проблему - незнакомый пользователь все равно может нажать без проверки - но это очень актуально!
Я думаю, что лучший подход, а не изучение каждого отдельного коммита, заключается в том, чтобы посмотреть на diff на все толкаемые коммиты: git diff <old> <new>
. Вы не хотите смотреть на весь diff, хотя, действительно; это может быть огромным. К сожалению, команда git -submodule фарфора не работает в голых репозиториях, но вы все равно сможете быстро изучить .gitmodules
, чтобы получить список путей (и, возможно, URL-адресов). Для каждого из них вы можете git diff <old> <new> -- path
, и если есть diff, возьмите новый компилятор подмодуля. (И если вы беспокоитесь о возможности фиксации 000000 старых, вы можете просто использовать git show
на новом, я считаю.)
Как только вы получите все, о чем позаботились, вы уменьшили проблему до проверки наличия данных коммитов в данных удаленных репозиториях. К сожалению, похоже, что вы заметили, что это не просто, по крайней мере насколько я знаю. Сохранение локальных, современных клонов, вероятно, будет вашим лучшим выбором, и похоже, что вы там хорошо.
Кстати, я не думаю, что кэширование будет иметь здесь значение, так как крюк обновления один раз за ref. Да, вы могли бы сделать это в pre-receive hook, который получает все ссылки на stdin, но я не понимаю, почему вам следует больше работать. Это не будет дорогостоящей операцией и с крючком обновления, вы можете индивидуально принять или отклонить различные ветки, которые будут нажаты, вместо того, чтобы предотвратить их обновление, потому что только один был плохим.
Если вы хотите сэкономить какие-то проблемы, я бы, вероятно, просто не разбирался в файле gitmodules, а hardcode - в список. Я сомневаюсь, что ваш список подмодулей меняется очень часто, поэтому, вероятно, дешевле поддерживать это, чем писать что-то автоматическое.
Ответ 2
Вот небольшая попытка крюка обновления git. Документирование здесь, чтобы оно могло быть полезным для других. Известная оговорка заключается в том, что специальный случай "0000..." не обрабатывается.
#!/bin/bash
REF=$1
OLD=$2
NEW=$3
# This update hook is based on the following information:
# http://stackoverflow.com/info/3418674/bash-shell-script-function-to-verify-git-tag-or-commit-exists-and-has-been-pushe
# Get a list of submodules
git config --file <(git show $NEW:.gitmodules) --get-regexp 'submodule..*.path' | while read key path
do
url=$(git config --file <(git show $NEW:.gitmodules) --get "${key/.path/.url}")
git diff "$OLD..$NEW" -- "$path" | grep -e '^+Subproject commit ' |
cut -f3 -d ' ' | while read new_rev
do
LINES=$(GIT_DIR="$url" git branch --quiet --contains "$new_rev" 2>/dev/null | wc -l)
if [ $LINES == 0 ]
then
echo "Commit $new_rev not found in submodule $path ($url)" >&2
echo "Please push that submodule first" >&2
exit 1
fi
done || exit 1
done || exit 1
exit 0