Einfach den richtigen Flow finden

Branching-Modelle und Workflows für SCM-Systeme
Keine Kommentare

Source-Control-Management-Systeme (SCM) gehören zur Standardausrüstung eines Softwareentwicklers. Trotz der täglichen Verwendung dieser Werkzeuge gibt es viele hilfreiche Techniken, die weitgehend unbekannt sind. Es gibt eine große Anzahl von SCM-Werkzeugen. Dieser Artikel bezieht sich auf das frei verfügbare, verteilte SCM-Tool Git, das wegen seiner starken Verbreitung eine sehr hohe Relevanz besitzt. Viele der vorgestellten Praktiken lassen sich mit wenigen Anpassungen auch auf andere Lösungen wie beispielsweise Subversion anwenden.

Ein essenzieller Faktor im Umgang mit SCM-Werkzeugen ist zu wissen, worin deren Grenzen liegen. Diese Systeme wurden entwickelt, um Änderungen in ASCII-Textdateien zu protokollieren. Ein SCM speichert der Effizienz wegen für jede Datei, die unter Konfigurationsmanagement gestellt wurde, lediglich die Änderung zu Vorgängerversion, ein sogenanntes Delta. Daraus ergibt sich als interne Repräsentation eine Baumstruktur für jede Datei. Das hat aber auch zur Folge, dass bei Binärdateien, wie sie von Office-Suiten erzeugt werden, kein Delta bestimmt werden kann, sodass SCM bei jeder Änderung einer solchen Datei eine vollständig neue Version abspeichert. Das führt dazu, dass das Repository sehr schnell eine beachtliche Größe auf der Festplatte beansprucht. Die Übertragungszeit von Dateien zwischen dem Repository und einer lokalen Arbeitskopie ist direkt abhängig von der Größe des gesamten Repository, auch wenn nur eine kleine Untermenge der gespeicherten Dateien übertragen wird. Der Grund ist, dass das SCM bei jeder Abfrage den gesamten Baum des Repository erzeugt, um dann die verlangten Inhalte bereitstellen zu können. So lässt sich sehr schnell einsehen, dass die Größe des Source Repository aus Sicht der Performance möglichst gering gehalten werden sollte. Das erreicht man mit wenig Aufwand, indem man einige Regeln befolgt.

Binäre Artefakte wie externe Bibliotheken werden in speziellen Repository-Servern wie Sonatype Nexus oder Artifactory verwaltet und gehören aus diesem Grund nicht ins Git. Inhalte wie Präsentationen, Excel-Tabellen oder Word-Dokumente werden am besten auf einem zentralen Netzwerkspeicher aufbewahrt. In sogenannten Dokumentenservern wie Microsofts SharePoint sind Dokumente für kollaborative Anforderungen gut aufbewahrt. Man könnte der Idee nachgehen, mehrere Projekte in einem gemeinsamen Repository zu verwalten. Doch davon sollte man aus mehreren Gründen Abstand nehmen. Sicher ist eine solche Lösung sehr komfortabel, aber neben den Geschwindigkeitseinbußen spielen auch Überlegungen der Datensicherheit eine Rolle. Auch Kleinstprojekte in einem separaten Repository aufzubewahren reduziert bei Vergabe von Outsourcingaufträgen das Risiko, dass der Auftragnehmer sensible Firmengeheimnisse zu Gesicht bekommt. Zudem können interne Teams so leichter gesteuert werden. Eine feingranulare Rechteverwaltung ist mit diesem Ansatz wesentlich einfacher aufzubauen. Üblicherweise besitzen Source-Control-Management-Tools von Haus aus keine Benutzerverwaltung. Diese Anforderung wird in Suiten wie der von GitLab mit einem zusätzlichem Layer für die Access Control List (ACL) bereitgestellt.

