Technology

Adrian Kirchner
Erschienen am 28. September, 2009
Kommentare
0
Subversion Hooks mit PHP einsetzen

Subversion Hooks mit PHP einsetzen

Subversion (SVN) ist unumstritten eines der populärsten Versionsverwaltungssysteme auf dem Markt. Große Opensource Projekte wie z.B. PHP, Ruby uvm. nutzen Subversion als Versionsverwaltung.
Neben dem recht einfachen Einstieg bietet SVN eine Reihe von fortgeschrittenen Funktionen, die ein großes Maß an Kontrolle über den verwalteten Quelltext ermöglichen. Eine dieser Funktionen sind die sogenannten Hooks - Aktionen die zu einem bestimmten Zeitpunkt ausgelöst werden.

Was ist ein Hook?

Wie so oft hält die Wikipedia eine sehr treffende Beschreibung bereit:
In der Programmierung bezeichnet der Begriff Hook eine Schnittstelle, mit der fremder Programmcode in eine bestehende Anwendung integriert werden kann, um diese zu erweitern, deren Ablauf zu modifizieren oder um bestimmte Ereignisse abzufangen.

Wikipedia Hook (EDV)

SVN bietet also über Hooks die Möglichkeit den Ablauf zu modifizieren. Konkret bedeutet das, dass wir beispielsweise beliebige Programme anstoßen können, bevor ein Commit passiert (pre-commit)- so auch eine eigene PHP-CLI Applikation

Vorbereitung

Zunächst brauchen wir also eine Übersicht über die aktuell 9 verschiedenen Hooks. Jeder Hook wird an einer anderen Stelle ausgeführt, stellt andere Parameter zur Verfügung und reagiert anders auf die Exit Codes der ausgeführten Programme. Es folgt also zuerst eine Übersicht über die verfügbaren Hooks - durch einen Klick auf den jeweiligen Hook wird eine kurze Beschreibung, Art und Anzahl der Parameter sowie mögliche Einsatzzwecke eingeblendet

start-commit
Beschreibung

Der start-commit Hook wird ausgeführt bevor die Commit-Transaktion existiert. In der Regel wird er genutzt um die Rechte des Benutzers zu überprüfen
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt, wird der Commit gestoppt bevor die Commit-Transaktion erstellt wurde. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Username des commitenden Benutzers
  3. Doppelpunkt getrennte Liste der Funktionen die der Client an den Server schickt, einschließlich depth, mergeinfo und log-revprops (seit Subversion 1.5)
Möglicher Einsatz

Zugriffskontrolle

pre-commit
Beschreibung

Der pre-commit Hook wird ausgeführt bevor die Commit-Transaktion zu einer neue Revision führt. In der Regel wird er genutzt um den übermittelten Inhalt oder die log message zu validieren (Quelltext-Überprüfung, log message muss ticket id enthalten usw.).
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt, wird der Commit abgebrochen und die Commit-Transaktion entfernt. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Name der Commit-Transaktion
Möglicher Einsatz

Validierung und Kontrolle der übermittelten Änderungen sowie der log message.

post-commit
Beschreibung

Der post-commit Hook wird ausgeführt nachdem die Transaktion committed und eine neue Revision angelegt wurde. Meistens wird dieser Hook verwendet um zusammenfassende E-Mails zu verschicken oder andere Tools anzustoßen. Manchmal wird dieser Hook auch als Auslöser für Sicherungen genutzt.
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt, wird der Commit nicht abgebrochen da die Transaktion bereits beendet und die neue Revision angelegt wurde. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Neue Revisions-Nummer
Möglicher Einsatz

Commit Benachrichtigung, Tool Integration

pre-revprop-change
Beschreibung

Der pre-revprop-change Hook wird ausgeführt bevor eine Änderung einer Revisions-Eigenschaft außerhalb des Kontextes eines normalen Commits durchgeführt werden soll. Anders als bei anderen Hooks wird die gewünscht Aktion immer abgebrochen wenn dieser Hook nicht existiert oder der Rückgabewert nicht gleich null ist.
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt, nicht ausführbar ist oder der Hook nicht existiert wird die Änderung der Eigenschaft unterbunden. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Revision dessen Eigenschaft geändert werden soll
  3. Username des Benutzers der die Änderung eingeleitet hat
  4. Name der zu ändernden Eigenschaft
  5. Art der Änderung: A (added), D (deleted) oder M (modified)
Möglicher Einsatz

Zugriffskontrolle. Validierung und Kontrolle.

post-revprop-change
Beschreibung

