Git? Gerrit? Jenkins?

Am 26.01.2011 zogen Extbase, Fluid und das Blog Example aus der bisherigen Versionsverwaltung SVN nach Git um. Git bietet viele Vorteile gegenüber SVN, vor allem das dezentrale Arbeiten auf einer eigenen Kopie des Repositories (inklusive der gesamten Historie) und die viel flexiblere Behandlung von Branches.

Git bringt viel mehr Möglichkeiten mit sich - zu branchen, zu mergen, zu cherry-picken und zu squashen - und hat damit natürlich eine wesentlich steilere Lernkurve als SVN. Ich will euch hier einen kurzen Einstieg in Git geben, der euch alles an die Hand gibt, was ihr zum Arbeiten braucht. Man sollte sich aber nichts vormachen... auch nach Monaten mit Git (und Gerrit) lernt man immer noch ständig etwas Neues dazu. ;-)

Gerrit ist ein webbasiertes Tool zur Code-Qualitätskontrolle. Patches die man einreicht (pusht), werden nicht sofort in den allgemein zugänglichen Branch (master) integriert, sondern durch Gerrit abgefangen. Hier kann man sich die Patches angucken, Diffs erstellen und für oder gegen Patches voten. Erreicht ein Patch die nötigen positiven Votes ist davon auszugehen, dass er qualitativ gut ist und der Code wird in den allgemein zugänglichen Branch übertragen (gemergt).

Jenkins (ehemals Hudson) ist ein Tool, welches bei Änderungen von Gerrit benachrichtigt wird und daraufhin die Extension auscheckt um verschiedene Aufgaben darauf auszuführen. Vollautomatisch können so Unit- und Functional-Tests ausgeführt und bei positivem oder negativem Ergebnis entsprechend im Gerrit gevoted werden. Jenkins wird in diesem Text nicht weiter behandelt, weil er für das Mitarbeiten an Extbase erstmal nicht weiter von Belang ist. Dazu wird es später einen eigenen Blog-Beitrag geben.

Dieser Artikel ist vor allem für Leute geschrieben, die selber per Git und Gerrit an Extbase mitarbeiten und Patches einreichen wollen.

Kopie der Extension anlegen

Als erstes loggt man sich mit seinen typo3.org-Zugangsdaten in Gerrit ein. Dort hinterlegt man unter "Settings" - "SSH Public Keys" seinen öffentlichen Schlüssel.

Als nächstes "clont", also kopiert, man sich das Repository der Extension. Dabei wird das ganze Repository kopiert, man hat also danach alle Änderungen und die gesamte History lokal verfügbar. Man kann Logfiles durchgucken, Diffs erzeugen und auf ältere Versionen zurücksetzen, ohne mit dem Internet verbunden zu sein. Gerrit verwaltet die Repositories und steuert, welche Patches enthalten sind und welche nicht.

git clone ssh://[typo3.org username]@review.typo3.org:29418/TYPO3v4/CoreProjects/MVC/extbase

Lokale Umgebung konfigurieren

Als nächstes muss man seine lokale Umgebung für die Arbeit mit Gerrit konfigurieren. Dies muss man für jede neu geclonte Extension wiederholen.

Man muss dem lokalen Git sagen, dass man seine Patches nicht in den allgemein Branch (master) schieben möchte, sondern in einen speziellen Branch, der von Gerrit überwacht wird. Das Schieben in den Master-Branch würde fehlschlagen, weil man darauf keine Schreibrechte besitzt. (Das trifft nur auf Gerrit und der Projekt-Admin zu.)

Außerdem fügt man einen sogenannten Commit-Hook hinzu. Dieser erzeugt für jeden Commit eine eigene Change-Id über die der Gerrit Commit einem Changeset zuordnen kann.

Zuletzt kann man Git noch sagen, dass es Änderungen an den Dateiberechtigungen nicht als Änderungen erkennen und committen soll und über die Datei .gitignore lassen sich bestimmte Ordner und Dateien von der Versionierung ausschließen.

