Dr. Hartmut Schorrig, www.vishia.org 2020-06-19

1. Vorteil Versionierung lokal speicherbar.

Die wesentliche Neuerung der Versionsverwaltungssysteme der dritten Generation, zu dem das weit etablierte git gehört, ist: Man kann lokal sein eigenes Git-Archiv oder Git-Repository haben. Damit kann die Arbeit an der Software unabhängiger zwischen mehreren Bearbeitern gestaltet werden, merge wird gut unterstützt. Insbesondere hat man lokal ohne Ankopplung an ein zentrales Netz die Versionshistorie zur Verfügung. Das kann essentiell sein, wenn man unabhängig arbeiten möchte an verschiedenen Standorten, im Urlaub ohne das sicherheitsgefährdete Hotel-Wlan zu nutzen, auf Baustelle mit ggf. schlechter Internet-Konnektivity etc. Die Synchronisation mit dem zentralen Archiv erfolgt dann Tage später, wenn man wieder online ist bzw. dies möchte.

Dies ist der bekannte Stand der Technik in git.

Die typische häufig anzutreffende und scheinbar so vorgedachte Arbeitsweise ist meist:

Workingdir
+- .git
+- Workingtree/files.edit

Man hat das git-Repository direkt im Arbeitsbereich, in dem man editiert. Diese Arbeitsweise wird durch einige Leistungsfähigkeiten von git unterstützt. Beispielsweise kann man mit git stash die eigene Arbeit mal schnell beiseite schieben, im Repository merken, um mit git checkout …​ einen alten Stand zu holen usw.

Bei dieser Herangehensweise sinkt die Bedeutung der eigenen Files. Sie werden zwischendurch auch mal schnell ins git-Archiv abgeschoben, es gibt nur einen Arbeitsbereich für alle Versionen. Auffällig ist auch dass die Bedeutung des Zeitstempels an den Files ungeschätzt ist, die Zeitstempel gehen verloren beim stash, checkout usw. Damit könnte ein Gefühl dafür wann die Änderungen erfolgt waren, verlorengehen.

2. .git in einem anderen lokal erreichbaren Verzeichnis

Es gibt eine andere Arbeitsweise mit getrenntem git-Workingdir, hier als git-Mirrordir bezeichnet, und dem git-unabhängigen eigenem Working-Directory oder auch mehreren Working-Directories für verschiedene Arbeiten, die mit einem auf der eigenen Festplatte (oder im eigenem lokalem immer verfügbarem Netz) synchronisiert sind. Der Schlüssel für diese Arbeitsweise liegt im git-Argument:

git --git-dir=D:/path/to/Mirrordir/.git ...further arguments

Damit wird dem git-command mitgeteilt, dass das .git-Repository nicht direkt im aktuellen Verzeichnis steht sondern auf einem erreichbaren File-path.

Es ergibt sich damit folgendes Schema:

WorkingdirA                            MirrorDir/
  .gitRepository ------------------+---> .git  ...Git-Repository
  WorkingDir/files                 |      MirrorDir/files ...als Git-Spiegel
                                   |
WorkingdirB                        |
  .gitRepository ------------------+
  WorkingDir/files

Es kann also mehrere Workingdir geben, die mit einem gemeinsamen Repository arbeiten. Das Repository ist nicht mehr die direkte Ablage der Working-Files, sondern spielt die Rolle eines Vergleichspunktes.

Man kann also die eigenen Files vergleichen mit den Ständen im Git-Archiv. Man kann die Versionen im Git-Archiv ändern, mit

git checkout VERSION

etc. und kann vergleichenderweise, ohne die eigenen Working-Files zu ändern, insbesondere auch mit stabilen File-Datum, die eigenen Files an Versionsständen spiegeln. Dieses 'spiegeln' kann über git-Befehle erfolgen, oder auch über den direkten File-Vergleich, der mit einigen Tools gut unterstützt wird. Man kann in verschiedenen Ständen parallel arbeiten, die Stände will man eigentlich zusammenhalten, es sind also eher nicht "Seitenzweige", aber man will sich diese Arbeit offenhalten. Es kann dabei sein, dass man bestimmte Arbeiten auch mal nicht eincheckt, sondern statt dessen einen zip-File ablegt, mit dem man direkt vergleicht, je nachdem welche Arbeitsweise situationsbedingt angenehm ist. Es erfolgt also eine geringere Unterordnung unter ein "Git-Regime', um es mal so auszudrücken. Dennoch soll der ordentlichen Versionierung nicht gegengeredet werden. Alles zu seiner Zeit.

Die Files im MirrorDir sind jederzeit die Stände aus dem Git-Archiv, ohne eigene Bearbeitungen, dort wird nicht editiert. Die Files sind aber flüchtig, können jederzeit bedenkenlos mit git checkout, git pull oder dergleiche Operationen überschrieben werden.

Das MirrorDir ist das lokalste Repository, vom Mirrordir wird gepusht und gepullt auf das eigentliche zentrale Git-Archiv.

Mit einem File .git anstatt des mit .git bezeichneten Archivverzeichnisses kann man die git-Commandline-Befehle direkt im Workingdir verwenden und auf das Archiv im Mirrordir zugreifen. Das File .git muss dazu enthalten:

gitdir: Path/to/.git

wobei dieser Path absolut sein kann/sollte, wenn der Mirror-tree im eigenem Filesystem irgendwo anders steht. Slash verwenden auch in Windows! See https://git-scm.com/docs/git-init option --separate-git-dir

3. Entwicklung im Seitenzweig

//Im git-Mirrordir:
_resyncRepository.jz.cmd
git checkout --force -b 2020-02-23

