2015-11-25 1 views
10

Commençons immédiatement avec un morceau du crochet pre-receive que je l'ai déjà écrit:crochet Git « pré-recevoir » et script « git-clang format » de rejeter de manière fiable pousse qui violent les conventions de style de code

#!/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 

REMARQUE:
Les zones dont le code n'est pas pertinent sont tronquées avec # ....

REMARQUE:
Si vous n'êtes pas familier avec git-clang-format, un coup d'oeil here.

Ce crochet fonctionne comme prévu, et jusqu'à présent, je n'ai pas remarqué de bugs, mais si vous remarquez un problème ou avez une suggestion d'amélioration, j'apprécierais tout rapport. Probablement, je devrais faire un commentaire sur quelle est l'intention derrière ce crochet. Eh bien, il vérifie la conformité de chaque révision poussée avec les conventions de style de code en utilisant git-clang-format, et si l'une d'entre elles n'est pas conforme, elle affichera le diff pertinent (celui indiquant aux développeurs ce qui doit être corrigé) pour chacun d'entre eux. Fondamentalement, j'ai deux questions en profondeur concernant ce crochet. Tout d'abord, notez que j'effectue une copie du dépôt nu (serveur) de la télécommande vers un répertoire temporaire et que j'expérimente le code à des fins d'analyse. Laissez-moi vous expliquer l'intention de cela. Notez que je fais plusieurs git checkout s et git reset s (en raison de la boucle for) afin d'analyser individuellement toutes les révisions poussées avec git-clang-format. Ce que j'essaie d'éviter ici, c'est le problème de concurrence (possible) sur l'accès push au dépôt nu (serveur) de la télécommande. C'est-à-dire, je suis sous l'impression que si plusieurs développeurs essayent de pousser en même temps vers une télécommande avec ce hook pre-receive installé, cela pourrait causer des problèmes si chacun de ces push "sessions" ne fait pas git checkout s et s avec sa copie privée du référentiel. Donc, pour simplifier, git-daemon dispose-t-il d'une gestion intégrée des verrous pour les "sessions" push simultanées? Va-t-il exécuter les instances de hook pre-receive correspondantes de manière strictement séquentielle ou il y a une possibilité d'entrelacement (ce qui peut potentiellement causer un comportement indéfini)? Quelque chose me dit qu'il devrait y avoir une solution intégrée pour ce problème avec des garanties concrètes, sinon comment les télécommandes fonctionneraient-elles en général (même sans crochets complexes) étant soumises à des poussées simultanées? S'il existe une telle solution intégrée, la copie est redondante et la simple réutilisation du référentiel nu accélèrerait réellement le traitement. En passant, toute référence à la documentation officielle concernant cette question est la bienvenue.

En second lieu, les processus git-clang-format ne mis en scène (mais pas engagé) change par rapport à engager spécifiques (HEAD par défaut). Ainsi, vous pouvez facilement voir où se trouve un boîtier de coin. Oui, c'est avec le racine commits (révisions). En fait, git reset --soft 'HEAD~1' ne peut pas être appliqué aux validations racine car ils n'ont pas de parents à réinitialiser. Par conséquent, le contrôle suivant ma deuxième question est là:

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

J'ai essayé git update-ref -d 'HEAD' mais cela brise le dépôt de telle sorte que git-clang-format n'est pas en mesure de traiter plus. Je crois que ceci est lié au fait que toutes ces révisions poussées qui sont en cours d'analyse (y compris celle de la version racine) n'appartiennent vraiment à aucune branche pour le moment. Autrement dit, ils sont en détachéHEAD état.Il serait parfait de trouver une solution à ce cas de coin, de sorte que initiales commits peuvent également subir la même vérification par git-clang-format pour la conformité avec les conventions de style de code.

Paix.

Répondre

4

REMARQUE:
Pour ceux qui recherchent une mise à jour, (plus ou moins) solution complète et éprouvée, je serai l'hôte du dépôt public correspondant [1]. Actuellement, les deux hooks importants s'appuyant sur git-clang-format sont implémentés: pre-commit et pre-receive. Idéalement, vous obtenez le flux de travail le plus automatisé et infaillible lorsque vous utilisez simultanément les deux. Comme d'habitude, les suggestions d'amélioration sont les bienvenues.

REMARQUE:
Actuellement, le crochet pre-commit [1] nécessite le patch git-clang-format.diff (écrit par moi aussi) [1] à appliquer à git-clang-format. Les exemples de cas de motivation et d'utilisation pour ce correctif sont résumés dans la soumission officielle de révision de correctif à LLVM/Clang [2]. Espérons qu'il sera accepté et fusionné en amont bientôt.


J'ai réussi à implémenter une solution pour la deuxième question. Je dois admettre que ce n'était pas facile à trouver en raison de la documentation rare de Git et de l'absence d'exemples. Jetons un regard sur les changements de code correspondant première:

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

Comme vous pouvez le voir, au lieu de faire à plusieurs reprises git reset --soft 'HEAD~1', maintenant j'instruis explicitement git-clang-format à agir contre HEAD~1 avec l'option --commit (alors que sa valeur par défaut est HEAD qui était implicite dans la version initiale présentée dans ma question). Cependant, cela ne résout pas le problème tout seul parce que quand nous frapperons racine commettre cela conduirait à nouveau à l'erreur comme HEAD~1 ne renverrait plus à une révision valide (de même que ce ne serait pas possible de faire git reset --soft 'HEAD~1') . C'est pourquoi dans ce cas particulier, j'ordonne git-clang-format de fonctionner contre le hachage (magique) 4b825dc642cb6eb9a060e54bf8d69288fbee4904 [3, 4, 5, 6]. Pour en savoir plus sur ce hash, consultez les références, mais, en bref, il fait référence à l'objet arbre vide - celui qui n'a rien mis en scène ou engagé, ce qui est exactement ce dont nous avons besoin git-clang-format pour opérer dans notre cas.

REMARQUE:
Vous ne devez pas se rappeler 4b825dc642cb6eb9a060e54bf8d69288fbee4904 par cœur et il est préférable de ne pas coder en dur il (juste au cas où ce hachage magique ne change jamais à l'avenir). Il s'avère qu'il peut toujours être récupéré avec git hash-object -t tree '/dev/null' [5, 6]. Ainsi, dans ma version finale du crochet pre-receive ci-dessus, j'ai commit="$(git hash-object -t tree '/dev/null')" à la place.

P.S. Je suis toujours à la recherche d'une réponse de bonne qualité à ma première question. Au fait, j'ai posé ces questions sur la liste de diffusion officielle de Git et je n'ai reçu aucune réponse à ce jour, quelle honte ...