# kann einfach komplett in die Kommandozeile kopiert werden
git config --add remote.origin.push HEAD:refs/for/master
scp -p -P 29418 review.typo3.org:hooks/commit-msg .git/hooks/
git config core.filemode false
echo ".svn" >> .gitignore
echo ".idea" >> .gitignore
echo ".DS_Store" >> .gitignore
echo "*.swp" >> .gitignore
echo "nbproject" >> .gitignore
echo ".gitignore" >> .gitignore

Einen eigenen Patch einreichen

Wenn man nun ein Feature oder einen Bugfix entwickeln möchte, erstellt man am besten einen neuen Branch. Branches sind in Git schnell erstellt (und ebenso schnell wieder gelöscht), tauchen in den Logs nicht auf und halten die ganze Sache etwas übersichtlicher. Die Branches können mit

git branch -l

angezeigt (der mit dem Sternchen ist der momentan Aktive), mit

git branch [branchname]

gewechselt und mit

git checkout [branchname]

aktiviert werden. Mit

git checkout -b [branchname]

können wir den Branch erstellen und direkt in ihn wechseln. Es empfiehlt sich natürlich, einen aussagekräftigen Branchnamen zu wählen. Eine Methode ist z.B. eine Ticket-ID aus dem Bugtracker oder einen Kurznamen für das Feature zu wählen.

Wenn man einen neuen Branch erstellt, wird immer eine Kopie des Standes gemacht, in dem man sich gerade befindet. Wenn man an verschiedenen Codestellen entwickeln will die nicht voneinander abhängen, sollte man darauf achten, alle seine Branches immer vom Master aus zu erstellen.

Wenn man nun in seinem Branch Änderungen vorgenommen hat, fügt man diese mit

git add Folder/Filename.html

zu einem Commit hinzu. Was sich geändert hat, sieht man mit

git status

Hat man alle Änderungen, die man innerhalb eines Commits sichern möchte, zusammen, committed man mit

git commit

Git öffnet daraufhin, soweit konfiguriert, einen Editor zum Erstellen der Commit-Nachricht. Bei dieser sollte man sich an den Coding Guidelines orientieren. Schließt man diesen, fügt Git (wie vorher konfiguriert) der Commit-Nachricht eine eindeutige Change-Id zu und committed die Änderungen in das lokale Repository. Diese Änderungen werden nur lokal gespeichert und nicht an Gerrit gesendet. So kann man ganz beruhigt alle x Stunden oder jeden Abend committen, ohne das jemand anderes bereits die Änderungen zu Gesicht bekommt.

Wenn wir die Änderungen später an Gerrit schicken, wird aus jedem Commit ein eigener Change erstellt. Es empfiehlt sich also, für jede Änderung (Bugfix oder Feature) nur ein Commit zu haben. Git bietet hierfür im Gegensatz zu SVN die perfekten Voraussetzungen, denn es kann Commits nachträglich verändern. Mit

git commit --amend

bewegen wir Git dazu, die noch nicht committeten Änderungen in den letzten Commit zu integrieren. Mit

git commit --amend -C HEAD

braucht man sogar nichtmal die Commit-Nachricht erneut abspeichern. Weil dieser Aufruf ziemlich unhandlich ist, kann man mit

git config --global alias.amend 'commit --amend -C HEAD'

einen Alias hinzufügen (system-weit) und von nun an mit

git amend

seine aktuellen Änderungen zum letzten Commit hinzufügen.

Wenn man nun seine Änderungen fertig entwickelt und jedesmal zum Commit hinzugefügt hat, wird es Zeit, den Commit an Gerrit zu schicken, damit andere ihn in Augenschein nehmen können. Mit

git push

schicken wir alle Commits des aktuellen Branches, die es im Gerrit noch nicht gibt, an Gerrit. Wenn wir einen Branch von Master erstellt und alle unsere Änderungen immer angehängt (amandet) haben, wird nur ein Commit an Gerrit gesendet. Es ist auch möglich zwei Features, bei denen das Zweite auf dem Ersten basiert, in einem Branch zu entwickeln. Dann hat man zwei Commits und beim Pushen werden beide an Gerrit übertragen.

Was passiert mit meinem Patch

Ist die Änderung an Gerrit gesendet (gepusht), wird diese im Webinterface als offene Änderung angezeigt. Änderungen sind in Projekten organisiert und eventuell erhalten Andere, die das entsprechende Projekt beobachten, eine E-Mail dass eine neue Änderung vorliegt.

Die Änderung wird im Web-Frontend dargestellt und lässt sich in verschiedenen Einstellungen betrachten (z. B. Diff +-3, Alles). Jeder kann nun direkt im Code Kommentare hinzufügen und für seinen Patch positiv oder negativ voten. Wie genau das Voten funktioniert, wird weiter unten unter "Einen Patch reviewen" erklärt.

Die Änderung lässt sich nun von anderen explizit auschecken um sie zu testen. Auch hier gibt es verschiedene Möglichkeiten, die weiter unten beschrieben werden. Wichtig ist, dass die Änderung im Moment noch nicht in der normalen Verteilung von git.typo3.org enthalten ist. Erst wenn die Änderung von genügend (und den richtigen) Leuten positiv bewertet wurde, wird sie in den Master-Branch gemergt und ist für alle verfügbar. Es besteht also keine Gefahr (mehr), dass eine Änderung die man sendet, irgendetwas kaputt macht.

Screenshots

Mein Patch wurde abgelehnt :-(

Keine Sorge, sowas passiert den Besten.

Es ist ganz normal, dass Änderungen abgelehnt werden, weil sie kleinere Fehler (CGL, Dokumentation) enthalten. Man muss dann eine neue Version vorbereiten und einschicken. Dies kann auf zwei Wege geschehen.

Wenn man die Änderung lokal noch gut erreichbar hat, kann man sie direkt anpassen. Gut erreichbar heißt, dass es noch keine weiteren (gepushten) Änderungen gibt, die auf dieser Änderung basieren. Man will diese Änderung ja anpassen und das würde in allen darauf basierenden Änderungen eine Nachbearbeitung erfordern. Wenn die Änderung aber als Letzte in einem Branch existiert, kann man sie direkt auschecken und nachbearbeiten.

Wenn die Änderung vor vielen anderen im Branch liegt oder lokal gar nicht verfügbar ist, kann man sich auch aus Gerrit eine saubere Kopie herstellen lassen. Dabei ist es egal, ob man seine eigene oder die Änderung eines Anderen bearbeiten will. Wie das geht wird unter "Ich will auch meinen Senf dazu geben" beschrieben.

Mit

git checkout [branchname]

bringt man seine Kopie auf den Stand der Änderung. Man passt nun die Sachen an, die im Gerrit moniert wurden und committed sie mit

git commit --amend

in die Änderung. Es ist wichtig, in der Commit-Nachricht die Change-Id zu erhalten, da Gerrit die neue Einsendung sonst nicht der Alten zuweisen kann. Man darf sich beim Commit nicht wundern, da man ja die Anpassungen in die letzte Änderung mit hinein committed, werden nochmal alle Dateien aufgeführt, die sich insgesamt geändert haben, nicht nur die, die man gerade angepasst hat.

Jetzt kann man mit

git push

den geänderten Commit wieder an Gerrit schicken. Dieser wird doch im selben Change als neuer Patch angezeigt und das Reviewen / Voten beginnt von vorne. Diese Prozedur wiederholt sich dann, bis das Ganze letztendlich positiv bewertet wird und in den Master-Branch kommt. Danach kann dieser Commit nicht mehr verändert werden und eventuelle Nachbesserungen müssen in einem neuen Commit eingereicht werden.

Ich will auch meinen Senf dazu geben

Auch wenn es vielleicht eine Hürde ist, Code von anderen zu kritisieren und sie aufzufordern, Sachen anders zu machen, ist das sehr wichtig. Im Moment ist das so genannte "reviewen", also das Durchgucken, Testen und Bewerten von Änderungen sehr wichtig. Leute, die Code schreiben und Änderungen einschicken gibt es viel mehr, als Leute, die sich die Mühe machen die Änderungen durchzugucken. So häufen sich im Gerrit regelmäßig große Mengen an eingereichten aber nicht gereviewten Änderungen.

Um eine Änderung zu reviewen, checkt man die gewünschte Änderung in seine Instanz aus. Im Gerrit finden sich über der Änderung dazu verschiedene Möglichkeiten. Interessant sind erstmal die folgenden zwei:

Checkout: Holt den kompletten "Baum" von Änderungen bis zum zur zu testenden Änderung. Man baut damit also die komplette Umgebung inklusive Abhängigkeiten nach. Das ist vor allem sinnvoll, wenn man eine Änderung testen möchte, die auf anderen noch nicht im Master-Branch verfügbaren Änderungen basiert.

Cherry-Pick: Holt die aktuelle zu testende Änderung alleine und versucht diese auf den aktuellen Stand des lokalen Repositorys zu portieren. Das ist vor allem sinnvoll, wenn man eine alleinstehende Änderung testen möchte, die eventuell auch auf einem älteren Stand entwickelt wurde und in diesem Zuge gleich auf den aktuellen Stand angepasst wird.

Der größte Unterschied zwischen diesen beiden ist, dass beim Cherry-Picken (was man übrigens auch lokal zwischen verschiedenen Branches machen kann) ein neuer Commit erzeugt wird. Der Commit wird sozusagen auf den aktuellen Branch "kopiert". Die Abhängigkeit ist danach auf den letzten Commit des aktuellen Branches und der SHA-1 ändert sich. Trotzdem erkennt Gerrit eventuelle Neu-Einsendungen anhand der Change-Id.

Nun kann man die Änderung testen und danach in Gerrit voten. Man kann natürlich auch selber Änderungen vornehmen und neu einsenden (siehe "Mein Patch wurde abgelehnt"). Es ist also nicht notwendig, dass eine Person die Änderung betreut, sondern es kann auch gemeinschaftlich daran gearbeitet werden.

Außerdem ist ganz ohne Auschecken das Durchgucken des Codes online per Web-Interface möglich. Man klickt durch die Dateien, guckt sich den Code an und hinterlässt gegebenenfalls Kommentare durch Doppelklick auf eine Zeile.

Am Ende (nach Kommentieren und Voten) klickt man über der Änderung auf "Review" und sendet seine Anmerkungen ein.

Das Voting in Gerrit

Um die kollaborative Arbeit per Git zu unterstützen, gibt es in Gerrit ein Voting-System. Dabei sind zwei Stimmen zu unterscheiden.

Verified: Gibt Aufschluss über die Lauffähigkeit des Codes. Wenn der Code ausgecheckt wurde und fehlerfrei funktioniert, voted man "verified" (+1), falls Fehler auftreten "Fails" (-1). Wenn man "nur" den Code gelesen und kommentiert aber nicht getestet hat, voted man hier mit "No Score" (0). Verified entspricht dem alten "by testing" in der Core-Liste.

Code Review: Gibt Aufschluss über die Code-Qualität. Normale Benutzer können zwischen "I would prefer that you didn't submit this" (-1), "No Score" (0) und "Looks good to me, but someone else must approve" (+1) voten. Diese Votings geben aber nur eine Orientierung und reichen nicht, um eine Änderung in den Master-Branch zu bringen. Team-Mitglieder haben zusätzlich die Möglichkeit die Änderung per "Do not submit" (-2) Voting zu verwerfen oder per "Looks good to me, approved" (+2) zum Master-Branch hinzuzufügen. Die Votings werden nicht addiert, zwei +1 Votings ersetzen also nicht ein +2 Voting. Das heißt die Votings von Team-Mitgliedern können nicht überstimmt werden. Code Review entspricht dem alten "+1 by reading" in der Core-Liste.

Um eine Änderung in den Master-Branch zu bringen, ist (im Moment) ein Verifiy und ein +2 Code Review nötig, was nach dem alten Prinzip dem "+1 by testing" von irgendjemandem und einem "+1 by reading" eines Core-Entwicklers entspricht. Diese Regeln können sich natürlich mit der Zeit ändern.

Wie geht es weiter ...

Nachdem FLOW3 schon länger auf Git und Gerrit entwickelt wird und auch der Extbase-/Fluid-Umzug gut gelaufen ist, wurde im Zuge des Code Sprints 2011 in Berlin auch der TYPO3 4.x Kern auf Git / Gerrit portiert. Außerdem wird an einer Möglichkeit gearbeitet, alle Forge-Projekte über Git / Gerrit abzuwickeln.

Mittelfristig wird also unsere gesamte Entwicklung über diese Kombination von Tools laufen und es macht deswegen durchaus Sinn, sich schonmal damit anzufreunden. ;-)