2015-11-25 6 views
10

Давайте сразу начнут с ломом на pre-receive крючке, который я уже писал:Git и «ГИТ-лязг формат» сценарий надежно отклонить толчки, которые нарушают соглашения стиля кода

#!/bin/sh 
## 
    format_bold='\033[1m' 
    format_red='\033[31m' 
format_yellow='\033[33m' 
format_normal='\033[0m' 
## 
    format_error="${format_bold}${format_red}%s${format_normal}" 
format_warning="${format_bold}${format_yellow}%s${format_normal}" 
## 
stdout() { 
    format="${1}" 
    shift 
    printf "${format}" "${@}" 
} 
## 
stderr() { 
    stdout "${@}" 1>&2 
} 
## 
output() { 
    format="${1}" 
    shift 
    stdout "${format}\n" "${@}" 
} 
## 
error() { 
    format="${1}" 
    shift 
    stderr "${format_error}: ${format}\n" 'error' "${@}" 
} 
## 
warning() { 
    format="${1}" 
    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: %s\n%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, посмотрите here.

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

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

Во-вторых, git-clang-format процессы только поставил (но не совершал) изменения по сравнению с конкретных фиксации (HEAD по умолчанию). Таким образом, вы можете легко увидеть, где находится угол. Да, это с корень совершает (ревизии). Фактически, 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 для соблюдения правил стиля кода.

Мир.

ответ

4

Примечание:
Для тех, кто ищет последнюю дату (более или менее) всеобъемлющий и хорошо проверенное решение, я у себя соответствующий открытый репозиторий [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, это приведет к ошибке, поскольку 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')".

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