Die vielfältigen Fähigkeiten von Git, Teil 2

Copy and Paste mit Git

Eine Situation mit mehreren getrennten Zusammenhangskomponenten des Commit-Graphen lässt sich auch anders herstellen: Man konfiguriert dazu für das lokale Repository zwei verschiedene Remotes, zwei beliebige Repositorys, die inhaltlich nichts miteinander zu tun haben.

Das kann gelegentlich nützlich sein, um einzelne Dateien oder Verzeichnisbäume von einem anderen Repository zu übernehmen, ohne dass Commits des anderen Repositorys deshalb in die eigene Versionsgeschichte integriert werden.

Die oben schon benutzte Variante von git reset erlaubt es ganz allgemein, bequem Material (Dateien und Verzeichnisse) von einem beliebigen Commit direkt in den Index zu kopieren. Es ist dabei nicht nötig, einen Umweg über Kopien im Arbeitsverzeichnis zu gehen.

Der Ursprungs-Commit des Materials erscheint dadurch nicht als Vorgänger. Man kann also kurzfristig ein zweites Repository als ein neues Remote lokal einbinden, Material mit git reset kopieren und das Remote wieder entfernen. Dadurch entsteht keine dauerhafte Beziehung zum zweiten Repository.

Mehrere Repositorys synchronisieren: "Die Pumpe"

Eine kurzfristige Kopie eines Repositorys auf einem zweiten Server kann wie dargelegt nützlich sein. Aber auch für einen langfristigen Zweitwohnsitz eines Repositorys gibt es oft gute Gründe.

Im einfachsten Fall ist dabei die Version auf einem der Server die führende Instanz. In solchen Fällen nutzt man gerne eine "Pumpe". Damit ist eine Automatik gemeint, die regelmäßig alle Commits vom führenden Repository in das andere kopiert.

Wann kann man so etwas gebrauchen? Zum Beispiel wenn man ein intern benutztes Repository veröffentlicht hat, aber nicht alle Teammitglieder und CI/CD/Build-Pipelines zwingen will, sich Credentials für das öffentliche Angebot zu besorgen. Stattdessen bleibt das alte, intern benutzte Repository wie bisher in Gebrauch. Eine Pumpe hält die öffentlich sichtbare Instanz automatisch aktuell.

Ein anderer Fall aus der Praxis: Das Entwicklungsteam ist gewohnt, auf den Server eines Git-as-a-Service-Anbieters zuzugreifen. Aber ein alternatives Angebot bietet eine angenehme CI-Lösung (für dort gehostete Repositorys), die das Team nutzen möchte. Hier hilft ebenfalls eine Pumpe.

Eine Pumpe soll wartungsfrei laufen, gehört also automatisiert und in einem CI/CD-Job untergebracht. Die nötigen Funktionen sind in jeder passablen Skriptsprache schnell geschrieben. Es folgt eine kurze Übersicht, was dafür zu tun ist. Die hier beschriebene Beispielpumpe nutzt ein lokales Repository, sie soll von remote_from nach remote_to kopieren.

Den folgenden Befehl ruft die Pumpe zunächst auf:

git fetch -p remote_from
git fetch -p remote_to

um sich beidseitig auf den neuesten Stand zu bringen. Dann wird der Stand extrahiert:

git for-each-ref --format '%(refname) %(objectname)' refs/remotes/remote_from
git for-each-ref --format '%(refname) %(objectname)' refs/remotes/remote_to

und der jeweilige Output (einzeln) aufgefangen. Das ergibt je eine Liste von Branch-Namen (HEAD ignoriert man) mit SHAs. Bei aktuellen Versionen von git kann man sich das Leben übrigens noch einfacher machen, indem man %(refname:lstrip=3) benutzt.

Beide Listen kann man nun mit ein paar Zeilen Code der genutzten Skriptsprache vergleichen. Auf Unterschiede reagiert die Pumpe wie folgt:

Einen Branch branch_n von remote_from, den es auf remote_to noch nicht gibt oder der dort auf einen anderen SHA hinausläuft als auf remote_from, kopiert man mit

git push -q --force remote_to refs/remote/remote_from/branch_n:refs/heads/branch_n

Einen old_branch, den es auf remote_from nicht mehr gibt, aber noch auf remote_to, löscht man mit

git push -q remote_to :old_branch

Regelmäßig aufgerufen, zwingt die programmierte Pumpe remote_to stets auf denselben Stand wie remote_from.