Vor kurzem sind wir mit unserer monolithischen Anwendung von Subversion zu Git gewechselt, darüber hat mein Kollege Frank schon in seinem Artikel Der git-Move – sanfter Wechsel des Versionskontrollsystems berichtet. Den einen goldenen Weg für so ein Projekt gibt es nicht. Wie geht man so eine Migration also an? Wo fängt man an? Und wo hört man auf?
Als ich zum ersten Mal mit Git gearbeitet habe, war das ganz schön gewöhnungsbedürftig. Ich kannte bis dahin nur Subversion (SVN) und konnte das Konzept eines verteilten Versionskontrollsystems noch nicht ganz greifen. Inzwischen möchte ich Git jedoch nicht mehr missen und liebe es, damit zu arbeiten.
In diesem Blogbeitrag beschreibe ich anhand einiger Meilensteine, wie wir endlich auch unser Hauptprodukt in ein Git-Projekt migriert haben und inzwischen auf SVN verzichten können.
Den Einstieg finden
Februar 2023 – Da saßen wir nun also. Vor uns eine über 20 Jahre gewachsene Anwendung, verwaltet in Subversion. Und die sollte nun nach Git migriert werden. Wir wussten schon, dass wir in der Vergangenheit nicht immer nach den besten Prinzipien gehandelt haben. Über die Zeit wurden viele Binärdateien in dem Repository vorgehalten, einige davon mehrere hundert Megabyte groß. Auch die Struktur des Repositories hat sich mehrmals geändert. Wir haben wohl einen ziemlichen Berg vor uns.
Aber das liegt alles in der Vergangenheit. Seit Ende 2018 haben wir kontinuierlich daran gearbeitet solche technischen Schulden abzubauen und Prozesse zu vereinfachen. Ein Punkt der besonders auf die Git-Migration eingezahlt hat, ist die Modernisierung unseres Buildsystems. Viele der Binärdateien konnten wir im Versionskontrollsystem entfernen. Sie werden stattdessen als Abhängigkeiten aus einem Artefaktstorage bezogen.
Für die Migration von SVN zu Git werden verschiedene Tools angeboten. Wir haben die folgenden für uns ausprobiert: git-svn, svn2git und SubGit
Alle gemein hatten eine ziemlich hohe Laufzeit. Wir haben die Tools bis zu einer Woche laufen lassen, ohne einen signifikanten Fortschritt zu sehen. Letztlich haben wir uns für git-svn entschieden. Die Konfiguration war simpel und auch für eine Migration der Branches haben wir trotz der Laufzeit Möglichkeiten zur Umsetzung gesehen.
Bevor wir uns aber dem großen Ganzen widmen, wie sieht es denn mit dem heutigen Stand der Software aus? Lässt sich damit unter Git arbeiten? Um diese Fragen zu beantworten, haben wir zunächst die Historie abgegrenzt und einen Git Klon auf Basis des aktuellen trunk-Stands erstellt.
In diesem Zustand betrug die Größe des Git-Repositories ca. 4 Gigabyte. Das schien uns noch etwas hoch zu sein, wir konnten aber erst einmal damit arbeiten.
Erste Erkenntnisse
März 2023 – Wir haben eine zusätzliche Build-Pipeline für das Git-Repository aufgesetzt und konnten zu diesem Zeitpunkt schon erste Gehversuche nehmen.
Als CI-System setzten wir Jenkins ein. Dort kann für Build-Jobs ein lokal auf den Build-Knoten liegendes Git-Repository als Referenz konfiguriert werden. Beim Build wird zunächst dieses Repository lokal geklont. Aus dem Remote-Repository muss nur noch die Differenz zu dem letzten Update des Referenz-Repositores bezogen werden. Dadurch kann die Zeit zum Klonen und Checkout möglichst gering gehalten werden. Das ist im Vergleich zu SVN schon ein ziemlicher Vorteil. Das Referenz-Repository auf den Build-Knoten halten wir über einen separaten Job nächtlich aktuell.
Nachdem die Build-Pipeline stand, konnten wir erste problematische Stellen im Buildsystem identifizieren. Wesentliche Probleme waren
- die Verknüpfung des Buildsystems mit SVN (zur Identifizierung des Branches und Erstellung eines Changelogs)
- die Versionierung anhand des ermittelten Branches
Diese Punkte konnten wir isolieren und darüber eine erste Buildfähigkeit herstellen. Mit einem Feature-Toggle versehen, haben wir diese Änderungen bereits im SVN-Repository eingecheckt und ab hier kontinuierlich neue Commits nach Git migriert. Die Buildfähigkeit wurde laufend sichergestellt.
Nach dieser positiven Erkenntnis sind wir in der Migration den nächsten Schritt gegangen und haben für trunk die vollständige Historie nach Git überführt. Das Vorgehen ist im Grunde identisch, nur grenzen wir die zu migrierenden Revisionen nicht mehr ein. Git scannt dann das gesamte Repsoitory und überträgt Schritt für Schritt alle Revisionen, die den angegebenen Pfad betreffen.
An dieser Stelle betrug die Größe des entstandenen Git-Repositories schon ca. 20 Gigabyte. Das war für uns eine Größe, mit der wir nicht täglich arbeiten wollen würden. Wir mussten also Abhilfe schaffen.
Analyse und Filterung des Git-Repos
April 2023 – Unter Git stehen einem viele Tools zur Analyse und Manipulation des Repositories zur Verfügung. Ein besonders beeindruckendes davon ist git-filter-repo, ein in Python geschriebenes Plugin für das Git Commandline-Tool. Zur Installation muss das Skript lediglich in den $PATH
gelegt werden. Anschließend kann es in einem Git-Repository per git filter-repo
aufgerufen werden.
Mit dem Befehl git filter-repo --analyze
haben wir als erstes einen Bericht über unser Repository erstellt. Das erzeugt verschiedene Dateien im Repository:
Unser Ziel war es besonders große und inzwischen gelöschte (hauptsächlich Binär-) Dateien aus der Historie zu entfernen. Alles was heute noch im Repository existiert, sollte erhalten bleiben.
Besonders interessant für uns war daher der Report path-deleted-sizes.txt. Darin sind alle Pfade zu Dateien gelistet, welche im Repository mal existiert haben, inzwischen aber entfernt wurden. Die Pfade sind dabei absteigend nach der Gesamtgröße (in Bytes, akkumuliert über alle Branches) aufgeführt. Der Report directories-deleted-sizes.txt bietet weitere Anhaltspunkte und liefert ähnliche Informationen auf Verzeichnisebene.
Aus diesen Informationen haben wir Listen an Dateien, Verzeichnissen und glob-Ausdrücken erstellt, welche aus dem Repository herausgefiltert werden sollen. Über ein Skript haben wir automatisiert, dass in wiederholten Ausführungen weitere obsolete Pfade aus dem Report ermittelt und ebenfalls in einer Liste gespeichert werden. Zum Beispiel wenn wir die Liste an relevanten Dateiendungen ergänzt haben. Darüber konnten wir uns nach und nach der endgültigen Filterliste nähern.
Im letzten Schritt haben wir mit dem Skript aus diesen Listen eine Inputdatei für git-filter-repo für die tatsächliche Filterung des Repositories generiert. Die sieht Beispielhaft so aus:
Das Repository kann damit entweder direkt gefiltert werden oder als Quelle dienen und das gefilterte Ergebnis in ein frisches Repository geschrieben werden. Da es sich dabei um einen destruktiven Prozess handelt (das gesamte Repository wird neu geschrieben), haben wir uns für die zweite Variante entschieden. Zu beachten ist noch, dass git-filter-repo standardmäßig von einer Positivliste ausgeht. Das kann mit dem Schalter --invert-paths
leicht umgedreht werden.
Wenn das gefilterte Repository immer noch zu groß erscheint (bei uns waren es immerhin noch 11 GB), kann der Befehl git gc
helfen. Dadurch werden ggf. entstandene Zwischenstände von git-filter-repo entfernt und die Objekte neu komprimiert.
Das entstandene Repository ist dann wieder bei einer handhabbaren Größe von etwas über 2 GB.
Die Stakeholder einbeziehen
Mai 2023 – Parallel zu den genannten Themen haben wir auch andere Punkte bearbeitet. So haben wir die Build- und Deploymentfähigkeit unter Feature-Branches hergestellt und ein neues Versionierungskonzept erarbeitet. Mit den verfügbaren Gradle-Plugins zur Versionierung ließ sich unser Konzept nicht einfach abbilden, dafür haben wir ein eigenes Gradle-Plugin erstellt.
Inzwischen hatten wir also einen ziemlich fortgeschrittenen Stand, mit dem wir nun an die verschiedenen Stakeholder gegangen sind.
Die Änderungen an der Versionierung haben wir insbesondere unserem Release- und Qualitätsmanagement, sowie den Verantwortlichen unseres Kunden präsentiert. Dabei sind zwar viele Fragen und Todos in Bezug auf bestehende Prozesse hochgekommen, aber unserem Vorhaben wurde positiv begegnet. Zum Testen der Prozesse beim Kunden haben wir erste Bereitstellungen von unter Git gebauten Software-Ständen vereinbart. Dafür haben wir im Nachgang entsprechend den Bereitstellungs-Prozess unter Git ermöglicht.
Das entstandene Git Repository haben wir unseren Systemarchitekten vorgestellt und dazu eingeladen, erste Features schon einmal unter Git zu entwickeln. Da Produktiv noch unter SVN versioniert wurde, mussten unter Git entwickelte Features natürlich letztendlich dorthin übertragen werden. Das ist aber Problemlos möglich, indem unter Git ein Patchset erstellt und in SVN anschließend eingespielt wird.
Zur Orientierung haben wir das und weitere Informationen in einem kleinen Leitfaden für die Entwickler zur Verfügung gestellt. Die Reaktionen waren zwar noch etwas verhalten, aber auch hier positiv gestimmt. Unser Angebot wurde vereinzelt angenommen und das daraus entstandene Feedback war für uns sehr wertvoll.