Im normalen Entwickleralltag nutzen wir die Standardmöglichkeiten, die
git
bietet. Aber gelegentlich können selten genutzte Fähigkeiten
von git
das Leben angenehm machen, wenn man sie denn kennt. Dieser
Artikel stellt einige solcher Fähigkeiten vor.
Wie ein roter Faden durchzieht „Vielfachheit“ die Fähigkeiten, die in diesem Artikel konkret vorgestellt werden. Damit ist gemeint, dass von einem Repository aus „mehrere“ verwaltet werden können, wo im normalen Alltag meistens „eins“ genügt. Beispiele gefällig? Man nutzt man im Normalfall nur einen Worktree (Verzeichnisbaum) auf der eigenen Festplatte, ein remote Repository „origin“ und einen Versionsgraphen im lokalen Repository.
Im Folgenden werden unter anderem Situationen vorgestellt, in denen mehrere Arbeitsverzeichnisse, mehrere Remotes und mehrere Versionsbäume sich nützlich machen.
Mehrere Arbeitsverzeichnisse
Eine solche Situation ist die Unterbrechung. Irgend etwas schiebt
sich in der Dringlichkeit nach vorne. Was man gerade getan hat, muss
unterbrochen werden und warten. Ein klassisches Beispiel aus der
DevOps-Praxis: Man entwickelt an einem Featurebranch. Plötzlich hängt
etwas in der CI/CD-Pipeline und ist im master
zu fixen, und zwar
schnell.
Es gibt mehrere Möglichkeiten, mit so einer Situation umzugehen.
Namentlich kann man git stash
benutzen, um den Featurebranchzustand
zu sichern; dann wechselt man vom Featurebranch zum master
und nimmt
die Arbeit am CI/CD-Problem auf.
Eine andere, probate Möglichkeit bietet sich dadurch, dass mit Git ein Respository auf der lokalen Festplatte mehr als ein Arbeitsverzeichnis („Worktree“) verwalten kann.
Man erzeugt so ein zweites Arbeitsverzeichnis aus der Wurzel des ersten heraus, zum Beispiel mit
Nun kann man mit einem schlichten cd ../worktree2
in das so erzeugte
Arbeitsverzeichnis wechseln, in dem master
bereits ausgecheckt ist,
und mit einem ähnlichen cd
wieder zurück ins ursprüngliche mit dem
Featurebranch.
Überhaupt ermöglichen multiple Arbeitsverzeichnisse bequemen und
schnellen Kontextwechsel. Zwei IDE-Instanzen können parallel laufen,
dann braucht man bei Bedarf nur zwischen den Fenstern zu wechseln.
In der geschilderten Beispielsituation, dass an der CI/CD-Pipeline etwas repariert werden muss, kann das sehr angenehm sein: Während der CI/CD-Job erst losläuft, hat man schon wieder die Arbeit am Featurebranch aufgenommen.
Intern hat worktree2
in seinem Verwaltungsverzeichnis .git
nur
eine Art Verweis auf das eigentliche Repository. Dadurch stehen alle
Commits, Branches, Remotes usw. in beiden gleichartig zur Verfügung.
Man kann also zum Beispiel in einem Arbeitsverzeichnis einen Commit zu
einem Branch hinzufügen und diesen Commit dann in einem zweiten
Arbeitsverzeichnis in einen anderen Branch hineinmergen.
Den Index gibt es naturgemäß für jeden Worktree einzeln. So bleiben
git add
hier und git add
dort unabhängig voneinander. Übrigens
wehrt sich Git dagegen, den selben Branch in zwei verschiedenen
Arbeitsverzeichnissen auszuchecken.
Hat man das Arbeiten mit parallelen Arbeitsverzeichnissen für sich
entdeckt, mag man vielleicht gleich beim Anlegen eines neuen
Featurebranches für ihn ein eigenes Arbeitsverzeichnis vorsehen.
Dafür bietet git add worktree
eine ähnliche Funktionalität -b new_branch
wie git checkout
. Man nutzt sie zum Beispiel so:
Ein solches Arbeitsverzeichnis lässt sich wieder abräumen, zum Beispiel mit folgender Zeile:
Die Arbeit mit mehreren Worktrees hat sich in der alltäglichen Praxis des Autors bewährt. Allerdings warnt die Dokumentation derzeit, dass dieses Feature von Git noch experimentell ist, und rät insgesondere davon ab, von Repositorys mit Submodulen mehrere Arbeitsverzeichnisse zu generieren.
Mehrere Remotes
Das Git-Repository auf der eigenen Festplatte kann mit mehreren „Remotes“ kommunizieren, also fernen Git-Repositorys, wie sie normalerweise auf irgendwelchen Git-Servern liegen.
Mehrere Remotes können zum Beispiel nützlich sein, wenn man temporär mit einer Person zusammenarbeitet, die auf den offiziellen Git-Server keinen Zugriff hat. Dem Autor ist das mehrfach passiert, zuletzt mit einer Praktikantin. Einerseits wünschte das betreffende Projekt ihre Mitarbeit. Andererseits wollte man für diese (kurzzeitige) Mitarbeit nicht den Aufwand treiben, der Praktikantin einen Account im Firmen-LDAP einzurichten. Am LDAP-Account hängt aber der Zugriff auf das offizielle Repo. Was nun? Kriegt man die Zusammenarbeit trotz dieser Rahmenbedingungen hin?
Das geht recht problemlos, indem man ein zweites, temporäres Remoterepo anlegt, auf das beide zugreifen können:
Schnell mal eben ein temporäres Remote
Es gibt dazu konkret verschiedene Möglichkeiten, wie man mal eben ein temporäres Repository aufsetzen kann, auf das zwei Personen A und B zugreifen können.
Besonders bequem hat man es, wenn es irgendwo einen Rechner gibt, auf
dem git
installiert ist und auf den sowohl A als auch B per ssh
zugreifen können.
Das könnte ein ohnehin vorhandener Server sein. Auch eine kurzfristig in irgendeiner Cloud oder bei einem Provider angemietete virtuelle oder reale Maschine ist brauchbar. Selbst ein Winzling (ein Raspberry Pi oder ähnliches) reicht für diesen Zweck. Einer der beiden Arbeitsplatzrechner (der von A oder der von B) lässt sich ebenfalls prima einsetzen, Vertrauen zwischen A und B vorausgesetzt. Man kann auch einen Dockercontainer nutzen.
Für so einen temporären Repositoryserver braucht man sich übrigens normalerweise keine Gedanken über Backups zu machen (was die Sache weiter vereinfacht). Denn alle Information sind in den lokalen Repositorys auch vorhanden, und was wichtig ist, wandert über kurz oder lang ins offizielle Repository. Nach Ende der Kooperation kann das temporäre Repository einfach ohne Weiteres gelöscht werden und gut ist.
Ist ein entsprechender Server gefunden, so ist das nötige temporäre Respository mit wenigen Handgriffen eingerichtet.
Im folgenden Beispiel haben beide auf dem Host server.example.org
Zugriff auf den gemeinsam genutzten User user
. Einer von beiden
bringt die Sache in Gang etwa so:
Anschließend richtet A für sein lokales Repository ein neue Verbindung
zu einem „Remote“ mit dem Namen collab
ein, mit
und füllt dieses neue temporäre Repo mit
B holt sich dieses Material mit
Nutzen A und B für den ssh
-Zugriff auf server.example.org
unterschiedliche Nutzer auser
und buser
, so wird es geringfügig
komplizierter. Gewöhnlich wird man eine gemeinsame Gruppe z.B.
ggroup
auf server.example.org
finden oder erstellen, der auser
und buser
beide angehören.
In diesem Fall kann A das Repository so in Gang bringen:
Anschließend nutzt Person A [email protected]:collab-repo
und
Person B [email protected]:/home/auser/collab-repo
für den
Zugriff.
Eventuell ist es noch nötig, /home/auser
mit chmod a+rx
/home/auser
für lesende Zugriffe zu öffnen. Das ermöglicht unter
Umständen lesenden Zugriff durch andere User von server.example.org
auch auf andere Dateien von auser
. Will man das vermeiden, nutzt
man alternativ ein neutrales Verzeichnis (/var/lib/collab-repo
oder
ähnlich).
Arbeiten mit einem temporären Kollaborationsrepository
Wenn man so ein temporäres Repository erst einmal hat, können die beiden beteiligten Personen A und B bequem damit arbeiten.
B möchte zum Beispiel in einem Featurebranch arbeiten:
A kann neues Material aus dem offiziellen Repository jederzeit zur Verfügung stellen:
B nimmt dieses Material entgegen, wie bei Featurebranches üblich:
B kann Material zur Verfügung stellen:
und A schaut es sich an:
(Alternativ richtet A sich dafür einen eigenen Worktree ein.)
Soll dieses Material im offiziellen Repository als Featurebranch auftauchen, so kann A es dort zugänglich machen:
Ross und beide Reiter nennen
Bei der vorgestellten Arbeitsweise bleiben die Commits von B erhalten. Sie finden sich später komplett mit Checkin-Kommentar, Zeitstempel und Autorenangabe „B“ im offiziellen Repo. Dass sie durch Vermittlung von A dort gelandet sind, geht aus dem Repositoryinhalt nicht mehr hervor.
Das kann im Einzelfall erwünscht sein oder unerwünscht. Möglicherweise möchte man die Mitwirkung von A langfristig nachvollziehen können. Für diesen Zwecke bietet sich eine andere selten genutzte Vielfachheit an, die Git bietet.
Und zwar kann selbstverständlich jedes Versionsmanagementsystem von Welt für jede Änderung die Frage beantworten: „Wer war das?“ Git bietet an dieser Stelle Mehrwert: Für jeden Commit werden nicht nur ein, sondern zwei Verantwortliche ins Repository eingetragen. Als „Author“ wird in der Git-Terminologie die Person bezeichnet, die die Änderung inhaltlich entwickelte, als „Committer“, wer sie ins Repository eintrug. Die beiden sind häufig identisch, aber sie können durchaus verschieden sein.
Einen neuen Commit kennzeichne ich als eintragende Person als den inhaltlichen Beitrag von, zum Beispiel „A. U. Thor“, mit einem Befehl wie
Die entstehenden kompletten Angaben kann man sich mit git log
anschauen, für den letzten Commit zum Beispiel mit
Ein beispielhafter Output sieht aus wie folgt:
Es ist auch möglich, sich nachträglich als Committer einzutragen. Dazu erstellt man einfach eine passende Kopie dieses Materials. In der oben beschriebenen Situation könnte A dazu folgende Befehle ausführen:
Repo mit einem Commit
Die bisher vorgeschlagene Methode der Zusammenarbeit von A und B basiert auf vollwertigen Repositorys, die jeweils die gesamte Vergangenheit des Projekts mit an Bord haben. Diese historische Vollständigkeit ist manchmal nützlich, aber gelegentlich auch überflüssiger Ballast. Das Hauptthemas des Artikels, „Vielzahl statt Einzahl“, wird deshalb hier kurzfristig umgekehrt: Es soll von Repositorys die Rede sein, die statt der sonst üblichen Gesamthistorie nur einen Commit enthalten.
Eine (teilweise) Kopie eines Repositorys mit unvollständiger Historie
nennt die Git-Dokumentation „shallow“. Man erzeugt sie mit einer
entsprechenden --depth
-Option von git clone
.
Als Beispiel soll eine Zusammenarbeit zwischen A und B diesmal auf dem
Featurebranch f_branch
erfolgen. Den hat A bereits angelegt. Dann
kann A ein Repository mit nur dem letzten Commit von f_branch
erzeugen und an B weitergeben. Das geht notfalls sogar ohne eigenen
Git-Server.
Angenommen, das lokale Repo von A liegt in $HOME/lokal-repo
. Dann
erzeugt A die unvollständige Kopie (in einem Verzeichnis, das
vorzugsweise gerade nicht in einem Git-Worktree liegt) mit:
Die so erzeugte teilweise Repository-Kopie enthält zunächst noch einen
Verweis auf das eigene lokale $HOME/lokal-repo
. Den entfernt A:
Das so präparierte Repository kann A nun in ein ZIP-Archiv o.ä. einpacken und dieses auf irgend einem passenden Weg B zur Verfügung stellen. So ein Vorgehen empfiehlt sich vor allem dann, wenn ein SSH-Server noch nicht gefunden ist, die Netzwerkverbindung zwischen A und B lahmt und B möglichst schnell loslegen soll.
B wird dieses Archiv wieder auspacken und das entstehende Repository
als Basis für eine git clone
- Operation benutzen. Mit dem daraus
entstehenden Arbeitsrepository arbeitet B dann lokal weiter wie
gewohnt.
Später soll B Ergebnisse zur Verfügung stellen. Man kann dann
problemlos nachträglich „aufrüsten“ auf einen gemeinsam erreichbaren
Clone, auf den B Schreibzugriff hat, zum Beispiel ssh
-basiert wie
oben.
Wenn nur Textdateien geändert wurden, kann B auch eine „Patchdatei“ an
A schicken, also den Output von git diff
. Das geht bequem via
E-Mail. Mit git apply
und anschließendem git commit --author
...
kann A die Ergebnisse von B lokal integrieren.
Unvollständige Kopien: Auch sonst nützlich!
Die Idee, sich beim Kopieren eines Repositorys auf den letzten Commit
zu beschränken, ist auch sonst gelegentlich nützlich. Viele Builds,
CI-Pipelines und ähnliche automatisierte Prozesse können auf einfache
Weise erheblich beschleunigt werden, wenn einem ohnehin
vorhandenen Befehl git clone
die Option --depth 1
mitgeben wird.
Derselbe Trick kann auch nützlich sein, wenn man auf den Inhalt eines OpenSource-Repositorys neugierig ist. Man will gerne so schnell wie möglich einen Worktree auf der eigenen Festplatte begutachten können. Die Historie des Repositorys interessiert erst später, in zweiter Linie.
In diesem Fall besorgt man sich das Repository zunächst mit git clone
--depth 1
. Das geht schnell, entsprechend bald kann man anfangen,
sich im Worktree umzusehen. Währenddessen lässt man im Hintergrund
den langen Download der Gesamthistorie laufen:
Wenn man das auf einem Repository mit vollständiger Historie aufruft, erscheint natürlich eine Fehlermeldung. Bei einigen Git-Versionen ist diese Fehlermeldung verwirrend falsch ins Deutsche übersetzt worden:
Das sollte „… mit vollständiger Historie …“ heißen.
Wenn man von einem Repository beim initialen Klonen nur den letzten
Commit haben wollte, holt Git auch bei späteren git
fetch
-Operationen zunächst nur den betreffenden Branch. Bei von
Anfang an komplett geklonten Repositorys holt git fetch
dagegen
normalerweise alle vorhandenen Branches. Man kann nachträglich
konfigurieren, dass alle Branches geholt werden sollen, mit:
Mehrere Identitäten
Im Zusammenhang mit OpenSource tritt manchmal ein Problem auf. Und zwar betrifft es Entwicklerinnen und Entwickler, die für „Lohn und Brot“ in kommerziellen Projekten arbeiten und sich zusätzlich in ihrer Freizeit für Open-Source Projekte engagieren. Auf den ersten Augenschein hin sieht es so aus, als müssten sie sich entscheiden: Welche E-Mail-Anschrift sollen sie für die eigenen Git-Aktivitäten benutzen: die berufliche oder die private?
Viele entscheiden sich für die private.
Es kann ganz amüsant sein, sich für ein internes kommerzielles Repository von
example.com
eine Gesamtliste der im Projekt benutzten E-Mail-Anschriften zu
besorgen, die nicht zu example.com
gehören:
Obwohl solche Kommandozeilen oft viel Output erzeugen, besteht dieses Problem eigentlich nur scheinbar. Denn Git erlaubt durchaus mehrere Identitäten derselben Person: Man kann die eigene Identität für jedes Repository einzeln konfigurieren.
Konkret wechselt man in ein Repository und konfiguriert dort
spezifisch die in die Commits dieses Repositorys einzutragende eigene
E-Mail-Anschrift, sowie, wenn man für den Namen eine andere Variante
benutzen möchte als sonst, auch diesen. Dafür genügt es, git
config
wie bei der Ersteinrichtung von git
aufzurufen, aber
jetzt die Option --global
wegzulassen. Dadurch wird die
Konfiguration Repository-spezifisch:
Mehrere Repositorys synchronisieren: Die Pumpe.
Eine kurzfristige Kopie eines Repositorys auf einem zweiten Server kann nützlich sein, wie dargelegt. 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 Funktionalität gemeint, die automatisch 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. Statt dessen bleibt das alte, intern benutzte Repository in Gebrauch wie bisher. Eine Pumpe hält die öffentlich sichtbare Instanz automatisch aktuell.
In unserer Praxis hatten wir neulich einen anderen Fall: 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 wir nutzen wollten. Auch hier half eine Pumpe.
So eine Pumpe soll wartungsfrei laufen, gehört also automatisiert und
in einen CI/CD-Job untergebracht. Die nötige Funktionalität ist 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.
Die Pumpe wird zunächst aufrufen
um sich beidseitig auf den neuesten Stand zu bringen. Dann wird dieser Stand extrahiert, indem
läuft und der jeweilige Output (einzeln) aufgefangen wird. Das ergibt
je eine Liste von Branchnamen (HEAD
ignoriert man) mit SHAs. Bei
aktuellen Versionen von git
kann man sich das Leben übrigens noch
leichter machen, indem man hier %(refname:lstrip=3)
benutzt.
Beide Listen kann man nun mit ein paar Zeilen Code der genutzten Skriptsprache vergleichen. Auf Unterschiede wird reagiert wie folgt:
Ein 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
Einen old_branch
, den es auf remote_from
nicht mehr gibt, aber
noch auf remote_to
, löscht man mit
Regelmäßig aufgerufen, wird die so programmierte Pumpe remote_to
stets auf denselben Stand zwingen, den remote_from
hat.
Mehrere Credentials im Zugriff
Innerhalb der CI/CD-Umgebung braucht die Pumpe zwei Credentials, um auf beide Repositorys zuzugreifen. Ein gängiger Weg ist, dass Credentials in CI/CD-Umgebungen mit Hilfe von Umgebungsvariablen zur Verfügung gestellt werden. Wie kann man das praktisch organisieren?
Das kommt auf den Zugriff an. Da gibt es (passend zum Thema des Artikels) wieder verschiedene Möglichkeiten. Die üblichen sind SSH und HTTPS.
SSH Credentials
Auf ein entferntes („remote“) Repository [email protected]
(oft
[email protected]
) wird mit SSH zugegriffen. Damit der Zugriff
funktioniert, sind normalerweise zwei Bedingungen zu erfüllen:
Der SSH-Hostkey des Servers ist lokal bekannt.
Der SSH-Key des lokalen Users ist auf dem Server eingetragen mit den entsprechenden Berechtigungen.
Um die erste Bedingung zu erfüllen, fängt man den Hostkey einmalig manuell auf mit
Wenn man mit diesem Host auf dem eigenen Rechner schon gearbeitet hat,
überprüft man, ob der so aufgefangene Host-Key in der neuen Datei
known_hosts
im derzeitigen Verzeichnis mit dem in der zentralen
$HOME/.ssh/known_hosts
übereinstimmt. Dazu vergleicht man den
Output von:
Damit haben wir den öffentlichen Host-Key in der Datei known_hosts
im derzeitigen Verzeichnis. An dieser Datei ist nichts Geheimes. Sie
kann z.B. in ein Git-Repository eingecheckt und in ein Dockerimage
kopiert werden. Um sie zu nutzen, kopiert man sie ins Verzeichnis
$HOME/.ssh
des betreffenden Benutzers, der in der CI/CD-Umgebung die
Pumpe ausführt.
Will man auf mehrere Server via SSH zugreifen, so werden die
entsprechenden Angaben hintereinander in eine gemeinsame known_host
kopiert.
Das eigentliche Credential für die Pumpe ist der private SSH-Schlüssel. Den erzeugt man zum Beispiel mit
Dieses Kommando erzeugt zwei Dateien, id_rsa.pub
und id_rsa
. Die
Datei id_rsa.pub
ist der öffentliche Schlüssel, den kopiert man
einmalig in die entsprechende UI des Gitservers. Diese Datei braucht
nicht geschützt oder geheimgehalten werden. Die Pumpe selbst benötigt
diese Datei übrigens nicht.
Der Inhalt von id_rsa
ist dagegen zu schützen. Wer diesen Inhalt
kennt, der kann auf die entsprechenden Repos auf dem Gitserver
zugreifen (also genau das, was die Pumpe tun soll). Diesen Inhalt
konfiguriert man in der entsprechenden UI des CI/CD-Umgebung als
„Geheimnis“, das während eines Laufes der Pumpe als Umgebungsvariable
zur Verfügung steht, zum Beispiel GIT_PRIVATE_KEY
.
Damit die Pumpe diesen Key nutzen kann, läuft am Anfang des Jobs:
Die Datei known_hosts
gehört ins selbe Verzeichnis $HOME/.ssh
.
HTTPS Credentials
Beim Zugriff auf ein Git-Repository via HTTPS kommt man mit
klassischen User und Password-Angaben weiter. Die stellt ein Skript
zur Verfügung, das darauf wartet, mit dem Parameter get
aufgerufen
zu werden, um dann zwei Zeilen Output zu erzeugen: Eine mit der
User-Angabe, eine für das Password.
Dieses Skript kann so aussehen:
Die nötigen Angaben konfiguriert man in der jeweiligen CI/CD-Umgebung
als Geheimnisse, die in den Umgebungsvariablen GIT_USER
und
GIT_PASSWD
zur Verfügung stehen.
Dieses Skript stellt man für die Pumpe zur Verfügung, im Beispiel
eines Docker-Container vielleicht als /usr/local/bin/gitcreds
. Es
wird dann für Zugriffe auf einen Git-Server https://git.example.org
aktiviert durch den Befehl
Wenn man Credentials für mehrere Server braucht, arbeitet man
möglicherweise mit mehreren Instanzen gitcreds1
, gitcreds2
und so
weiter. Alternativ genügt auch ein einzelnes Skript. Indem es
STDIN
auswertet, kann es erfahren, welche Credentials gerade
gebraucht werden. Einzelheiten hierzu finden sich in der
Git-Dokumentation; der passende Einstieg ist die Beschreibung von
„Credential Helpers“ in
api-credentials.
Mehrere Bäume
Nach diesem Ausflug zur „Pumpe“ sei abschließend noch auf eine etwas obskure Mehrfach-Möglichkeit von Git eingegangen. Sie wird nicht so oft gebraucht, aber wenn, ist sie da: Und zwar kann Git im selben Repository auch komplett getrennte Branchbäume verwalten.
Normalerweise gibt es in einem Repository nur einen Commit ohne Vorgänger: Den ersten. Aber es kann durchaus auch mehrere solcher Commits geben. Graphentheoretisch ausgedrückt: Der Commitgraph kann unzusammenhängend sein.
Man kann in einem existierenden Repository einen neuen leeren Branch ohne jede Commits und Vorfahren erzeugen zum Beispiel mit
Die Situation ist ähnlich der nach git init
: Ein Commit, den man in
dieser Situation erzeugt, ist ein „erster“ Commit, also ein Commit
ohne Vorgänger. Man fängt sozusagen noch einmal ganz von vorne an.
Dies kann nützlich sein unmittelbar vor der Veröffentlichung bisher interner Arbeit. Etwas hat klein angefangen, als privates Feierabendprojekt einer einzelnen Person, hat einige Irrungen und Wirrungen durchlebt, aber sich dann ganz manierlich entwickelt und soll nun als Open-Source-Repository debütieren.
In dieser Situation ist es eine offene Frage, ob man die Irrungen und Wirrungen mit veröffentlichen will. Die genauen Zeitstempel, wann man seinerzeit aktiv war, sind verhältnismäßig intime personenbezogene Daten, die man vielleicht nicht zur Verfügung stellen will. An die Stelle der Gesamthistorie kann man ein Art „Paukenschlag-Commit“ treten lassen, der das gesamte bisher Erreichte sozusagen „aus dem Nichts“ entstehen lässt.
Ein beispielhaftes Vorgehen (das anschließend im Detail besprochen wird) ist:
Hier wird zunächst der bisherige master
umbenannt in old-master
.
In diesem Augenblick gibt es keinen Branch master
in diesem
Repository. In der nächsten Zeile wird master
als leerer Branch
neu hergestellt, ohne Commit, Vorgänger oder Historie.
Anschließend wird eine Variante von git reset
benutzt. Diese
Variante kopiert den gesamten Datenbestand des letzten Commits von
old-master
direkt in den Index. (Man beachte den diese Kommandozeile
abschließenden Punkt.)
Es ist etwas ungewohnt, dass die Daten direkt im Index landen und
nicht im Arbeitsverzeichnis. Wer das nicht weiß, den kann der Output
von git status
verwirren.
Mit dem abschließenden git commit
wird ein neuer Commit ohne
Vorgänger angelegt (und nebenbei wird das Arbeitsverzeichnis gefüllt).
Man kann sich anschließend mit gitk
oder git log --pretty=raw
davon überzeugen, dass der neue Commit tatsächlich keine Vorgänger
hat. Dieser Commit ist der gewünschte „Paukenschlag-Commit“,
der veröffentlicht werden kann.
Copy+Paste mit Git
Eine Situation mit mehreren getrennten Zusammenhangskomponenten des Commitgraphen lässt sich auch anders herstellen: Man konfiguriert dazu für das lokale Repository zwei verschiedene Remotes, irgendwelche zwei 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 Ursprungscommit 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 keinerlei dauerhafte
Beziehung zum zweiten Repository.
Fazit
Git bietet vielfache Möglichkeiten auch abseits des allgemein Üblichen. Wenn man sie kennt, kann man sich immer mal wieder das Leben leichter machen. Obendrein hilft die Kenntnis dieser Möglichkeiten, besser zu durchschauen, was man beim Üblichen eigentlich tut. Das verschafft das angenehme Gefühl, bei der Nutzung von Git mehr „Wasser unter den Kiel“ zu haben.