Git 'pre-receive' hook y 'git-clang-format' script para rechazar de manera confiable los push que violan las convenciones de estilo de código

Comencemos de inmediato con un fragment del gancho de pre-receive que ya he escrito:

 #!/bin/sh ## format_bold='\033[1m' format_networking='\033[31m' format_yellow='\033[33m' format_normal='\033[0m' ## format_error="${format_bold}${format_networking}%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 

NOTA:
Los lugares con código irrelevante se anulan con # ...

NOTA:
Si no está familiarizado con el git-clang-format , mire aquí .

Ese gancho funciona como se esperaba, y hasta ahora, no noté ningún error, pero si detecta algún problema o tiene una sugerencia de mejora, agradecería cualquier informe. Probablemente, debería hacer un comentario sobre cuál es la intención detrás de este gancho. Bueno, comtesting cada revisión empujada para verificar el cumplimiento de las convenciones de estilo de código usando git-clang-format , y si alguno de ellos no cumple, generará la diferencia relevante (la que le dice a los desarrolladores qué debería arreglarse) para cada uno de ellos . Básicamente, tengo dos preguntas en profundidad con respecto a este gancho.

Primero, observe que realizo una copy del repository desnudo (del server) del control remoto en algún directory temporal y miro el código para analizarlo allí. Déjame explicar la intención de esto. Tenga en count que realizo varias git checkout y git reset (debido al bucle for ) para analizar todas las revisiones enviadas individualmente con git-clang-format . Lo que estoy tratando de evitar aquí, es el (posible) problema de concurrency en el acceso de inserción al repository vacío del remoto (server). Es decir, estoy bajo la printing de que si varios desarrolladores intentarán presionar al mismo time a un control remoto con este gancho de pre-receive instalado, eso podría causar problemas si cada una de estas "sesiones" de inserción no hace el git checkout y git reset s con su copy privada del repository. Entonces, para simplificar, ¿tiene git-daemon una gestión de locking incorporada para las "sesiones" de inserción simultáneas? ¿Ejecutará las instancias correspondientes pre-receive gancho pre-receive estrictamente secuencial o existe la posibilidad de entrelazado (que puede causar un comportamiento indefinido)? Algo me dice que debería haber una solución incorporada para este problema con garantías concretas; de lo contrario, ¿cómo funcionarían los mandos a distancia en general (incluso sin ganchos complejos) sometidos a empujes concurrentes? Si existe una solución integrada, entonces la copy es networkingundante y simplemente reutilizar el repository simple realmente aceleraría el procesamiento. Por cierto, cualquier reference a la documentation oficial con respecto a esta pregunta es muy bienvenida.

En segundo lugar, los processs de git-clang-format solo realizan cambios en etapas (pero no se comprometen) frente a compromisos específicos ( HEAD por defecto). Por lo tanto, puede ver fácilmente dónde se encuentra un caso esquina. Sí, es con las confirmaciones de raíz (revisiones). De hecho, git reset --soft 'HEAD~1' no se puede aplicar a las confirmaciones de raíz ya que no tienen padres a los que se pueda restablecer. Por lo tanto, el siguiente control con mi segunda pregunta está allí:

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

He intentado con git update-ref -d 'HEAD' pero esto rompe el repository de tal manera que git-clang-format ya no puede procesarlo. Creo que esto está relacionado con el hecho de que todas estas revisiones empujadas que se están parsing (incluida esta raíz) todavía no pertenecen a ninguna twig. Es decir, están en estado HEAD separado . Sería perfecto encontrar una solución para este caso de esquina también, para que las confirmaciones iniciales también puedan someterse a la misma verificación por git-clang-format para cumplir con las convenciones de estilo de código.

Paz.

NOTA:
Para aquellos que buscan una solución actualizada (más o less) integral y comprobada, alojo el repository público correspondiente [ 1 ]. Actualmente, se implementan los dos ganchos importantes que se basan en el git-clang-format : pre-commit y pre-receive . Idealmente, obtendrá el flujo de trabajo más automatizado y a testing de tontos al usar ambos simultáneamente. Como de costumbre, las sugerencias de mejora son bienvenidas.

NOTA:
Actualmente, el git-clang-format.diff [ 1 ] requiere el parche git-clang-format.diff (creado por mí también) [ 1 ] para ser aplicado a git-clang-format . Los ejemplos de motivación y uso de este parche se resumen en el envío de la revisión del parche oficial a LLVM / Clang [ 2 ]. Con suerte, será aceptado y fusionado pronto.


Pude implementar una solución para la segunda pregunta. Tengo que admitir que no fue fácil de encontrar debido a la escasa documentation de Git y la ausencia de ejemplos. Echemos un vistazo a los cambios de código correspondientes primero:

 # ... 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 # ... 

Como puede ver, en lugar de hacer repetidamente git reset --soft 'HEAD~1' , ahora explícitamente le pido a git-clang-format que opere contra HEAD~1 con la opción --commit (mientras que su pnetworkingeterminado es HEAD que estaba implícito) en la versión inicial presentada en mi pregunta). Sin embargo, eso aún no resuelve el problema por sí mismo, porque cuando git reset --soft 'HEAD~1' esto generaría un error ya que HEAD~1 ya no se referiría a una revisión válida (de manera similar a como no sería posible hacer un git reset --soft 'HEAD~1' ). Es por eso que para este caso en particular, le pido al git-clang-format que opere contra el hash (mágico) 4b825dc642cb6eb9a060e54bf8d69288fbee4904 [ 3 , 4 , 5 , 6 ]. Para get más información acerca de este hash, consulte las references, pero, en resumen, se refiere al object de tree vacío de Git, el que no tiene nada organizado o comprometido, que es exactamente lo que necesitamos para que funcione el git-clang-format en nuestro caso.

NOTA:
No tiene que recordar 4b825dc642cb6eb9a060e54bf8d69288fbee4904 de memory y es mejor no codificarlo duro (por si acaso este hachís mágico cambia alguna vez en el futuro). Resulta que siempre se puede recuperar con git hash-object -t tree '/dev/null' [ 5 , 6 ]. Por lo tanto, en mi versión final del anzuelo pre-receive , he commit="$(git hash-object -t tree '/dev/null')" lugar.

PD. Todavía estoy buscando una respuesta de buena calidad en mi primera pregunta. Por cierto, hice estas preguntas en la list de correo oficial de Git y no recibí respuestas hasta el momento, qué lástima …

Intereting Posts