Parallele Softwareentwicklung spielend meistern

Seite 2: Branch-Management

Inhaltsverzeichnis

In den gängigen Softwareprodukten wird der Quellcode in zentralen oder dezentralen Repositories, wie Team Foundation Server (TFS), Subversion oder Git verwaltet. Als Branch (Zweig) wird im TFS ein Abbild eines Verzeichnisses inklusive Inhalt verstanden. (In diesem Artikel werden die Begriffe des TFS verwendet. In anderen Versionsverwaltungssystemen wie Subversion werden Branches zusätzlich in Trunk und Branch unterschieden.) Beim Erzeugen eines neuen Branches wird eine exakte Kopie der Dateien des übergeordneten Branches erstellt und zugleich eine Eltern-Kind-Beziehung zwischen den beiden Branches angelegt. Bei der Gegenoperation, dem Merge, werden die Inhalte zweier Branches zusammengeführt. Das gleichzeitige Arbeiten an der gleichen Softwareversion lässt sich folglich mit verschiedenen Branches realisieren. Die einfachste Form eines Branching-Konzepts ist in Abbildung 1 dargestellt. Die waagerechten Graphen repräsentieren eine zeitliche Achse.

Der Basis-Branch-Plan ermöglicht das ständige Bereitstellen einer lauffähigen Softwareversion, das parallele Weiterentwickeln und das Erstellen von Service Packs bereits ausgelieferter Softwareversionen (Abb. 1).

Zu Beginn wird ein Branch auf Basis von Main jeweils für die Entwicklung (Dev) und die Veröffentlichung (Release) erzeugt (1). In regelmäßigen Abständen werden Änderungen von Main nach Dev integriert (2) – das Integrieren von einem Eltern- zu einem Kind-Branch wird als Forward Integration (FI) bezeichnet. Gemeint sind beispielsweise Merges von Main- nach Dev-Branches. Sie sind allerdings nur notwendig, sofern mehrere Dev-Branches existieren, deren Codeänderungen in die jeweils anderen Dev-Branches einfließen müssen. Um die Integrität von Main zu gewährleisten, arbeitet man auf diesem Branch nicht direkt. Des Weiteren wird der Integrationsaufwand nach Main minimiert und ein stabiler Versionsstand lässt sich schneller erreichen. Ebenso regelmäßig werden vollständig implementierte Features von Dev nach Main transferiert (3).

Das Integrieren von einem Kind- zu einem Eltern-Branch bezeichnet man als Reverse Integration (RI). Dies sind beispielsweise Merges von Dev- nach Main-Branches. Eine späte Integration, kurz vor dem Release, würde den Integrationsaufwand erheblich erhöhen und möglicherweise das geplante Release verzögern. Sind alle geplanten Features im Dev-Branch umgesetzt, werden diese nach Main integriert (4). Anschließend erfolgt die Integration von Main nach Release, sodass sich dieneue Softwareversion veröffentlichen lässt (5). Damit das Erstellen von Service Packs nicht mit der aktuellen Entwicklung kollidiert, werden diese auf dem Release-Branch entwickelt und danach auf Main und anschließend Dev integriert (6), sodass der Bug auch in kommenden Versionen korrigiert ist.

Nach jedem Release, das vom Main-Branch gezogen wird, ist ein Release einer Vorgängerversion schwer realisierbar. Wie Abbildung 1 zeigt, wird spätestens mit dem letzten Merge auf Main die Vorgängerversion gänzlich überschrieben. Ein Service Pack für ältere Versionen auf dem Release-Branch zu entwickeln ist somit nicht mehr möglich. Dieser konkrete Fall ließe sich mit dediziertem Release-Branch für jede einzelne Version lösen.

Das dargestellte Branching-Konzept lässt sich beliebig erweitern. Beispielsweise können oberhalb des Dev-Branches mehrere Feature- oder Team-Branches erstellt werden, was sich bei entsprechender Team- oder Projektgröße empfiehlt. Die Verwendung von Feature- oder Team-Branches ermöglicht die unabhängige Entwicklung von Features. Erst nach Fertigstellung erfolgt die Integration auf den Eltern-Branch, sodass letztlich nur vollständig implementierte Features in einem Release enthalten sind. Des Weiteren ist es möglich, Service- und Hotfix-Branches zwischen Main- und Release-Branch einzufügen, um das Erstellen von Service Packs und Patches separat zu realisieren. Diese und weitere Möglichkeiten werden zum Beispiel im "Visual Studio Team Foundation Server Branching and Merging Guide" der Visual Studio ALM Rangers detailliert erläutert. Für das nachfolgende, erweiterte Branching-Konzept sind diese Erweiterungen gleichermaßen anwendbar.

Werden nun mehrere Dev-Branches notwendig, weil mehrere Softwareversionen parallel entwickelt werden, stößt das Branching-Konzept aus Abbildung 1 an seine Grenzen. Denn wenn Merges der verschiedenen Dev-Branches auf Main integriert werden, ist eine allzeit lauffähige Version des Softwareproduktes nicht gewährleistet. Nebenbei stellt sich die Frage, welche Version Main enthält. Abbildung 2 verdeutlicht das Problem.

Wird die Version 2.0 auf den Main-Branch integriert, ist kein Release auf Basis von Version 1.0 des Main-Branches möglich (Abb. 2).

Wird bei der parallelen Entwicklung ebenso zwischen den Dev-Branches und Main-Branch integriert, enthält Main nach der ersten Reverse Integration eine Version, die im Beispiel noch nicht veröffentlicht werden soll, nämlich Version 2.0 (1). Bei der späteren Forward Integration zwischen Main und Dev-1.0-Branch würde die Version 1.0 sogar Features der zukünftigen Version 2.0 enthalten, die eventuell miteinander inkompatibel sind (2). Ein konsistentes Release der Version 1.1 wäre nicht mehr möglich (3). Ein Ansatz ist, dass nur zwischen dem Branch der aktuellen Version und Main integriert wird und somit Main eine stabile Version enthält und sich später davon ein Release erstellen lässt. Für die anderen, parallel entwickelten Versionen ginge es aber nicht, jederzeit ein Release auszuliefern. Selbst für Cherry Picking (selektives Integrieren von Codeänderungen) genügt der eine, vorhandene Main-Branch nicht.