Das führt uns auch gleich zu der Frage, welcher Client für die Arbeit mit Git empfehlenswert ist. Auf Windows-Systemen ist das freie TortoiseGit, das sich in den Windows Explorer integriert, weit verbreitet. SmartGit hingegen ist eine plattformunabhängige Anwendung mit einer eigenständigen GUI und für den privaten Gebrauch sowie als Open-Source-Benutzeroberfläche ebenfalls frei verfügbar. Während TortoiseGit recht intuitiv ist, benötigt SmartGit ein wenig Einarbeitungszeit. Dafür stellt SmartGit ein umfangreiches Set an Funktionalitäten bereit und unterstützt professionelle Anforderungen im Umgang mit SCM-Systemen durch gut strukturierte Assistenten. Ein weiteres charmantes Detail ist, dass SmartGit in Deutschland entwickelt wird. Aus persönlicher Erfahrung kann ich getrost behaupten, dass sich beide Anwendungen hervorragend ergänzen. Im Unternehmenseinsatz ist TortoiseGit eine sehr gute Wahl für Entwickler, während sich Konfigurations- beziehungsweise Build-Manager sehr schnell mit SmartGit wohlfühlen. Damit wollen wir es auch an dieser Stelle belassen und uns weiteren Begriffen zuwenden, die eine wichtige Rolle spielen.

Ausdrucksweisen

Sicherlich werden den meisten Entwicklern die im Folgenden dargestellten Erläuterungen geläufig sein. Dennoch ist es immer nützlich, den notwendigen Kontext ein wenig detaillierter zu beleuchten. Beginnen wir mit einer kurzen Ausführung, was eine Revision darstellt. Jede Übertragung, also jeder Commit, stellt eine sogenannte Änderung im Repository dar. Um die verschiedenen Änderungen unterscheiden zu können, spricht man im Generellen von Revisionen. Bei Subversionen wurden die einzelnen Revisionen durch eine einfache, inkrementierte Revisionsnummer gekennzeichnet. Git verfolgt eine etwas andere Strategie: Hier ist die Revisionsnummer ein Hash. Das eröffnet etwas mehr Flexibilität beim Branching und anderen Aktivitäten. Nicht zu verwechseln mit der Revision ist das Release. Ein Release bezeichnet ein definiertes Set an Funktionalitäten, das mehr oder minder erfolgreich umzusetzen ist. Das bedeutet, dass ein Release aus mindestens einer, aber auch beliebig vielen Revisionen bestehen kann.

Ein anderer Aspekt betrifft die interne Organisation eines Repository. Hier haben sich im Laufe der Jahre Begriffe aus dem Umfeld von Apache Subversion durchgesetzt. Auch wenn diese sich nicht exakt auf die verteilte Welt von Systemen wie Git oder Mercurial übertragen lassen, gibt es einige Überscheidungen. So wird der Hauptentwicklungszweig als Trunk bezeichnet. In Git sind die Details ein wenig komplizierter. Der Hauptentwicklungszweig in der lokalen Kopie des Repository wird hier als Master bezeichnet. Auf dem Server hingegen heißt dieser Zweig Origin. Diese Konventionen sind Standardwerte, die durchaus verändert werden können, was aber weniger zu empfehlen ist.

Ausgewählte Revisionen, wie beispielsweise Releases, sind von besonderem Interesse. Um diese bei Bedarf schnell wiederfinden zu können, besteht die Möglichkeit, Lesezeichen zu setzen. Diese Lesezeichen werden auch als Tag bezeichnet. In diesem Kontext sind auch die Verzweigungen zu nennen, die sich vom Hauptentwicklungszweig abspalten und als Branch bezeichnet werden. Es besteht natürlich die Möglichkeit, von einem Branch beliebig viele weitere Branches zu erzeugen und diese später auch wieder zusammenzuführen. Der Vorgang einer Vereinigung wird als Merge bezeichnet, womit wir auch zum nächsten Punkt voranschreiten wollen.

Teile und herrsche

Auch wenn das Teilen ein recht trivialer Vorgang sein mag, kann es – unüberlegt durchgeführt – ein späteres Zusammenführen der Teile schier unmöglich machen. Das Standardvorgehen ist in Abbildung 1 ersichtlich. Eine grundlegende Annahme in dieser Grafik ist ein Releaseprozess, der das Semantic Versioning berücksichtigt. Dabei bezeichnet das Post-fix SNAPSHOT ein Artefakt, das sich in der Entwicklung befindet. Der Hauptentwicklungszweig startet in der Version 1.0 und wird nach einem Release für die Version 1.1 fortgeführt. Werden nun nach der inkrementierten Version 1.1 für die Vorgängerversion Fehlerkorrekturen notwendig, wird von der Revision, die das Release 1.0 darstellt, ein Branch erzeugt. In diesem Branch werden die Korrekturen durchgeführt, es wird keine neue Funktionalität in Version 1.0 eingebracht. Nach erfolgreicher Korrektur wird das Ergebnis aus dem Branch wieder in den Trunk überführt. Dieses Vorgehen verhindert, dass neue Funktionalitäten der Version 1.1 in die Version 1.0 ungewollt übernommen werden. Folgt man der hier vorgestellten Strategie, lassen sich viele Aktivitäten weitestgehend automatisieren.