Damit wird die Entwicklung im benannten Seitenzweig fortgeführt. checkout bezieht sich auf den Workingspace. Es die letzte Version des bisherigen branch (meist 'master') ausgecheckt. Das --force bewirkt, dass auf bestehende Änderungen in den Files gegenüber dem Repository nicht geachtet wird, also einfach überschrieben werden. Das ist immer korrekt für das Mirrordir, denn die aktuellen File befinden sich im Workingdir. Ansonsten sollte man zuvor committen.

Wichtig ist das -b, es besagt dass ein neuer branch angelegt wird. Dessen Bezeichnung (tag-Name) kann einfach aus dem Datum abgeleitet werden, da man keinen großen Aufwand in Benamungen stecken sollte an dieser Stelle. Der Name muss neu in diesem Archiv sein.

Alle Commits werden nun in diesem Seitenzweig (branch) abgelegt.

//Im workingdir
git commit ....

Um nach der Entwicklung wieder auf den Hauptzweig zu kommen, gibt es drei Möglichkeiten:

3.1. Übernahme insgesamt als Hauptzweig

//Im git-Mirrordir:
git checkout --force master

Damit ist wieder der Stand vor dem Abzweig im Working space hergestellt. Der Seitenzweig ist aufgrund seiner commits im Archiv gespeichert.

git merge 2020-02-23

Ist der Seitenzweig linear und es gibt im Hauptzweig kein anderes commit, dann wird der gesamte Seitenzweig einfach als Hauptzweig übernommen. Es ist so wie wenn man ihn erst gar nicht angelegt hätte. Der Seitenzweig war aber zweckmäßig da es ja möglich gewesen wäre dass ein anderer Mitarbeiter auch in einem parallelen Seitenzweig committed hätte.

3.2. Übernahme nur des End-Standes in den Hauptzweig - squash

Will man die Details der Seitenzweigbearbeitung nicht public stellen, beispielsweise weil wegen Fehlersuche hin- und her-geändert wurde, oder weil man das Archiv übersichtlich, frei von vielen Änderungen halten will, dann hilft squash:

//Im git-Mirrordir:
git checkout --force master
git merge --squash 2020-02-23

Mit diesem Merge wird der letzte Stand des Seitenzweiges übernommen, der Seitenzweig wird aber 'zerdrückt'. Die Versionshistorie des Seitenzweiges bleibt im eigenem Archiv des git-Mirrordir erhalten, ist damit vorerst weiterhin sichtbar, wird aber in das push-Archiv nicht mit übertragen. Damit ist die squash-Historie weg, wenn das eigene Archive gelöscht wird und über pull neu gearbeitet wird. Der Seitenzweig ist auch nicht sichtbar wenn in andere git-Mirrorarchive gepullt wird.

3.3. Dokumentation als Seitenzweig im Hauptarchiv

Damit der Seitenzweig als solcher im Hauptarchiv (push-Archiv) erscheint, muss es künstlich einen Fortschritt im Hauptzweig geben. Entweder dieser ist sowieso gegeben, weil es andere Commits und push in den Hautpzweig gegeben hat. Dann braucht man nichts weiter tun als

git merge 2020-02-23

Beim merge werden die Änderungen automatisch gemerged, solange sie automatisch mergebar sind. Sind Files im Hauptzweig geändert, die im Seitenzweig unverändert, unbearbeitet sind (also an einem anderen Teil gearbeitet wurde), dann gibt es keine merge-Konflikte. Das ist immer der Fall, wenn im Team nur an verschiedenen Stellen gearbeitet wurde. Gibt es merge-Konflikte, dann ist es ratsam, diese vor dem Merge bereits zu lösen, siehe folgendes Kapitel.

Wenn es aber keinerlei Änderungen im Hauptarchiv gegeben hat, dann würde hier ungewollt der gesamte Seitenzweig als einzelne Fortschritte im Hauptarchiv erscheinen. Man muss also künstlich eine Änderung einbringen, in einem File der nicht im Seitenzweig bearbeitet wurde. Nun ist es zweckmäßig, dafür einen extra File anzulegen, einen Textfile, in dem möglicherweise nur dokumentiert wird, dass es einen Seitenzweig gegeben hat. Also einen speziellen Branchdocu.txt-File.

3.4. Merge-Konflikte vermeiden

Man kann nun im Mirror-Dir auf den Hautpzweig umschalten und vor dem Merge auf der Basis des direkten Filevergleichs feststellen, ob es im Hauptzweig von einem anderen Teammitarbeiter eine Änderung an den eigenen Files gegeben hat. Das kann jedenfalls passieren bei Schnittstellenfiles, oder auch bei Nachbesserungen mit Abhängigkeiten. Es ist wesentlich einfacher, diesen Konflikt vor dem Merge zu lösen. Möglicherweise muss/sollte auch persönliche Rücksprache über den Grund der Änderung geführt werden, oder die Änderung nochmals im Hauptzweig nachgebessert werden (zum Beispiel seites des Maintainer), damit die Versionen zusammenpassen.

Änderungen im Hauptzweig, die die eigenen Files betreffen, sind so in den eigenen Files zu übernehmen, dass sie zum Hauptzweig passen. Wenn sich Zeilen dann noch unterscheiden, aber im eigenen Seitenzweig alle Änderungen im Hauptzweig von der Abzweigversion des eigenen Seitenzweigs bis zur aktuellen Version wiederfinden, dann gelingt vom git auch der Merge mit differierenden Fileinhalten. Nur wenn git beim merge die Historie der Änderungen im Hautzweig nicht nachvollziehen kann, gibt es den Merge-Konflikt. Möglicherweise muss man Änderungen zunächst so übernehmen damit es passt, und dann mit einem folgenden Commit im Hauptzweig nochmal nachbessern.

TODO hier wäre ein gutes Beispiel angebracht, weil genau das immer unklar ist.