2015-11-25 6 views
10

Lassen Sie uns sofort mit einem Schrott des pre-receive Haken beginnen, die ich bereits geschrieben habe:Git ‚pre-receive‘ Haken und ‚git-Klirren-Format‘ Skript Schübe zuverlässig ablehnen, die Code-Stil Konventionen verletzen

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

HINWEIS:
Orte mit irrelevantem Code sind mit # ... stubbed.

HINWEIS:
Wenn Sie mit git-clang-format nicht vertraut sind, werfen Sie einen Blick here.

Dieser Hook funktioniert wie erwartet, und bisher habe ich keine Bugs bemerkt, aber wenn Sie irgendein Problem entdecken oder einen Verbesserungsvorschlag haben, würde ich mich über jeden Bericht freuen. Wahrscheinlich sollte ich einen Kommentar geben, was hinter diesem Haken steckt. Nun, es überprüft jede Push-Version auf Übereinstimmung mit den Codestilkonventionen unter Verwendung von git-clang-format, und wenn einer von ihnen nicht den Anforderungen entspricht, gibt es für jeden von ihnen den entsprechenden diff aus (der den Entwicklern sagt, was behoben werden sollte). Grundsätzlich habe ich zwei eingehende Fragen zu diesem Haken.

Beachten Sie zunächst, dass ich eine Kopie des (barrierefreien) (remotes) Server-Repositorys in ein temporäres Verzeichnis überführe und dort den Code für die Analyse auschecke. Lassen Sie mich die Absicht erklären. Beachten Sie, dass ich mehrere git checkout s und git reset s (aufgrund for Schleife), um alle Push-Revisionen einzeln mit git-clang-format zu analysieren. Was ich hier zu vermeiden versuche, ist das (mögliche) Nebenläufigkeitsproblem beim Push-Zugriff auf das bare Repository des Servers (der Server). Das heißt, ich habe den Eindruck, dass, wenn mehrere Entwickler versuchen werden, gleichzeitig zu einer Fernbedienung mit diesem pre-receive Haken zu drücken, dies Probleme verursachen kann, wenn jede dieser Push "Sitzungen" git checkout s und s nicht tut seine private Kopie des Repositories. Also, um es einfach zu machen, hat git-daemon integrierte Lock-Management für gleichzeitige Push "Sitzungen"? Wird es die entsprechenden pre-receive Hook-Instanzen strikt sequentiell ausführen oder besteht die Möglichkeit von Interleaving (was möglicherweise zu undefiniertem Verhalten führen kann)? Etwas sagt mir, dass es eine eingebaute Lösung für dieses Problem mit konkreten Garantien geben sollte, sonst wie würden Fernbedienungen im Allgemeinen (auch ohne komplexe Haken) dem gleichzeitigen Drücken unterworfen werden? Wenn es eine solche integrierte Lösung gibt, ist die Kopie redundant, und die einfache Wiederverwendung des blanken Repositorys würde die Verarbeitung beschleunigen. Übrigens ist jeder Hinweis auf offizielle Dokumente zu dieser Frage sehr willkommen.

Zweitens git-clang-format Prozesse nur inszeniert (aber nicht verpflichtet) ändert sich gegenüber spezifischen (HEAD Standardeinstellung) begehen. So können Sie leicht sehen, wo ein Eckfall liegt. Ja, es ist mit dem root commits (Revisionen). Tatsächlich kann git reset --soft 'HEAD~1' nicht auf root-Commits angewendet werden, da sie keine Eltern haben, auf die sie zurückgesetzt werden können. Daher ist die folgende Prüfung mit meiner zweiten Frage gibt:

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

Ich habe versucht, git update-ref -d 'HEAD' aber das bricht das Repository so dass git-clang-format nicht in der Lage ist, es nicht mehr zu verarbeiten. Ich glaube, dass dies mit der Tatsache zusammenhängt, dass all diese gedrängten Revisionen, die analysiert werden (einschließlich dieser Wurzel), eigentlich noch keinem Zweig angehören. Das heißt, sie sind in gelöstHEAD Zustand.Es wäre auch perfekt, eine Lösung für diesen Eckfall zu finden, so dass initial commits können auch die gleiche Prüfung von git-clang-format für die Einhaltung von Code-Stil Konventionen unterzogen werden.

Frieden.

Antwort

4

HINWEIS:
für die Suche nach einer up-to-date suchen, (mehr oder weniger) umfassende und bewährte Lösung, Host ich die entsprechende öffentliche Repository [1]. Derzeit sind die zwei wichtigen Hooks, die auf git-clang-format angewiesen sind, implementiert: pre-commit und pre-receive. Im Idealfall erhalten Sie den größtmöglichen Automatisierungsgrad und idiotensicheren Workflow, wenn Sie beide gleichzeitig verwenden. Wie immer sind Verbesserungsvorschläge sehr willkommen.

HINWEIS:
Derzeit ist die pre-commit Haken [1] erfordert die git-clang-format.diff Patch (wie auch von mir verfassten) [1] zu git-clang-format angewendet werden. Die Beispiele für Motivation und Anwendungsfälle für diesen Patch sind in der offiziellen Patch-Review-Einreichung an LLVM/Clang [2] zusammengefasst. Hoffentlich wird es bald angenommen und fusioniert.


Ich habe es geschafft, eine Lösung für die zweite Frage zu implementieren. Ich muss zugeben, dass es wegen der knappen Git-Dokumentation und der Abwesenheit von Beispielen nicht einfach war, sie zu finden. Schauen wir uns die entsprechenden Code-Änderungen einen Blick zuerst:

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

Wie Sie sehen können, anstatt immer wieder git reset --soft 'HEAD~1' tun, jetzt anweisen ich git-clang-format ausdrücklich gegen HEAD~1 mit der --commit Option für den Betrieb (während seine Standard HEAD ist, das war impliziert in der ursprünglichen Version in meiner Frage). Das löst das Problem jedoch immer noch nicht alleine, denn wenn wir root commit würden, würde dies wiederum zu einem Fehler führen, da HEAD~1 nicht mehr auf eine gültige Revision verweisen würde (ähnlich wie es nicht möglich wäre, git reset --soft 'HEAD~1' zu tun) . Aus diesem Grund weise ich in diesem speziellen Fall an, git-clang-format gegen den (magischen) 4b825dc642cb6eb9a060e54bf8d69288fbee4904 Hash zu agieren [3, 4, 5, 6]. Um mehr über diesen Hash zu erfahren, konsultieren Sie die Referenzen, aber kurz gesagt, es bezieht sich auf das Git leeres Baumobjekt - das, das nichts inszeniert oder begangen hat, das ist genau das, was wir in unserem Fall gegen git-clang-format arbeiten müssen.

HINWEIS:
Sie haben noch 4b825dc642cb6eb9a060e54bf8d69288fbee4904 auswendig zu erinnern und es ist besser, nicht zu hart Code es (nur für den Fall dieses magische Hash jemals in Zukunft ändert). Es stellt sich heraus, dass es immer mit git hash-object -t tree '/dev/null' [5, 6] abgerufen werden kann. Also, in meiner endgültigen Version der oben genannten Haken, habe ich stattdessen commit="$(git hash-object -t tree '/dev/null')".

P.S. Ich bin immer noch auf der Suche nach einer guten Antwort auf meine erste Frage. Übrigens habe ich diese Fragen auf der offiziellen Git-Mailingliste gestellt und bisher keine Antworten erhalten, was für eine Schande ...