Каков наилучший способ написать хук обновления 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