Wie die Entscheidung zwischen Tabs und Leerzeichen ist auch der Umgang mit Branches ein emotionales Thema, das online wie offline hitzige Debatten auslöst.
Zwar gibt es viele überzeugte Meinungen zum besten Ansatz, doch die richtige Antwort hängt, wie so oft in der Softwareentwicklung, vom Kontext ab. Sehen wir uns daher an, was Branching-Strategien sind und wie sie mit CI/CD zusammenhängen.
Einfach ausgedrückt ist eine Branching-Strategie eine teaminterne Vereinbarung darüber, wie und wann Branches (Entwicklungszweige) in der Versionsverwaltung erstellt und wieder zusammengeführt werden. Wie Sie Ihr Versionierungssystem einrichten und Branches verwenden, wirkt sich auf die Einrichtung Ihrer CI/CD-Pipeline aus. Daher ist es wichtig, ein Modell zu wählen, das Ihren Anforderungen entspricht.
Im Zuge der Verbreitung von verteilten Versionsverwaltungssystemen (DVCS) wie Git, die einen einfacheren Umgang mit Branches ermöglichen, sind Branching-Strategien in den Vordergrund gerückt.
Bei verteilten Systemen gibt es mehrere Kopien des Repositorys und damit mehrere maßgebliche Datenquellen (sources of truth), wobei Teams in der Regel eine zentrale oder primäre Kopie benennen. Sie und Ihre Teammitglieder arbeiten parallel an Ihren jeweiligen Repository-Kopien und teilen Ihre Arbeit, indem Sie Ihre Änderungen zu anderen Kopien desselben Repositorys hochladen.
Git macht das Erstellen von Branches und das Zusammenführen (Merging) von Commits aus verschiedenen Branches zu einer sehr einfachen Angelegenheit. In Git ist ein Branch nichts weiter als ein oder mehrere Commits, die mit einem bestimmten Namen gekennzeichnet sind. Branches eignen sich ideal zur Aufnahme einer zusammengehörigen Reihe von Änderungen, die etwa bei der Arbeit an einem neuen Feature oder beim Tüfteln an einem Experiment anfallen.
Sie können Ihre Änderungen mit anderen teilen, indem Sie den Branch in ein anderes Repository hochladen und/oder mit einem anderen Branch – z. B. Master – mergen, um ihn mit dem Rest des Codebestands zusammenzuführen. Sie haben auch die Möglichkeit, die Änderungen einfach zu verwerfen, wenn Sie sich gegen sie entschieden haben. Beim Merging von zwei Branches kümmert sich Git um die Zusammenführung der Commits in beiden Branches und nimmt Ihnen damit einen Großteil der Komplexität ab, die in anderen Versionsverwaltungen mit diesem Vorgang verbunden ist.
Bei Continuous Integration werden von allen Teammitgliedern häufige Commits erwartet. Dadurch können Sie regelmäßig testen, ob alles wie erwartet funktioniert, anstatt Wochen oder Monate damit zu verbringen, verschiedene Arbeitsströme zu integrieren, nachdem die Programmierarbeit abgeschlossen ist. Auf diese Weise können Sie die Vorteile von Continuous Delivery und Deployment nutzen und häufigere Software-Updates veröffentlichen.
Den Schnittpunkt zwischen der Verwendung von Branches und CI/CD stellt die Entscheidung dar, wo Commits durchgeführt werden sollen, wann automatisierte Builds und Tests auszuführen sind und was der Ausgangspunkt für die Veröffentlichung neuer Releases sein soll. Der einfachste Ansatz – zumindest theoretisch – besteht darin, auf Branches ganz zu verzichten. Bei der trunkbasierten Entwicklung überführen alle Mitwirkenden ihre Änderungen regelmäßig per Commit in den Master-Branch in einem zentralen Repository, das stets in einem releasefähigen Zustand gehalten wird und von dem aus regelmäßig Deployments in die Produktion erfolgen.
Die trunkbasierte Entwicklung kann zwar sehr effektiv sein, insbesondere wenn Sie über ein ausgereiftes CI/CD-Setup verfügen und Continuous Deployment auf einem gehosteten System betreiben – allerdings ist sie auch mit einigen Herausforderungen verbunden. Branching-Strategien bieten bei der Verwaltung von Codeänderungen alternative Möglichkeiten, die jeweils ihre eigenen Vor- und Nachteile haben. Im Folgenden stellen wir Ihnen einige der im CI/CD-Bereich verbreiteten Strategien vor.
Wie der Name schon sagt, dienen Feature-Branches dazu, einzelne Features vom Rest des Codebestands zu trennen. Wenn Sie Features, die sich in Entwicklung befinden, aus dem Master-Branch heraushalten, kann es einfacher sein, den Master in einem Deployment-fähigen Zustand zu halten. Wenn Sie zudem Releases direkt vom Master aus (und nicht wie weiter unten beschrieben von einem Release-Branch aus) bereitstellen, entgehen Sie durch die Verwendung von Feature-Branches der Gefahr, dass unvollständige Features in der Produktionsversion landen. Sie können Ihre Arbeit trotzdem mit dem Rest des Teams teilen, indem Sie Ihren Branch entweder in das zentrale Repository (sofern Sie eines haben) oder in ein beliebiges anderes Repository hochladen.
Der größte – und von Befürwortern der trunkbasierten Entwicklung am häufigsten kritisierte – Nachteil von Feature-Branches besteht darin, dass Sie die Vorteile der Continuous Integration verlieren, wenn Sie die Integration von Änderungen bis zur „Fertigstellung“ des Features hinauszögern. Im Vergleich zu iterativen Commits riskieren Sie dadurch Merge-Konflikte und komplexere Bugs, die langwieriger zu beheben sind.
Sie können diesen Problemen entgegenwirken, indem Sie Ihren CI-Server so einrichten, dass automatisierte Builds und Tests sowohl in Feature-Branches als auch im Master-Branch (bzw. dem Branch, den Sie zur Vorbereitung von Releases verwenden) ausgeführt werden. Dadurch gewährleisten Sie sofortiges Feedback zu Ihrer laufenden Entwicklungsarbeit und minimieren gleichzeitig das Risiko von Problemen beim Zusammenführen von Änderungen.
Eine Möglichkeit, Merge-Konflikte zu minimieren, besteht darin, Feature-Branches kurzlebig zu halten – höchstens ein oder zwei Tage. Eine andere Option ist das regelmäßige Rebasing des Feature-Branches auf dem Master-Branch oder das regelmäßige Merging der Änderungen aus dem Master. Dadurch bleibt Ihr Feature-Branch stets auf dem neuesten Stand – inklusive aller Änderungen, die in den Master-Branch aufgenommen wurden. Wenn Sie jedoch eine große Anzahl von Feature-Branches gleichzeitig verwenden, die jeweils ihre Commits zum Master hinauszögern, bleibt die Gefahr von Konflikten bestehen.
Während Feature-Branches zum Management der laufenden Entwicklungsarbeit dienen, werden Release-Branches verwendet, um Änderungen vor dem Release Härtetests zu unterziehen. Release-Branches eignen sich gut für ein Continuous-Delivery-Modell, bei dem Updates in Abständen statt sofort bereitgestellt werden, und sie erleichtern die Unterstützung mehrerer Versionen im Produktionseinsatz.
Wenn alle für ein bestimmtes Release geplanten Änderungen fertiggestellt sind, wird bei diesem Workflow ein Release-Branch mit den relevanten Änderungen erstellt (entweder vom Master oder einem anderen Entwicklungs-Branch aus). In diesen Release-Branch werden keine weiteren Features mehr aufgenommen. In der Zwischenzeit können Features, die für andere Releases geplant sind, weiterhin in den Master oder einen anderen Branch integriert werden.
Der Release-Branch-Build wird dann einer Reihe von automatisierten Tests unterzogen. Alle notwendigen Fehlerkorrekturen werden im Release-Branch vorgenommen, gefolgt von weiteren Testrunden, bis die Software zur Veröffentlichung bereit ist. Diese Fehlerbehebungen werden im Anschluss wieder in den Master-Branch übernommen, um sicherzustellen, dass sie in zukünftigen Produktversionen enthalten sind.
Wenn Sie Ihre Release-Branches so lange behalten, wie Sie Unterstützung für die jeweilige Version Ihrer Software leisten müssen, ist es viel einfacher, Fehlerkorrekturen für alte Versionen bereitzustellen.
Wenn ein Update erforderlich ist, kann es in einem Hotfix-Branch oder im Master entwickelt und wie gewohnt getestet werden, um die Änderung dann auf den Release-Branch anzuwenden (etwa per Cherry-Pick).
Die Änderung durchläuft dann in diesem Branch die CI/CD-Pipeline, um vor der Veröffentlichung sicherzustellen, dass die neue Version keine Probleme aufweist. Alternativ können Sie die Arbeit auch direkt im Release-Branch erledigen und Änderungen nach Bedarf in den Master übernehmen.
Hotfix-Branches funktionieren zwar ähnlich wie Feature-Branches, werden hier aber der Vollständigkeit halber separat vorgestellt. Ein Hotfix-Branch dient dazu, eine Fehlerkorrektur so schnell wie möglich für die Produktion freizugeben.
Sie können einen Hotfix-Branch auf dem Master oder einem Release-Branch basieren, je nachdem, wo welchem Branch aus Releases bereitgestellt werden. Wie bei Feature-Branches können Sie Ihren Hotfix-Branch einen Teil Ihrer CI/CD-Pipeline durchlaufen lassen, bevor Sie ihn mit dem Release- oder Master-Branch mergen, wo er dann vor der Veröffentlichung weiteren automatisierten Tests unterzogen wird.
Wir haben uns einige gängige Möglichkeiten zur Verwendung von Branches in CI/CD-Workflows angesehen, aber es gibt noch viele weitere Variationen.
Das Wichtigste ist, eine Strategie zu finden, die in Ihrem konkreten Kontext funktioniert. Wenn Sie sich die DevOps-Mentalität der kontinuierlichen Verbesserung zu eigen machen und für verschiedene Optionen offen bleiben, können Sie Ihren Ansatz an Ihre jeweiligen Bedürfnisse anpassen, während Sie Ihr CI/CD-System ausbauen.