In Build-Umgebungen mit CI-Servern verzichtet man auf das Erzeugen neuer Branches, bis sie notwendig werden. Das verringert den Verwaltungsaufwand der CI-Infrastruktur. Für Entwickler trifft diese Aussage allerdings nicht zu. Das leichte Erzeugen und Verwerfen von Branches ist auch eine der Stärken von Git. Das wollen wir mit den Branch-Modellen ein wenig weiter vertiefen. So hat das in Abbildung 1 beschriebene Branch-by-Release-Modell ein Defizit, das die Entwicklung von parallelen Funktionen nicht berücksichtigt. Sogenannte Feature-Branches werden dann notwendig, wenn die umzusetzenden Funktion nicht während eines Releasezyklus abgeschlossen werden kann. Es können jederzeit, je nach Bedarf, aus dem Trunk aktuelle Revisionen in den entsprechenden Feature Branch überführt werden. Ein Merge vom Feature Branch auf den Trunk erfolgt frühestens für das Release, für das das Feature vorgesehen wurde. Bei aufwendigen und risikoreichen Funktionen versucht man, den Zeitpunkt eines Merge möglichst hinauszuzögern, um bei Problemen und Verzögerungen kein Rollback durchführen zu müssen. Ein Testlauf eines solchen Vorhabens lässt sich auch lokal über eine Integrations-Branch durchführen, um zu sehen, wie gut die einzelnen Fragmente ineinander greifen.

Um bei allen Verzweigungen den rechten Überblick wahren zu können, ist es auch notwendig, sich auf eine Nomenklatur der jeweiligen Branches zu einigen. Eine bewährte Methodik ist die Benennung der Feature Branches nach dem Featurenamen, z. B. FEATURE_DocumentParser. Jedes Release ist über die Releasenummer zu taggen, beispielsweise als Release_1.0. Die gegebenenfalls notwendigen Bugfix Branches vorhandener Releases erhalten lediglich eine Benennung nach der Versionsnummer mit Major und Minor. Der dritte Abschnitt der Versionsnummer mit der Bugfix-Version sollte nicht mit in den Namen des Branches einfließen. Ein Beispiel hierfür ist Version_1.0, für die Bezeichnung des Bugfix Branches. So kann der Branch beliebig oft für künftige Bugfixes weiterverwendet werden. Das gewährleistet eine gute Übersichtlichkeit.

Abb. 1: Branch-by-Release-Modell

Abb. 1: Branch-by-Release-Modell

Schreibtischwechsel

Ebenfalls zur Thematik der Branches gehört eine Methodik, mit der man den eigenen Arbeitsbereich sichert, um auf einen anderen Branch zu wechseln, um dort gegebenenfalls Korrekturen oder Ähnliches durchzuführen. Viele Entwickler bevorzugen einen etwas umständlichen Weg, haben für verschiedene Branches mehrere Workspaces auf ihrem Arbeitsplatz ausgeheckt und wechseln sie dann bei Bedarf. Dieses Vorgehen ist durchaus praktikabel, beansprucht aber einiges an Speicherplatz auf der Festplatte. Wenn das Source Repository kompakt gehalten wurde, sind die Übertragungszeiten für das Wechseln eines Branches sehr gering.

Die in Git eingebaute Funktion stash erzeugt einen frischen Branch und überträgt dahin sämtliche Änderungen des Arbeitsbereichs, um sie zu sichern. Anschließend kann man zu einem beliebigen Branch mit dem Kommando rebase wechseln, die notwendigen Arbeiten durchführen und nach einer erfolgreichen Übertragung der Änderungen ins Git zum vorherigen Arbeitsstand zurückkehren. Git ermöglicht es mehrere stash-Stände zu erzeugen. Daher ist es notwendig, auch ihnen eindeutige Namen zu geben. So ist eine Kombination aus STASH.<Branchname>.<User-Workspace> empfehlenswert.

Arbeitsfluss

