en
de

Git-Branching und Continuous Integration

11 April 2014
| |
Lesezeit: 3 Minutes

Der Blog-Beitrag von Vincent Driessen, in dem er sein favorisiertes Branching-Model für Git vorstellt, hat bei Zühlke eine kontroverse Diskussion rund um dieses Thema ausgelöst. Uns bewegt dabei besonders die Frage, welche Auswirkung die Arbeit mit (Feature) Branches auf Continuous Integration (CI) hat. Letzteres empfinde ich als essentiell für unseren Projekterfolg.

Daher bereitet mir jegliche Arbeitsweise unbehagen, die meinem Ziel die Software-Builds und -Tests zu automatisieren erschwert. Insbesondere in unseren häufig agil ausgestalteten Projekten steigt und fällt der Erfolg mit der CI.

Zunächst ist also die Frage interessant, ob und wann der Einsatz von Feature-Branches Vorteile bringt. Letztlich ist es dabei zweitrangig, ob wir mit Git oder Subversion arbeiten. Ich investiere den Arbeitsaufwand einen Branch zu erzeugen und später mit dem Master zu reintegrieren nur dann, wenn sich das auszahlt. Für kleinere und nicht-destruktive Änderungen erscheint mir der Preis zu hoch. Derartige Änderungen werde ich wohl auch weiterhin direkt in den Master einspielen. Das CI-System klopft mir da relativ schnell auf die Finger, wenn ich versehentlich einen Fehler einbaue. Ich kann natürlich alle Feature-Branches für sich automatisiert übersetzen und testen.

Aus dem „fail early“ direkt nach dem Commit wird dennoch ein „fail late“ beim Reintegrieren, also ein echtes Projektrisiko.

Nun komme ich aber nicht umhin auch größere Änderungen durchführen zu müssen. Dann stehe ich vor der Wahl meine Änderungen über mehrere Tage nicht zu veröffentlichen, oder den Build zu brechen. Diese Entscheidung erachte ich als vergleichbar derer zwischen Pest und Cholera. Einen möglichen Ausweg sehe ich in einem Feature-Branch. Dort kann ich über einen längeren Zeitraum auch destruktive Änderungen Schritt für Schritt durchführen, ohne den Build zu brechen. Dabei gehe ich jedoch das Risiko ein, den Anschluss zum Master zu verlieren.

Je länger ich auf dem Feature-Branch vor mich hin werkle, desto höher wird die Wahrscheinlichkeit, dass ich meinen Feature-Branch nicht mehr problemlos reintegrieren kann. Ich muss also immer situativ entscheiden, ob ich die organisatorische Komplexität eines Feature-Branchs wirklich benötige.

Community-Rebase-Workflow anpassen und integrieren

Meine Hoffnung zur Vermeidung eines Merge-Chaos liegt nun auf dem CI-System. Das CI-System soll die Reintegration meinens Feature-Branches regelmäßig testen und bei Merge-Konflikten Alarm schlagen. Diese Alarme erinnern mich daran, dass im Branching-Model von Vincent ein kleines Detail unterschlagen ist: Das regelmäßige Rebase. Dieses ist bei Open-Source-Projekten aus gutem Grund Usus. Sofern ich täglich ein Rebase meines Feature-Branches durchführe, bleibt mir auch das Merge-Chaos bei der Reintegration erspart.

Open-Source-Maintainer neigen dazu Pull-Requests abzulehnen, wenn das Feature nicht passend „rebased“ ist. Für unseren Workflow in Verbindung mit dem CI-System ergibt sich aus einem Rebase allerdings ein Problem: Ein Rebase verändert die Branch-Historie. Für mich lokal kann ich das problemlos tun. Sobald ich einen Branch jedoch in einem Team-Repo veröffentlicht habe, werden mich die Kollegen verfluchen, wenn ich ihnen die Historie unterm Hintern weg ändere. Ich kann stattdessen regelmäßig alle Änderungen des Masters in den Feature-Branch mergen, um ein änhliches Ergebnis zu erreichen. Die zusätzlichen Merge-Commits in der Branch-Historie muss ich akzeptieren.

Sobald ich es mit mehr als einem parallelen Feature-Branch zu tun bekomme, hilft mir das Synchronisieren einzelner Features mit dem Master nur noch bedingt. Solange ich die verschiedenen Features in unabhängigen Code-Teilen meiner Software durchführe, kann ich dem Merge-Chaos noch entgehen. Der Linux-Kernel mit seinen unabhängigen Subsystemen und einzelnen Treibern ist ein gutes Beispiel.

