L und M präsentiert sich im neuen Gewand
Pünktlich zum Neubau der Firmenzentrale in Flintbek präsentiert sich L und M, ein schon langjähriger Kunde vom networkteam, mit einem frischem und modernen Design.
Der L und M Relaunch basiert weiterhin auf dem WCMS TYPO3 und kann von Kundenseite aus weiter gepflegt werden. Das Design wurde modern und schlicht gehalten, wirkt aber dennoch durch einzelne Design-Elemente sehr erfrischend. Die Startseite sorgt für viel Animation und einer interaktiven Unternehmenspräsentation auf Basis von JavaScript.
Die verschiedenen Bereiche, die das IT Systemhaus L und M anbietet, werden durch einen animierten Bilderwechsel (Slideshow) vorgestellt. Auch die beiden Hauptbereiche, IT- und Drucklösungen, werden übersichtlich in einer "Tab-Box" dargestellt.
Der "Virtuelle Rundgang" dient, anhand des Neubaus in Flintbek, zur gesamthaften Darstellung der einzelnen Bereiche in Tooltips.
Lassen auch Sie sich durch das Unternehmen führen und besuchen Sie die L und M Website.
Moin moin
Moin moin
Ich möchte diese Gelegenheit nutzen, um mich als neues Mitglied des networkteam kurz vorzustellen.
Mein Name ist Thilo Kressdorf und ich bin nun seit knapp 2 Monaten ein Teil des networkteam und werde am 01.08.2011 eine Ausbildung zum Mediengestalter für Digital- und Printmedien beginnen. Parallel zu meiner Ausbildung habe ich am 15.05.2011 ein Studium im Bereich Grafik-Design an einer Hamburger Kunstschule begonnen.
Schon in der Schulzeit habe ich mich für Kunst, digitale Bildbearbeitung, Webdesign etc. interessiert. Und warum nicht das Hobby zum Beruf machen?
Nach einem ersten zweiwöchigen Praktikum war mir bereits klar, dass networkteam die richtige Agentur für mich ist.
Lockeres Betriebsklima, nette Kollegen, bestes Know-how und einen 1A Kaffee. Das Gesamtpaket war einfach perfekt: „Wo soll ich unterschreiben?“
Und so ist es dann auch geschehen.
Ich freue mich auf den Beginn meiner Ausbildung, neue Herausforderungen und weiterhin auf gute Zusammenarbeit.
FLOW3 und PHP Testing Tricks
Welcher Test ist der richtige?
In der Entwicklung mit FLOW3 spielen Tests eine große Rolle. Das Tests dabei wartbar und aussagekräftig sind, erfordert einiges an Erfahrung beim Test-Driven-Development. Wichtig ist zwischen den verschiedenen Arten von Tests zu unterscheiden und für den richtigen Einsatzzweck anzuwenden:
Unit-Tests sind ein wichtiger Bestandteil zur Steigerung der Qualität und für eine größere Sicherheit bei zukünftigen Änderungen. Dabei wird jeweils nur eine Code-Einheit (z.B. eine Klasse) ohne größere Abhängigkeiten getestet. Ein fehlgeschlagener Unit-Test kann die Frage nach dem genauen Ort eines Problems oder einer inkompatiblen Änderung beantworten. Auch sind Unit-Tests die eigentliche Grundlage für Test-Driven-Development mit einem Test-First-Ansatz (also zuerst einen Test schreiben der fehlschlägt, dann die Implementierung).
Aber: selbst wenn alle Unit-Tests laufen, heißt das noch nicht, dass das Gesamtsystem zuverlässig läuft. Ausserdem sind Unit-Tests aus meiner Erfahrung bei der ersten Implementierung von ganz neuen Features (z.B. ein neues Persistenz-Backend wie das CouchDB-Package) unzureichend und störend, da die Richtung der Entwicklung oftmals noch nicht klar ist und über viele Schichten des Systems hinweg gearbeitet wird.
Functional-Tests in FLOW3 sind ein noch recht neues Feature um das System mit (fast) allen Abhängigkeiten testen zu können. Dabei steht das komplette FLOW3 mit Dependency Injection, AOP etc. in einem speziellen Kontext Testing zur Verfügung. So kann z.B. einfach der Versand von E-Mails oder die Datenbankverbindung für Tests umkonfiguriert werden.
Gerade für das Implementieren neuer Features können am Anfang gut Functional-Tests für eine automatische Überprüfung der Ziele benutzt werden. Wer im Browser in einer Webanwendung immer dieselben Schritte macht bis der Code das richtige tut, sollte vielleicht überlegen, ob Functional-Tests nicht Zeit einsparen könnten.
Der wichtigste Punkt für Functional-Tests ist für mich aber die Sicherheit, dass ein Feature im kompletten System mit allen Packages und Abhängigkeiten funktioniert. Generell sollte für jedes Szenario einer User-Story, also jedes zentrale Feature einer Applikation ein Functional-Test hinterlegt werden. Dabei ist es nicht wichtig jeden Ausführungspfad zu testen, das können auch Unit-Tests erledigen.
Functional-Test Tricks
1. Partielles Mocken von Objekten
Dabei werden Abhängigkeiten eines Objekts zum Teil durch ein Mock ersetzt. Nützlich für schwierig zu konfigurierende und zu testende Abhängigkeiten, z.B. Mailversand, externe Webservices oder Simulation eines Requests (die Helper-Methode sendWebRequest der FunctionalTestCase-Klasse nutzt selber diesen Trick). Richtig angewendet kann man damit zwar viele Schichten des Systems testen, aber bestimmte Abhängkeiten ausschließen.
2. Fixture Factories für Testdaten
Natürlich brauchen Functional-Tests irgendwann auch ein fertig instanziiertes Model als Grundlage. Recht lästig wird es, wenn jedesmal bestimmte Eigenschaften gesetzt werden müssen und dieses dann in verschiedenen Tests wiederholt wird. Auch Tests sollten nicht zu viel Redundanz aufweisen. Änderungen am Model und Änderungen in der Validierung verursachen dann viel Arbeit.
Model. Bauen. Redundanz? Da gibt es doch dieses Factory-Pattern? Genau. Ein wunderbarer Anwendungsfall. Mit ein bisschen Flexibilität gepaart, können durch eine Fixture-Factory viele Zeilen Testcode vereinfacht werden.
Nehmen wir z.B. ein Customer-Model für Kundendaten. Eine solche Factory könnte eine Methode buildValidCustomer bereitstellen, die ein Objekt baut und gleich mit den richtigen Beispiel-Eigenschaften versieht. Wenn dann noch Eigenschaften in einem Array zum Überschreiben übergeben werden können, sind auch Tests mit Abhängigkeiten auf bestimmte Werte in Eigenschaften gut lesbar und unterschiedliche Varianten eines Objektes möglich.
Mit einer zusätzlichen Method createValidCustomer könnte man dann auch das Repository im Functional-Test sparen und das Objekt direkt zum Repository hinzufügen.
class Customers { 'customerNumber' => '123456', 'emailAddress' => 'john.doe@example.com', 'firstname' => 'John', 'lastname' => 'Doe' ); $customer = new \F3\MyPackage\Domain\Model\Customer(); foreach ($properties as $propertyName => $propertyValue) { if (\F3\FLOW3\Reflection\ObjectAccess::isPropertySettable($customer, $propertyName)) { \F3\FLOW3\Reflection\ObjectAccess::setProperty($customer, $propertyName, $propertyValue); } } return $customer; } }
class CustomersTest extends \F3\FLOW3\Tests\FunctionalTestCase { /** * @var \F3\MyPackage\Tests\Function\Fixtures\Domain\Model\Customers */ protected $customers; public function setUp() { parent::setUp(); $this->customers = $this->objectManager->get('F3\MyPackage\Tests\Function\Fixtures\Domain\Model\Customers'); } /** * @test * @expectedException \F3\FLOW3\Persistence\Generic\Exception\ObjectValidationFailedException */ public function customerEmailAddressHasToBeUnique() { $this->customerRepository->add($customer1); $this->customerRepository->add($customer2); $this->persistenceManager->persistAll(); } }
Was uns direkt zum nächsten "Trick" bringt: dem effektiven Testen von Exceptions in Tests.
4. Test von Exceptions
PhpUnit bringt eine einfache Möglichkeit mit sich Exceptions zu erwarten. Ein mit @expectedException (wie im Beispiel oben) annotierter Test stellt sicher, dass eine Exception mit dem angegebenen Typ geworfen wurde.
Was aber, wenn die Eigenschaften einer Exception relevant sind? Nehmen wir z.B. eine Exception, die bei Validierungen geworfen wird, und die ein Validierungsergebnis (F3\FLOW3\Error\Result) beinhaltet. Wenn ein Test sicherstellen soll, dass die richtige Eigenschaft als Fehlerhaft markiert wurde, macht es Sinn, die Exception abzufangen, die Testerwartung durch ein Assert anzugeben und die Exception wiederum zu werfen, damit ein @expectedException für uns den Rest erledigt und den Test gut lesbar dokumentiert.
class CustomersTest extends \F3\FLOW3\Tests\FunctionalTestCase { // ... Set up dependencies /** * @test * @expectedException \F3\MyPackage\Exception\ValidationException */ public function createCustomerWithMissingEmailAddressThrowsValidationException() { try { $this->customerService->createCustomer($this->customers->buildValidCustomer('emailAddress' => '')); } catch (\F3\MyPackage\Exception\ValidationException $exception) { $this->assertTrue($exception->getResult()->forProperty('emailAddress')->hasErrors()); throw $exception; } } }
Testen testen testen ...
Es gibt noch viele weitere interessante Bereiche des Testings von Webanwendungen mit PHP und FLOW3. Dazu gehört sicherlich auch die richtige Anwendung von Mock-Objects und vor allem das Schreiben von aussagekräftigen und lesbaren Tests. Es bleibt also noch Platz für den einen oder anderen Folgeartikel...
Aus aktuellem Anlass: Wir suchen erfahrene PHP-Entwickler, die unser Team verstärken wollen und Lust auf spannende PHP-Projekte der nächsten Generation z.B. mit FLOW3 Entwicklung haben.
Dependency Injection in Extbase
Dependency Injection
Dependency Injection ist ein Entwurfsmuster (Design Pattern), dass helfen soll, Abhängigkeiten eines Objektes in der objekt-orientierten Programmierung einfacher aufzulösen. Es findet eine Umkehr der Steuerung (Inversion of Control) statt, um das Objekt von unnötigen Verbindungen zu seiner Umwelt zu befreien, die es nur für die Auflösung von Abhängigkeiten, nicht aber für seine eigentliche Aufgabe benötigt. Die Verantwortung für das Auflösen der Abhängigkeiten wird aus dem Objekt in das umliegende Framework, in unserem Falle Extbase, verlagert.
Warum sollten wir Dependency Injection nutzen
Wenn wir ein Objekt (bspw. einen Controller) haben, das eine externe Abhängigkeit (bspw. einen Logger-Service) hat, gibt es verschiedene Wege, diese aufzulösen.
Wir könnten in unserem Objekt einfach t3lib_div::makeInstance('LoggingService'); aufrufen. Das hat aber den Nachteil, dass wir die konkrete Klasse des gewünschten Services angeben müssen. Machen wir dies nun in 30 Objekten und aus unserem LoggingService wird irgendwann ein FileLoggingService, haben wir viel Änderungsarbeit vor uns, da wir alle Einbindungen ändern müssen.
Besser ist es also, dem Objekt per Dependency Injection die korrekte Implementierung geben zu lassen, ohne dass das Objekt überhaupt wissen muss, welche Implementierung das ist. Für die Dependency Injection reicht es, dem Objekt zu sagen, dass es irgendeine Implementierung von LoggingServiceInterface bekommt. An einer zentralen Stelle, kann konfiguriert werden, welche Implementierung genutzt werden soll und das Framework kümmert sich dann darum, die richtige Abhängigkeit zu initialisieren und über eine Inject-Methode in das Objekt zu übergeben.
Wie nutzen wir Dependency Injection in Extbase?
In Extbase wird, wie beim Vorbild FLOW3, die Dependency Injection durch den ObjectManager geregelt*. Jedes Objekt, das per objecktManager->get(), objectManager->create() oder selber per Dependency Injection initialisiert wurde, kann Dependency Injection nutzen.
Per TypoScript kann (im Moment nur systemweit, nicht pro Extension) konfiguriert werden, welche Implementierung genutzt werden soll.
config.tx_extbase.objects.Tx_MyExtension_Service_LoggerServiceInterface.className = Tx_MyExtension_Service_FileLoggerService
Sogar das überschreiben konkreter Klassen in der Dependency Injection ist möglich, wenn der ursprüngliche Autor keine Interfaces verwendet hat. Hierbei ist natürlich besonders darauf zu achten, dass die gewählte Implementierung die benötigten Methoden anbietet.
config.tx_extbase.objects.Tx_MyExtension_Service_DatabaseLoggerService.className = Tx_MyExtension_Service_FileLoggerService
Im Code verwenden wir Inject-Methoden um die von Extbase gelieferten Objekte in Variablen zu speichern.
/** * @var Tx_MyExtension_Service_LoggingServiceInterface */ protected $loggingService; /** * @param Tx_MyExtension_Service_LoggingServiceInterface $loggingService * @return void */ public function injectLoggingService(Tx_MyExtension_Service_LoggingServiceInterface $loggingService) { $this->loggingService = $loggingService; }
Anhand der Annotation erkennt Extbase, welches Interface implementiert werden soll. Über die TypoScript-Konfiguration wird das konkrete Objekt aufgelöst, initialisiert, auf dem neuen Objekt ebenfalls Dependency Injection ausgeführt und dann die Inject-Methode aufgerufen und das entsprechende Objekt übergeben.
* Die eigentliche Logik der Dependency Injection passiert in der Container-Extension von Daniel Pötzinger. Diese wurde vom Extbase-Team übernommen, abgeändert und direkt in Extbase eingegliedert. Die Logik wird aber durch den ObjectManager abstrahiert und nur dieser ist öffentliche API. Es sollte also auf jeden Fall nur der ObjectManager und nie der ObjectContainer direkt verwendet werden.
Tipps und Tricks
Gibt es für ein Interface nur eine gleichnamige Implementierung, muss man diese nicht konfigurieren. Wenn man also
Tx_MyExtension_Service_LoggerServiceInterface
durch
Tx_MyExtension_Service_LoggerService
auflösen lassen möchte, kann Extbase dies automatisch erkennen und braucht dafür keine TypoScript Konfiguration.
Ausblick
Als Backport "erbt" Extbase all diese Vorgehen natürlich von FLOW3. Das ganze Prinzip des Objekt Managers und der Konfiguration (bei FLOW3 in Objects.yaml) wurde von FLOW3 zurückportiert. Einziger großer Unterschied: FLOW3 benötigt für die Konfiguration nur eine Member-Variable mit einer @inject-Annotation und keine extra inject-Methode.
FLOW3 hat sich aber inzwischen weiterentwickelt und verwendet den ObjectManager nicht mehr. Stattdessen wird der "new"-Operator von PHP genutzt. Die Änderungen dafür sind so umfangreich, dass eine Rückportierung in Extbase im Moment sehr unwahrscheinlich ist.
Mitarbeiten per Git und Gerrit
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. ;-)