Der post-revprop-change Hook wird ausgeführt nachdem eine Änderung einer Revisions-Eigenschaft außerhalb des Kontextes eines normalen Commits durchgeführt wurde. In der Regel wird er verwendet um Benachrichtigungen zu verschicken.
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt wird die Änderung der Eigenschaft nicht unterbunden da diese bereits vollzogen wurde. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Revision dessen Eigenschaft geändert wurde
  3. Username des Benutzers der die Änderung eingeleitet hat
  4. Name der geänderten Eigenschaft
  5. Art der Änderung: A (added), D (deleted) oder M (modified)
Möglicher Einsatz

Benachrichtigung

pre-lock
Beschreibung

Der pre-lock Hook wird ausgeführt bevor ein Pfad gesperrt(lock) werden soll. Er kann genutzt werden um das Sperren gänzlich zu unterbinden oder an Richtlinien zu binden. Er kann außerdem genutzt werden um bereits existierende Sperren von anderen Benutzern "klauen" zu lassen.
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt, wird die Sperrung abgebrochen. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Versionierter Pfad der gesperrt werden soll
  3. Username des Benutzers der eine Sperrung einleiten möchte
Möglicher Einsatz

Zugriffskontrolle

post-lock
Beschreibung

Der post-lock Hook wird ausgeführt nachdem ein Pfad gesperrt(lock) wurde. In der Regel wird er eingesetzt um Benachrichtigungen über die Sperrung zu verschicken.
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt, wird die Sperrung nicht abgebrochen da sie bereits durchgeführt wurde. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Username des Benutzers der eine Sperrung eingeleitet hat
Möglicher Einsatz

Benachrichtigung

pre-unlock
Beschreibung

Der pre-unlock Hook wird ausgeführt wenn eine Sperrung aufgehoben werden soll. Er kann benutzt werden um zu entscheiden welcher Benutzer Sperren entfernen darf (auch von anderen Benutzern).
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt, wird das Aufheben der Sperre abgebrochen. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Versionierter Pfad der entsperrt werden soll
  3. Username des Benutzers der die Sperre aufheben möchte
Möglicher Einsatz

Zugriffskontrolle

post-unlock
Beschreibung

Der post-unlock Hook wird ausgeführt wenn eine Sperrung aufgehoben wurde. In der Regel wird er eingesetzt um Benachrichtigungen über die Entsperrung zu verschicken.
Wenn das von dem Hook ausgeführte Programm einen Rückgabewert ungleich null zurück gibt, wird das Aufheben der Sperre nicht abgebrochen da die Entsperrung bereits passiert ist. Alle auf dem stderr-Stream getätigten Ausgaben werden an den Client übermittelt.

Input Parameter(s)

Die folgenden Kommandozeilen-Argumente, in der Reihenfolge der Auflistung, werden an das Hook Programm gesendet:

  1. Repository Pfad
  2. Username des Benutzers der die Sperre aufgehoben hat.
Möglicher Einsatz

Benachrichtigung



Was bedeutet stderr und was sind Exit Codes?

Standard Streams

Auf *nix-Systemen gibt es drei Standard Streams (Standard-Datenströme). Der stderr-Stream ist, wer hätte das gedacht, für Fehlerausgaben vorgesehen. Ein echo in PHP sendet auf dem stdout-Stream, dem Stream für Standardausgaben. Glücklicherweise stellt uns PHP eine simple Methode zur Verfügung um auf nahezu beliebigen Streams zu senden. So sendet zum Beispiel folgendes Listing ein simples "Fehler aufgetreten" auf dem stderr-Stream:

  1. <?php
  2.  
  3. file_put_contents('php://stderr', 'Fehler aufgetreten');

Exit Codes

Exit Codes auf *nix-Systemen sind ganzzahlige Rückgabewerte von 0 bis 255, die ein Prozess nach dessen Beendigung an die aufrufende Instanz zurück gibt. Über den Exit Code informiert der aufgerufene Prozess über Erfolg oder Misserfolg der eigenen Ausführung. Es gibt keinen Standard für die 256 möglichen Werte, nur die Konvention, dass der Exit Code 0 (null) für Erfolg steht und alles ungleich 0 Misserfolg bedeutet.
In PHP kann man den Exit Code über die Funktion exit(int $status) (gleichbedeutend zu die()) senden. Wird exit() ohne Parameter oder garnicht aufgerufen, so sendet das PHP-Script den Exit Code 0 (also erfolgreich) zurück. Der Exit Code 255 ist für den PHP-Prozess reserviert und sollte nicht verwendet werden.

Zusammenfassung