Viele Entwickler arbeiten erfolgreich parallel und verteilt, und daher zumindest auf impliziten Feature-Branches. Dass dieses Vorgehen erfolgreich möglich ist, liegt also nicht zuletzt in der Software-Architektur begründet. Änderungen an gemeinsamen Code-Teilen muss ich organisatorisch in den Griff bekommen. Dazu kann ich beispielsweise von mehreren Features benötigte Refactorings als eigenes „Feature“ ansehen und diese entsprechend vorgelagert durchführen.

Eine überschaubare Anzahl Features mit gegenseitigen Abhängigkeiten kann ich mit Git bei Bedarf noch als Octopus-Merge reintegrieren. Ich werde jedoch für jedes derartige Feature-Set eine passende Reintegrationsvorschrift erstellen müssen, um diese Reintegration im CI-System zu automatisieren.

Natürlich stehe ich dann schnell vor der Frage welches Feature die Ursache für einen Merge-Konflikt ist, da die Konflikte erst in Kombination mehrerer Änderungen auf unterschiedlichen Branches zum Tragen kommen. Ich bezweifle, dass sich dieser Aufwand regelmäßig lohnt. Daher tendiere ich eher dazu, abhängige Features möglichst nicht parallel auf unterschiedlichen Feature-Branches zu realisieren.

Integration-Manager-Workflow automatisieren

Richtig cool wird mein CI-System, wenn es nun die Reintegration meines Feature-Branches übernimmt, also die Integrator-Rolle automatisiert. Ich stelle nur einen Pull-Request für mein Feature-Branch ein und den Rest erledigt das CI-System: Mergen, Kompilieren, Testen.

Anschließend publiziert das CI-System das Integrationsergebnis auf dem Master. Auf diese Weise kann ich sicherstellen, dass alle automatisierten Integrationstests auf dem resultierenden Stand erfolgreich gelaufen sind bevor der nächste Nightly-Build gegen die Wand fährt. Famos! Wer macht’s?

Kommentare (6)

Avatar

Daniel Marbach

11 April 2014 um 15:33

Hallo Jonatan,
Ich bin mir noch nicht sicher ob ich dein letzter Absatz richtig verstanden habe, auf das Risiko hin dass es nicht der Fall ist möchte ich dich trotzdem auf ein cooles Feature bei TeamCity von Jetbrains hinweisen *Trommelwirbel*:

http://blog.jetbrains.com/teamcity/2013/02/automatically-building-pull-requests-from-github-with-teamcity/

Viola! Cool ist dass TeamCity dann ein API call auf Github macht und die Informationen neben dem Pullrequest visualisiert. Z.B. https://github.com/ninject/ninject/pull/126

Gruss Dani

    Jonatan Antoni

    Jonatan Antoni

    15 April 2014 um 11:00

    Hallo Daniel,

    ich denke Du hast meinen letzten Absatz völlig richtig interpretiert. Vielen Dank für den Hinweis auf TeamCity, ich denke das ist ein Blick wert. Spannend ist noch die Frage, wie gut sich dieses Tool in unsere interne Projektinfrastruktur abseits von GitHub und Co. integrieren lässt.

    Für Jenkins-CI gibt es ein Plugin mit ähnlicher Funktionalität. Allerdings scheint mir dieses Plugin auf anhieb ebenfalls recht GitHub-fixiert zu sein.

    Grüße
    Jonatan

      Avatar

      Daniel Marbach

      15 April 2014 um 11:44

      Hallo Jonatan,
      Danke fürs Antworten 🙂 Meine Jenkins Kenntnisse sind etwas verstaubt, darum bin ich da sicherlich keine grosse Hilfe. Die Branchtriggerdefinition laufen auch ohne Github. Wir haben die intern im Einsatz. Du kannst dort relative ausgefeilte Patterns hinterlegen z.B. auch Personal Builds triggern auf Grund des Usernamens. Die Branches müssen dann aber einer Konvention folgen die du festlegst. Einzig die Rücknotifikation geht dann nicht. Das HTTP Api hinter TeamCity ist aber OK. Sowas kann man sehr schnell einbauen in die interne Infrastruktur.
      Beim TFS 2013 Update 1 (behafte mich nicht darauf) sind auch Branchbuilddefinition möglich. Die sind aber rudimentär im Vergleich zum TeamCity.

      Gruss Dani

Avatar

Nils H

15 April 2014 um 20:12

Wir arbeiten bei uns in einem sehr großen Projekt mit vielen Entwickler-Teams und vielen Feature Branches (aktuell ca. 25).
(Wir setzen statt git mercurial ein, aber der Unterschied ist an der Stelle zu vernachlässigen)

