GIT – aus eins mach viele

Software Projekte starten meist mit einem SCM Repository. Nach einem gewissen historisch bedingten Wachstum hat man dann auf einmal eine Sammlung unabhängiger Komponenten. Diese gehören aber in eigene Repositories. Gerade wenn unterschiedliche Versionen in der Produktion zum Einsatz kommen sollte man tunlichst darauf achten. Sonst wird es schwer unabhängig voneinander zu entwickeln ohne Kompatibilität zu brechen.
Leider hat man es zu spät gemerkt. Was tun um die Historie nicht zu verlieren? In den dunklen Ecken von GIT findet sich git-filter-branch. Ein mächtiges Tool wenn es darum geht am kompletten Baum etwas zu verändern. Aber beginnen wir von vorne:
1. Repository auschecken:

$ git checkout git@git.foo.bar:repository.git && cd repository

2. Entfernen aller anderen Verzeichnisse bis auf das gewollte

$ git filter-branch --prune-empty --subdirectory-filter path/to/component -- --all

Mit dem obigen befehl wird alles entfernt was nicht im Verzeichnis ‘path/to/component’ vorkommt. Normalerweise werden hierbei leere commits erzeugt welche nichts mit dem Filter zu tun haben. Um dies zu vermeiden gibt es die Option ‘–prune-empty’.
3. Anlegen eines neuen Repositories und setzen eines neuen Ziels

$ git remote add target git@git.foo.bar:component.git

4. Explizites pushen des master branch in das neue Ziel

$ git push target master

Neues Repository wird mit der gefilterten Historie gefüllt. Allerdings ist der alte Code noch im ehemaligen Repository vorhanden
5. Neu auschecken und redundanten Code entfernen
Lokale Kopie von der Platte entfernen (Schritt 1) und mit folgendem GIT Befehl weiterarbeiten

$ git filter-branch --tree-filter ' rm -rf path/to/component' HEAD

6. Repository pushen und aufräumen
Wir haben die Timeline geändert und unsere Änderungen passen sich nicht mehr in die Historie ein:

$ git push -f

Mit der Option -f wird alles Remote überschrieben, egal ob es passt oder nicht. Man tun gut daran kurz inne zu halten und den Branch zu überprüfen.
Danach können wir die alten Reflogs löschen um Speicherplatz frei zu geben:

$ git reflog expire --expire=now --all && git gc --aggressive --prune=now

Pa-Tsching – fertig!
Natürlich ist das nicht die ganze Wahrheit und passt auf einfach Anwendungsfälle. Mit unterschiedlichen Release Tags und verzweigten Branches wird das extrahieren etwas aufwendiger. Allerdings ist dies dann keine Standardfall mehr und genau auf das Projekt zugeschnitten (Namensschemata usw.).
Detail Informationen finden sich auf der Manpage von GIT.

Marius Hein
Marius Hein
Head of Development

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert, dann als Application Developer gearbeitet und ist nun Leiter der Softwareentwicklung. Ausserdem ist er Mitglied im Icinga Team und verantwortet dort das Icinga Web.