Neulich berichtete ein Kommilitone von seinem Projekt. Da nutzten sie Subversion, und als sie einen neuen Branch aufmachen wollten, wurde einfach ein komplett neues Repository aufgemacht. Das ist natürlich auch unter Subversion nicht der vorgeschlagene Weg für Branches, aber mal ehrlich: Ist eine trunk–tags–branches-Struktur wirklich besser? Und vor allem, wie integriert sich so etwas in die im Artikel vorgeschlagene Kombination mit Mantis und CheckStyle?
In der Theorie kann auch Subversion mit divergierenden Bearbeitungen umgehen. In der Praxis hat sich jedoch gezeigt, dass es dabei kläglich versagt. Allerdings bestehen die Schwächen nicht nur dort. Gerade der Anwendungsfall, Bugs aus dem Tracker zu bearbeiten und die Patches dann einzuchecken, wird besonders schlecht unterstützt, denn Subversion trennt nicht die separaten Vorgänge Patch speichern (commit) und Patches veröffentlichen (push). Dies führt, vor allem in Unternehmen, sehr stark dazu, dass größere Änderungen in einem großen Patch gesammelt werden. Der Gedankengang dabei ist, dass man nicht bei jeder atomaren Änderung die gesamte Testsuite durchlaufen lassen möchte (bzw. auch gar nicht kann) und daher erst das Teilprojekt fertig stellt, um selbst nicht zum „Build breaker“ zu werden. Wie nun sollen aber mehrere an einem Teilprojekt arbeiten?
An dieser Stelle kommen verteilte Versionsverwaltungssysteme (DVCS) ins Spiel. Die Entwicklerschaft lässt sich grob in zwei Lager einteilen: Die einen, die das alles für neumodischen, komplizierten Unsinn halten. Und die anderen, die sich wundern, warum überhaupt jemand etwas anderes als DVCS verwendet. Üblicherweise haben Mitglieder letzteren Lagers auch mit Subversion oder gar CVS angefangen und sind dann an die Grenzen gestoßen. Im Folgenden möchte ich daher kurz erläutern, welche praktischen Nachteile Subversion konkret mit sich bringt, wie diese am Beispiel von Git ausgeglichen werden und welche wesentlichen Erleichterungen zusätzlich angeboten werden.
Was ist Git?
Git ist ein typischer Vertreter der DVCS-Gattung und neben Mercurial eines der am meisten verbreiteten Tools. Das Konzept beinhaltet, dass jeder Entwickler seine eigene Kopie der gesamten Projektgeschichte (also des kompletten Repositorys) hat. Änderungen bzw. Patches werden im ersten Schritt nur lokal gehalten, d. h. ein commit propagiert die Änderungen nicht automatisch an den zentralen Server.1 Im zweiten Schritt erst werden Mengen von Änderungen per push und pull propagiert. Gab es divergierende Entwicklungen, müssen diese zuerst gemerged werden, was Git ausgezeichnet gelingt, doch dazu später mehr. Der für Großprojekte weitaus größte Vorteil ist dabei, dass mehrere Repositorys unterschiedliche Aufgaben übernehmen können, ohne dass der zentrale Entwicklungszweig dadurch beeinträchtigt wird. Der Server mit den Integrationstests kann z. B. vorgeschaltet werden, bevor Änderungen in das Produktionsrepository gehen.
Warum nicht Subversion?
Wer bereits mit Subversion in größeren Projekten gearbeitet hat, merkt schnell, dass es zwei gängige Szenarien gibt, die sich oft wiederholen:
-
Eine Gruppe, die mit einem Teilprojekt betraut ist, denkt sich, dass es mit dem Branchen und Mergen doch so schwer nicht sein könne und erzeugt einen neuen Unterordner in branches. Dort wird dann gearbeitet und das Feature fertig gestellt. Am Ende, beim Mergen, stellt man fest: Konflikte. Und zwar solche, die sich trivial auflösen ließen, speicherte Subversion doch nur genügend Informationen über den Branch. Tatsächlich aber verwaltet Subversion in einer obskuren „Property“ namens mergeinfo, welche Revisionen schon gemerged wurden. In der Praxis reicht das aber nicht aus, und das ganze Team trifft sich an einem Computer, um innerhalb nur weniger Stunden sämtliche Konflikte aufzulösen. Zeit, die man auch hätte besser investieren können.
Git hingegen verwaltet einen gerichteten azyklischen Graphen (DAG) von Commits, d. h. zu jedem regulären Commit wird der Vorgänger gespeichert.2 Angenommen, zwei Teams arbeiten an verschiedenen Features und beginnen mit einer bestimmten Projektversion A. Beide Teams erzeugen jetzt ihre jeweilige Historie von Patches. Am Ende müssen diese beiden gemerged werden, was unter Git dadurch so einfach gelingt, dass die gesamte Historie immer verfügbar ist. Darüber hinaus verwaltet Git Inhalte wesentlich tiefgründiger als Subversion. So kann es unter anderem selbsttätig feststellen, wenn eine Datei umbenannt wurde (sogar, wenn nach der Umbenennung noch Bearbeitungen vorgenommen wurden).
Es gibt außerdem einige grafische Tools (z. B. gitk), die die Projekt-Historie grafisch darstellen. So können sich unerfahrene Nutzer leicht einen Überblick darüber verschaffen, wie sich das Projekt entwickelt hat.
-
In jedem Projekt gibt es weniger erfahrene Entwickler. Meist sind die verwendeten Tools am Anfang das größte Problem der Einarbeitung, z. B. auch Subversion. Ob sich derjenige nicht mit Properties auskennt, svn add/mv/cp vergessen hat oder ähnliches – trotz ausgefeilter Hooks, die Commits auf Plausibilität zu prüfen, kommt es häufiger vor, dass das gesamte Repository salopp gesagt vermüllt ist. Ein Beispiel dafür habe ich selbst erlebt: In einem Projekt gab es mal das Problem, dass Ant eigenmächtig den .svn-Ordner aus src nach build kopiert hat, was dazu führte, dass der Client total durcheinander kam und auf einmal .class-Dateien trotz gesetztem svn:ignore committen wollte. Prompt wollte das auch jemand machen, und den Rest kann man sich denken.
Bei Git wird man nicht dazu gezwungen, jedem Entwickler Schreibzugriff auf das zentrale Repository zu geben. Stattdessen kann man auch pull-basiert arbeiten: Jeder Entwickler hat ein privates und ein öffentliches Repository. Im privaten werden die Patches verwaltet und getestet; sobald man fertig ist, propagiert man zu seinem öffentlichen, wo nur der jeweilige „Eigentümer“ Schreibrechte hat. Anschließend sendet man einen pull request, d. h. fordert einen erfahreneren Mitarbeiter auf, die Änderungen vom eigenen, öffentlichen Repository zu übernehmen. Bei dieser Gelegenheit erhält man den Effekt des peer reviews gratis dazu. Der erfahrene Mitarbeiter kann dann die Änderungen akzeptieren und in das zentrale Repository kopieren oder einzelne Commits bearbeiten (nebenbei ist das komfortable Bearbeiten von Patches auch ein Alleinstellungsmerkmal von Git). Dies wird in fast allen Open-Source-Projekten so gehandhabt. Git ermöglicht es sogar, automatisiert Patches per E-Mail zu versenden.
Natürlich ist es auch möglich, mehreren Schreibzugriff zu geben. Dafür bietet sich z. B. Gitosis an, was mit öffentlichen SSH-Schlüsseln arbeitet und eine eigene Nutzerverwaltung mitbringt. So kann ein Server schnell aufgesetzt werden.
Oftmals wird Git in dem Punkt kritisiert, dass es schwer bis unmöglich sei, feingranulare Zugriffsrechte auf einzelne Unterordner im Repository zu vergeben. Dieser Einwand mag stimmen, zeugt aber von einem mangelnden Verständnis des Modells. Zum einen ist es bei DVCS allgemein geboten, nicht pro Unternehmen – wie bei Subversion üblich – ein einziges Repository zu verwalten, sondern vielmehr jedes Projekt einzeln zu verwalten. Git bietet einem dabei an, gemeinsam genutzte Bibliotheken als submodules in das Projekt einzubinden, ähnlich wie ein mount point.
Tool-Unterstützung
Um noch einmal auf den Anfang zurück zu kommen: Das im Artikel vorgestellte Mantis unterstützt auch Git, genau wie Subversion. Der populäre CI-Server Hudson beherrscht ebenfalls Git und kann auf Wunsch funktionierende Builds in ein besonderes Repository übertragen – bei Subversion undenkbar. Der große Vorteil hierbei: Commits sind weltweit eindeutig, da sie per SHA-1-Summe identifiziert werden; in Commit-Kommentaren oder im Bugtracker kann daher problemlos auch auf Commits aus anderen Branches verwiesen werden. Dies erkauft man sich mit kryptisch aussehenden Bezeichnern, die man sich im Gegensatz zu den Revisionsnummern von Subversion kaum merken kann. Praktisch entsteht daraus aber keine große Behinderung, da Revisionen viel einfacher und konsistenter getaggt werden können und in der Regel auch nur acht bis zehn Zeichen einer Revisionsbezeichnung für die eindeutige Zuordnung ausreichen.
Da, wie bereits gesagt, jeder Entwickler eine komplette Kopie der Geschichte lokal hat, kann Git mit einer ganz besonderen Nettigkeit aufwarten: git bisect. Kurz gesagt handelt es sich dabei um ein systematisches Vorgehen zum Aufspüren von Regressionen. Zu Beginn gibt man Git zwei Revisionen: Die erste bekannte nicht-funktionierende und die letzte bekannte funktionierende. Dann kann man sich Schritt für Schritt per binärer Suche durch den Patch-Graphen hangeln und ein Commit jeweils für funktionierend oder eben auch nicht befinden. Zum Schluss kann Git einem mitteilen, welcher Patch verantwortlich ist. Möglich ist hier auch ein automatisiertes Vorgehen, wobei ein Skript auf Grund von z. B. JUnit-Ergebnissen automatisch good oder bad entscheidet.
Fazit
Der ein oder andere mag jetzt immer noch denken, wie kompliziert das alles sei und dass man doch den Überblick verliere wenn jeder ein Repository hat, und überhaupt, Subversion reiche doch, die Probleme seien gar nicht so schlimm. Ich hingegen behaupte: Git ist einfacher zu erlernen als Subversion, da man für fast jeden Anwendungsfall eine stringente Repräsentation unter Git hat. Tag? git tag im Gegensatz zu svn cp trunk tags/…. Branch? git branch statt svn cp trunk branches/…. Revert? git revert statt kontra-intuitivem svn merge -r …. Diese Liste lässt sich beliebig fortsetzten. Ein bisher noch ungenannter praktischer Vorteil ist die viel höhere Geschwindigkeit von DVCS im Allgemeinen und Git im Speziellen. Es muss nicht erst eine Verbindung für Commits aufgebaut werden, und das Protokoll ist bei weitem besser als bei Subversion. Dank Kompression schafft Git es sogar, dass das .git-Verzeichnis3 in der Regel kleiner ist als die Summe der .svn-Verzeichnisse.
Hingegen denen, die ich überzeugen konnte, empfehle ich die zahlreichen gelungenen Git-Einführungen, zu finden auf der Website. Nicht vergessen: Die Überzeugung weitergeben. Es gibt noch viel zu tun!
-
Tatsächlich gibt es bei Git nicht einmal zwangsläufig einen zentralen Server. Der Einfachheit halber gehen wir jetzt einmal davon aus, dass ein solcher trotzdem existiert, z. B. für den Hauptentwicklungszweig eines Projekts. ↩
-
Beziehungsweise mehrere Vorgänger, falls es sich um einen sogenannten merge commit handelt, dessentwegen mindestens zwei verschiedene Zustände werden, daher gibt es auch mindestens zwei „Vorgänger“. ↩
-
Wovon es im übrigen nur ein einziges gibt, so kann Ant bei mir wenigstens keinen Unfug treiben. ↩