Bei uns werden nur merges aus hotfixen etc. auf dem Master direkt gemacht und alle anderen Änderungen in einzelnen Featurebranches. Für diese gibt es Templates und Skripte, sodass ein neuer Branch inkl. eigenem continous und nightly jenkins job innerhalb von ca. 5-10min angelegt ist.
Einer der großen Vorteile ist, das dadurch einzelne features auf die Abnahmeumgebung deployed und abgenommen werden können. (Eins der Abnahmekriterien sind natürlich grüne Builds und ein rebase vom master 😉 ).

Dadurch können features zum einen zurückgestellt werden und noch etwas „reifen“ (ohne das release zu zerstören), und zum anderen bleibt unser master „eigentlich immer“ grün.

Schwierig ist bei dem Ansatz die Reihenfolge der Entwicklung im voraus festzulegen und den richtigen Zeitpunkt für grundlegende Änderungen zu finden (aber ich befürchte das ist nie leicht… ).

PS: auf meinen Smartphone getippt…

    Jonatan Antoni

    Jonatan Antoni

    16 April 2014 um 09:47

    Hallo Nils,

    danke für Deinen Kommentar.

    Scripte für das Branchen und Erzeugen von Jenkins-Jobs sind sicher ein gutes Hilfsmittel. Ich habe jedoch die Erfahrung gemacht, dass insbesondere Jenkins-Jobs nur eingeschränkt scriptbar sind. Sobald der Build-Prozess komplexer wird als ein Job pro Branch (ich habe teilweise bis zu 10 Jobs), wird das trotzdem anstrengend. Unhandlich finde ich insbesondere, dass ich die Job-Templates bei einem Jenkins-Update händisch nachziehe muss. Dafür habe ich noch keine ideale Lösung gefunden. Das wäre aber wohl ein Thema für einen weiteren Blog-Beitrag.

    Interessant finde ich Deine Aussage zu etwa 25 parallelen Feature-Branches. Wie stellt Ihr sicher, dass sich Eure Features nach einer längeren Entwicklungszeit schmerzlos reintegrieren lassen? Prüft Ihr das kontinuierlich, etwa wie von mir vorgeschlagen per automatisiertem Merge-Test? Oder erlaubt Euch Eure Software-Architektur bereits eine starke Entkopplung der Features, sodass es nur selten zu Überschneidungen kommt?

    Grüße
    Jonatan

Avatar

Rolf Bruderer

7 Mai 2016 um 18:30

Hoi Jonatan,

Guter Artikel, Danke.

Bei mir im Projekt arbeiten wir mit Feature-Branches und Merge-Requests (=Pull-Requests) und haben im Prinzip eine Art von Deinem vorgeschlagenen automatischen Integrator realisiert, nur ein klein wenig anders.

Wenn jemand bei uns seinen Feature-Branch auf den Develop-Branch integrieren will, so passiert das immer über einen Merge-Request. Diesen Merge-Request lassen wir dann von unserem automatisierten Build-System mit dem Develop-Branch probehalber mergen und testen. Der Merge-Request wird also automatisch auf eine mögliche fehlerfreie Integration mit dem Entwicklungs-Haup-Branch getestet. Ein Kollege reviewed dann noch den Code, bevor er dann quasi auf Knopfdruck tatsächlich in den Develop-Branch gemerged wird (immer unter der Voraussetzugn dass alle Tests erfolgreich waren). Das funktioniert wunderbar und hat zusätzlich den Vorteil, dass wir uns regelmässig den Code gegenseitig reviewen.

Wichtig dabei ist natürlich, dass im Prinzip jeder Entwicklungstask auf einem eigenen Feature-Branch entwickelt wird und in möglichst kleinen Iterationen (wenige Tage) ein solcher Feature-Branch fertig umgesetzt und in den Entwicklungsbranch integriert werden soll. Das ist natürlich immer sehr wichtig, sonst landet man irgendwann in der Integrations-Hölle.

Bei komplizierteren Refactorings oder Breaking-Changes kann man auch mal ausnahmsweise etwas länger auf einem Feature-Branch isoliert arbeiten. In einem solchen Fall ist es aber besonders wichtig gut mt den anderen Kollegen darüber zu sprechen, damit es nicht zu übermässigen Integrations-Problemen mit anderen Branches kommt.

×

Updates

Schreiben Sie sich jetzt ein für unsere zwei-wöchentlichen Updates per E-Mail.

This field is required
This field is required
This field is required

Mich interessiert

Select at least one category
You were signed up successfully.

Erhalten Sie regelmäßige Updates zu neuen Blogartikeln

Jetzt anmelden

Oder möchten Sie eine Projektanfrage mit uns besprechen? Kontakt aufnehmen »