Wer auch nur eine der obigen Hook-Beschreibungen gelesen hat, versteht jetzt vielleicht, was mit dem Gespann Exit Code und stderr-Stream machbar ist:
Mit dem Exit Code können wir dem ausgewählten Hook mitteilen, ob dieser erfolgreich ausgeführt wurde und per stderr-Stream können wir dem SVN-Client per Text mitteilen, was im Falle eines Exit Codes ungleich null falsch gelaufen ist.

Hook Aktivierung

Ein SVN Repository sieht auf dem Dateisystem ungefähr wie folgt aus:
  1. entwicklung:repository AKirchner$ ls -al
  2. drwxr----- 2 AKirchner .. 4096 27. Sep 22:53 conf
  3. drwxr-S--- 6 AKirchner .. 4096 27. Sep 22:53 db
  4. -rwxr----- 1 AKirchner .. 2 27. Sep 22:53 format
  5. drwxr----- 2 AKirchner .. 4096 27. Sep 22:53 hooks
  6. drwxr----- 2 AKirchner .. 4096 27. Sep 22:53 locks
  7. -rwxr----- 1 AKirchner .. 229 27. Sep 22:53 README.txt
Das Verzeichnis hooks sollte folgende Hook Templates enthalten:
  1. entwicklung:repository AKirchner$ ls -al hooks
  2. -rwxr----- 1 AKirchner .. 2083 26. Sep 01:52 post-commit.tmpl
  3. -rwxr----- 1 AKirchner .. 1690 26. Sep 02:36 post-lock.tmpl
  4. -rwxr----- 1 AKirchner .. 2307 26. Sep 02:36 post-revprop-change.tmpl
  5. -rwxr----- 1 AKirchner .. 1606 26. Sep 02:36 post-unlock.tmpl
  6. -rwxr----- 1 AKirchner .. 2982 26. Sep 02:36 pre-commit.tmpl
  7. -rwxr----- 1 AKirchner .. 2038 26. Sep 02:36 pre-lock.tmpl
  8. -rwxr----- 1 AKirchner .. 2764 26. Sep 02:36 pre-revprop-change.tmpl
  9. -rwxr----- 1 AKirchner .. 1980 26. Sep 02:36 pre-unlock.tmpl
  10. -rwxr----- 1 AKirchner .. 2758 26. Sep 02:36 start-commit.tmpl
Um nun einen Hook zu aktivieren genügt es, die Endung .tmpl zu entfernen. Das in der Hook-Datei enthaltende Shell-Script wird ab sofort an entsprechender Stelle ausgeführt.
  1. $ mv post-commit.tmpl post-commit


Beispiel: post-commit

Dieses Beispiel zeigt die obige Theorie am Praxisbeispiel für einen post-commit Hook. Dazu aktivieren wir diesen Hook wie im vorangegangen Beispiel und passen den Inhalt der Datei post-commit so an, dass er in etwas wie im folgenden Beispiel aussieht
  1. #!/bin/sh
  2.  
  3. REPOS="$1"
  4. REV="$2"
  5.  
  6. /pfad/zum/hook/script/post-commit.php "$REPOS" "$REV"

Wie in der Auflistung der Hooks nachzulesen ist, enthält der post-commit Hook zwei Parameter. Das vorangegangene Shell-Script ruft das PHP-Script post-commit.php mit diesen beiden Parametern auf.

Ich habe im Folgenden ein paar Beispielscripte angefügt. Mit einem Klick auf den Dateinamen wird der Inhalt eingeblendet. Dort sind auch rudimentäre Kommentare enthalten, die allerdings zum Verständnis ausreichen sollten.

  1. #!/usr/bin/php
  2. <?php
  3.  
  4. include 'Stream.php';
  5. include 'Mail.php';
  6. include 'SVNLook.php';
  7.  
  8. try {
  9.  
  10. // Neue Instanz von SVNLook mit Übergabe des Repository Pfades
  11. $oRepoLook = new SVNLook($argv[1]);
  12. // Beliebige Benachrichtigung (hier per mail())
  13. Mail::send($oRepoLook->getAuthor(), 'Commit am '.$oRepoLook->getDate(), $oRepoLook->getMessage());
  14.  
  15. } catch(Exception $e) {
  16.  
  17. // Wenn irgendwas schief geht, wird die Exception-Message aus dem STDERR-Stream gesendet
  18. // SVN leitet diese Nachricht an den SVN Client weiter
  19. Stream::send($e->getMessage(), Stream::STDERR);
  20. // Exit Code ungleich Null um die Fehlermeldung dem SVN Client anzuzeigen
  21. exit(64);
  22.  
  23. }