Neben den verschiedenen Gesichtspunkten für das Erzeugen von Branches existieren auch alternative Workflowkonzepte. Besonders bei Open-Source-Projekten ist es notwendig, eine große Anzahl von Committern im Zaum zu halten. Qualität, Qualifikation und Stil divergieren hier sehr stark. Um die Qualität der Codebasis zu sichern, gibt es unterschiedliche Praktiken, wie Entwickler ihre Änderungen in die Codebasis einbringen dürfen. Das kommerzielle Werkzeug IBM Synergy beispielsweise bietet verschiedene Workflows und dazugehörige Rollenmodelle im Standardvorgehen an. Der einfachste Workflow, wie er von zentralisierten Systemen (SVN) bereitgestellt wird, ist der Push-and-Pull-Workflow. Jeder Entwickler überträgt seine Änderungen direkt in das Repository, ohne dass zusätzliche Kontrollinstanzen vorhanden sind. Das birgt die Gefahr, dass die Codebasis in den Status driftet, dass sie nicht kompiliert werden kann, was wiederum zu einem Build Fail auf dem entsprechendem CI-Server führt.

Um einen solchen Fehler zu vermeiden, gibt es den sogenannten Dictatorship-Workflow. Alle Entwickler übertragen ihre Änderungen in ein temporäres Repository, und anschließend prüft ein Build-Manager (Dictator), ob die Änderungen akzeptiert werden. In dem Fall, dass der Commit eines Entwicklers angenommen wird, überträgt der Build-Manager diesen Commit in ein Referenz-Repository, aus dem das gesamte Team die Änderungen bezieht. Nach dieser Überlegung arbeitet auch das von GitHub angebotene Pull-Requst-Verfahren. Bei großen Teams kann ein solches Vorgehen die Arbeitslast des Build-Managers überanspruchen. Um diesen Effekt abzumildern, wurde der Lieutenant-Dictatorship-Workflow ersonnen. Er etabliert eine Zwischenschicht, sogenannte Lieutenants, die für eine kleine Entwicklergruppe übertragene Commits vorselektieren und bei Qualitätsmängeln zurückweisen. Speziell in Git gibt es ebenfalls einfache Möglichkeiten, wie Entwickler untereinander Codestände austauschen können, ohne dass der Hauptentwicklungszweig davon betroffen ist. Eine Möglichkeit ist das Erzeugen eines Branches, der später wieder verworfen wird. Alternativ und etwas komplexer ist der Umweg über ein zusätzliches Tausch-Repository.

Kirschen sammeln und anderes Nützliches

Eine sehr praktische Funktion für den Umgang mit Branches ist das Cherry Picking. Hier können mehrere Revisionen aus einem anderen Branch eingesammelt und auf den Hauptenwicklungszweig angewendet werden. Betrifft der Merge mehrere Revisionen, die über verschiedene Branches verteilt sind, ist für jeden Branch ein separater Cherry Pick mit den selektierten Revisionen durchzuführen. Ein kleiner Workaround für die Tatsache, dass Git keine leeren Verzeichnisse unter Konfiguration stellt, ist das Anlegen einer leeren Textdatei mit dem Namen placeholder oder gitkeep. Das ist besonders bei der Initiierung eines Repositories nötig, um die notwendige Projektstruktur für das Entwicklungsteam bereitstellen zu können.

Auch sehr nützlich ist die Möglichkeit des Zurücknehmens von Revisionen. Im Fall eines missglückten Übertrags kann nachträglich mit einem revert die entsprechende Revision zurückgenommen und eine korrigierte Variante bereitgestellt werden. Das ist neben dem Löschen von Branches eine der Möglichkeiten, die sich durch die Verwendung von Hashes als Revisionsnummer leichter realisieren lassen als bei einer autoinkrementierten Zahl.

Resümee

Wir konnten sehen, dass SCM-Systeme und speziell Git einige Vorzüge mit sich bringen, die sich bei Kenntnis sehr positiv auf die Produktivität auswirken. Sicher ist das gesamte Thema weitaus umfangreicher, als es in einem solchen Artikel wiedergeben werden kann. Dennoch ist zu hoffen, dass mit den hier ausgeführten Erläuterungen Neugier auf eine weitergehende Beschäftigung mit den Möglichkeiten von Source-Control-Management-Systemen geweckt werden konnte. Wir konnten also sehen, dass diese Werkzeuge weit mehr bieten, als ein reines Datensilo darzustellen.

PHP Magazin

Entwickler MagazinDieser Artikel ist im PHP Magazin erschienen. Das PHP Magazin deckt ein breites Spektrum an Themen ab, die für die erfolgreiche Webentwicklung unerlässlich sind.

Natürlich können Sie das PHP Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -