Git "pre-receive" hook и скрипт "git-clang-format" для надежного отклонения толчков, нарушающих соглашения о стиле Кода

давайте сразу начнем с утиля pre-receive крюк, который я уже написал:

#!/bin/sh
##
  format_bold='3[1m'
   format_red='3[31m'
format_yellow='3[33m'
format_normal='3[0m'
##
  format_error="${format_bold}${format_red}%s${format_normal}"
format_warning="${format_bold}${format_yellow}%s${format_normal}"
##
stdout() {
  format=""
  shift
  printf "${format}" "${@}"
}
##
stderr() {
  stdout "${@}" 1>&2
}
##
output() {
  format=""
  shift
  stdout "${format}n" "${@}"
}
##
error() {
  format=""
  shift
  stderr "${format_error}: ${format}n" 'error' "${@}"
}
##
warning() {
  format=""
  shift
  stdout "${format_warning}: ${format}n" 'warning' "${@}"
}
##
die() {
  error "${@}"
  exit 1
}
##
git() {
  command git --no-pager "${@}"
}
##
list() {
  git rev-list "${@}"
}
##
clang_format() {
  git clang-format --style='file' "${@}"
}
##
while read sha1_old sha1_new ref; do
  case "${ref}" in
  refs/heads/*)
    branch="$(expr "${ref}" : 'refs/heads/(.*)')"
    if [ "$(expr "${sha1_new}" : '0*$')" -ne 0 ]; then # delete
      unset sha1_new
      # ...
    else # update
      if [ "$(expr "${sha1_old}" : '0*$')" -ne 0 ]; then # create
        unset sha1_old
        sha1_range="${sha1_new}"
      else
        sha1_range="${sha1_old}..${sha1_new}"
        # ...
        fi
      fi
      # ...
             GIT_WORK_TREE="$(mktemp --tmpdir -d 'gitXXXXXX')"
      export GIT_WORK_TREE
             GIT_DIR="${GIT_WORK_TREE}/.git"
      export GIT_DIR
      mkdir -p "${GIT_DIR}"
      cp -a * "${GIT_DIR}/"
      ln -s "${PWD}/../.clang-format" "${GIT_WORK_TREE}/"
      error=
      for sha1 in $(list "${sha1_range}"); do
        git checkout --force "${sha1}" > '/dev/null' 2>&1
        if [ "$(list --count "${sha1}")" -eq 1 ]; then
          # What should I put here?
        else
          git reset --soft 'HEAD~1' > '/dev/null' 2>&1
        fi
        diff="$(clang_format --diff)"
        if [ "${diff%% *}" = 'diff' ]; then
          error=1
          error '%s: %sn%s'                                                   
                'Code style issues detected'                                   
                "${sha1}"                                                      
                "${diff}"                                                      
                1>&2
        fi
      done
      if [ -n "${error}" ]; then
        die '%s' 'Code style issues detected'
      fi
    fi
    ;;
  refs/tags/*)
    tag="$(expr "${ref}" : 'refs/tags/(.*)')"
    # ...
    ;;
  *)
    # ...
    ;;
  esac
done
exit 0

Примечание:
Места с нерелевантным кодом заглушаются # ....

Примечание:
Если вы не знакомы с git-clang-format посмотри здесь.

этот крюк работает, как ожидалось, и до сих пор я не заметил никаких ошибок, но если вы обнаружите какие-либо проблемы или у вас есть предложение по улучшению, я бы ценю любой отчет. Вероятно, я должен дать комментарий о том, какие намерения стоят за этим крючком. Ну, он проверяет каждую нажатую ревизию на соответствие соглашениям о стиле кода с помощью git-clang-format, и если какой-либо из них не соответствует, он выведет соответствующий diff (тот, который говорит разработчикам, что должно быть исправлено) для каждого из них. В принципе, у меня есть два глубоких вопроса относительно этого крючка.

во-первых, обратите внимание, что я выполняю копию (серверного) репозитория удаленного некоторый временный каталог и проверьте код для анализа там. Позвольте мне объяснить цель этого. Обратите внимание, что я делаю несколько git checkouts и git resets (из-за for loop) для того, чтобы проанализировать все сдвинутые ревизии по отдельности с git-clang-format. То, что я пытаюсь избежать здесь, - это (возможная) проблема параллелизма при принудительном доступе к (серверному) голому репозиторию remote. То есть, у меня создается впечатление, что если несколько разработчиков попытаются одновременно нажать на пульт с этим pre-receive установлен крюк, который может вызвать проблемы, если каждый из этих push - "сеансов" не делает git checkouts и git resets с его частной копией репозитория. Так, проще говоря, делает git-daemon имеют встроенное управление блокировкой для одновременных push-сессий? Выполнение соответствующих pre-receive экземпляры крючка строго последовательно или есть возможность чередования (что потенциально может вызвать неопределенное поведение)? Что-то говорит мне, что должно быть встроенное решение для эта проблема с конкретными гарантиями, иначе как бы пульты работали вообще (даже без сложных крючков), подвергаясь одновременным толчкам? Если есть такое встроенное решение, то копия является избыточной и просто повторное использование голого репозитория фактически ускорит обработку. Кстати, любая ссылка на официальную документацию по этому вопросу очень приветствуется.

во-вторых,git-clang-format процессы только поставил (но не зафиксированы) изменения и конкретная фиксация (HEAD по умолчанию). Таким образом, вы можете легко увидеть, где в углу лежит. Да, это с root коммитов (изменений). На самом деле,git reset --soft 'HEAD~1' не может быть применен к корневым коммитам, поскольку у них нет родителей для сброса. Следовательно, существует следующая проверка с моим вторым вопросом:

        if [ "$(list --count "${sha1}")" -eq 1 ]; then
          # What should I put here?
        else
          git reset --soft 'HEAD~1' > '/dev/null' 2>&1
        fi

я пробовал git update-ref -d 'HEAD' но это нарушает репозиторий таким образом, что git-clang-format не в состоянии обработать его больше. Я считаю, что это связано с тем, что все эти выталкиваемые ревизии, которые анализируются (включая этот корневой), на самом деле еще не принадлежат какой-либо ветви. То есть, они в отдельно стоящее HEAD государство. Было бы идеально найти решение и для этого углового случая, так что нач коммиты также могут проходить ту же проверку git-clang-format для соответствия соглашениям о стиле Кода.

Мирный.

1 ответов


Примечание:
Для тех, кто ищет современное, (более или менее) комплексное и хорошо протестированное решение, я размещаю соответствующий публичный репозиторий [1]. В настоящее время два важных крючка полагаются на git-clang-format реализованы: pre-commit и pre-receive. В идеале, вы получаете максимальную автоматизацию и защиту от дураков при одновременном использовании обоих. Как обычно, предложения по улучшению очень добро пожаловать.

Примечание:
В настоящее время pre-commit крюк [1] требует git-clang-format.diff патч (автор я) [1] применяется к git-clang-format. Мотивация и примеры использования для этого патча обобщены в официальном представлении обзора патча LLVM / Clang [2]. Будем надеяться, что он будет принят и вскоре объединен вверх по течению.


мне удалось реализовать решение для второго вопрос. Я должен признать, что это было нелегко найти из-за скудной документации Git и отсутствия примеров. Давайте сначала рассмотрим соответствующие изменения кода:

# ...
clang_format() {
  git clang-format --commit="${commit}" --style='file' "${@}"
}
# ...
      for sha1 in $(list "${sha1_range}"); do
        git checkout --force "${sha1}" > '/dev/null' 2>&1
        if [ "$(list --count "${sha1}")" -eq 1 ]; then
          commit='4b825dc642cb6eb9a060e54bf8d69288fbee4904'
        else
          commit='HEAD~1'
        fi
        diff="$(clang_format --diff)"
        # ...
      done
      # ...

как вы можете видеть, вместо того, чтобы неоднократно делал git reset --soft 'HEAD~1', Я сейчас явно указать git-clang-format для работы HEAD~1 С --commit опция (в то время как его значение по умолчанию HEAD это подразумевалось в первоначальной версии, представленной в моем вопросе). Однако это все еще не решает проблему его собственный, потому что, когда мы ударим root commit это снова приведет к ошибке как HEAD~1 больше не будет ссылаться на действительную ревизию (аналогично тому, как это было бы невозможно сделать git reset --soft 'HEAD~1'). Вот почему для этого конкретного случая я инструктирую git-clang-format для работы против (магии)4b825dc642cb6eb9a060e54bf8d69288fbee4904 хеширования [3, 4, 5, 6]. Чтобы узнать больше об этом хэше, обратитесь к ссылкам, но, вкратце, это относится к Git пустой объект Дерево - тот, который не имеет ничего инсценированного или совершенного, что именно то, что нам нужно git-clang-format для работы в нашем случае.

Примечание:
Вы не должны помнить 4b825dc642cb6eb9a060e54bf8d69288fbee4904 наизусть, и лучше не жестко кодировать его (на случай, если этот волшебный хэш когда-либо изменится в будущем). Оказывается, его всегда можно получить с помощью git hash-object -t tree '/dev/null' [5, 6]. Таким образом, в моем окончательном варианте выше pre-receive крюк, у меня commit="$(git hash-object -t tree '/dev/null')" вместо.

П. С. я все еще ищу хороший качественный ответ на мой первый вопрос. Кстати, я задавал эти вопросы в официальном списке рассылки Git и пока не получил ответов, какой позор...