Plain TextQuelltext herunterladen
  1. <?php
  2.  
  3. class Stream {
  4.  
  5. const STDOUT = 'php://stdout';
  6.  
  7. const STDERR = 'php://stderr';
  8.  
  9. public static function send( $sMessage, $sStream = self::STDOUT ) {
  10.  
  11. file_put_contents($sStream, $sMessage);
  12.  
  13. }
  14.  
  15. }
Plain TextQuelltext herunterladen
  1. <?php
  2.  
  3. class Mail {
  4.  
  5. public static function send( $sTo, $sSubject, $sMessage ) {
  6.  
  7. mail($sTo, $sSubject, $sMessage);
  8.  
  9. }
  10.  
  11. }
Plain TextQuelltext herunterladen
  1. <?php
  2.  
  3. class SVNLook {
  4.  
  5. protected $_sSVNLookPath = '/usr/bin/svnlook';
  6.  
  7. protected $_sRepoPath = '';
  8.  
  9. protected $_sAuthor = '';
  10.  
  11. protected $_sDate = '';
  12.  
  13. protected $_sMessage = '';
  14.  
  15.  
  16. public function __construct( $sRepoPath ) {
  17.  
  18. if( ! strlen($sRepoPath)) {
  19. throw new Exception('Repository Path fehlt');
  20. }
  21.  
  22. $this->_sRepoPath = $sRepoPath;
  23.  
  24. $this->_fetchLook();
  25.  
  26. }
  27.  
  28. protected function _fetchLook() {
  29.  
  30. // Kommando zusammensetzen
  31. $sCommand = $this->_sSVNLookPath .' info '. $this->_sRepoPath;
  32. // exec() gibt den Output aus STDOUT in einem Array zurück
  33. $aOutput = array();
  34. $iReturnVar = 0;
  35.  
  36. // Kommando ausführen
  37. exec($sCommand, $aOutput, $iReturnVar);
  38.  
  39. // Wenn Exit Code ungleich null, wird eine Exception geschmissen
  40. if($iReturnVar) {
  41. throw new Exception('SVNLook nicht erfolgreich');
  42. }
  43.  
  44. $this->_parseLookOutput($aOutput);
  45. }
  46.  
  47. protected function _parseLookOutput( $aOutput ) {
  48.  
  49. // Erste Zeile des Outputs enthält den Commit-Autor
  50. $this->_sAuthor = $aOutput[0];
  51. // Zweite Zeile des Outputs enthält das Datum
  52. $this->_sDate = $aOutput[1];
  53. // Ab der vierten Zeile folgt die log-message
  54. $this->_sMessage = implode("\n", array_slice($aOutput, 3));
  55. }
  56.  
  57. public function getAuthor() {
  58. return $this->_sAuthor;
  59. }
  60.  
  61. public function getDate() {
  62. return $this->_sDate;
  63. }
  64.  
  65. public function getMessage() {
  66. return $this->_sMessage;
  67. }
  68.  
  69. }
Plain TextQuelltext herunterladen


Wie wird ein PHP-CLI Script ausgeführt?

Grundsätzlich gibt es zwei Wege PHP-Scripte per Command Line Interface auszuführen (ja ja es gibt noch mehr..):

1.: Wie in der Beispiel-Datei post-commit.php: Dort wird der Pfad des zu nutzenen Interpreters in der ersten Zeile definiert (hier: #!/usr/bin/php). Anschließend fehlt nur noch das Ausführungsrecht (execute = x) und das Script kann wie folgt ausgeführt werden:
  1. $ chmod u+x post-commit.php
  2. $ ./post-commit.php

2.: Die andere Möglichkeit sieht vor, den Pfad des PHP-Scriptes direkt an den PHP-Interpreter zu übergeben (dabei ist das X-Recht nicht nötig):
  1. $ /usr/bin/php post-commit.php


Fazit

Mit dem Einsatz von Hooks hält man ein mächtiges Werkzeug in den Händen. Die Einsatzmöglichkeiten sind nahezu unbegrenzt jedoch nicht immer sinnvoll. Nils von PHP hates me hat sich in dem Artikel "Warum SVN Pre Commit Hooks böse sind!" Gedanken um den Einsatz des pre-commit Hooks gemacht.
Der Einsatz von PHP erleichtert vielen PHP-Entwicklern die Implementierung auch von komplexen Prüf-Abläufen.


Tags: - - - - - -
Adrian Kirchner

der autor

Adrian Kirchner

wissensdurstig unterwegs auch im hintersten Ende aller Systeme, spricht fließend PHP, oft nur für Sie.

0 Kommentare

Hinterlasse einen Kommentar

Kommentare zu diesem Artikel abonnieren (jederzeit kündbar)