Wie ich meiner Frau Git erklären würde

Um mit Git zu arbeiten ist es wahrscheinlich wichtiger als bei jedem anderen System, dass man ein Verständnis dafür entwickelt, wie das System arbeitet und funktioniert. Aus meiner Erfahrung in unzähligen Trainings zum Thema Git weiß ich, dass sich viele Entwickler verständlicherweise anfangs schwer tun, die Konzepte hinter Git zu verstehen.

Das Schöne ist, Git ist vom Prinzip und von den Konzepten sehr einfach und kinderleicht zu verstehen. Glauben Sie nicht? Ich erzähle öfter eine Geschichte, die das Verständnis für die Konzepte hinter Git ungemein erleichtert.

Die Idee zu dieser Geschichte stammt (leider) nicht von mir, sondern basiert auf dem wundervollen Blog-Eintrag “The Git Parable” von Tom Preston Werner (dem GitHub Gründer). Ich habe die Geschichte nur ein wenig adaptiert, damit ich sie in meinen Training verwenden kann. Meiner Ansicht nach ist dies der beste und unterhaltsamste Weg, sich dem Thema Git zu nähern.Die Geschichte ist auch Teil des Daily Git-Buches, das sie bei Amazon erwerben können.

Ein neues System

Lars ist motivierter junger Mann, mitte 30, lebt in Nürnberg und war bisher angestellt als Software-Entwickler bei einem mittelständischen Softwareunternehmen.
Lars hat nur ein großes Problem – er ist extrem ehrgeizig. Die Arbeit als einfacher Entwickler füllt ihn nicht mehr aus, er möchte mehr und entwickelt seit einigen Wochen insgeheim an seiner großen Geschäftsidee.
Diese Idee hat Potential und Lars sieht sich schon reich, berühmt und als Revolutionär in einer kleinen eCommerce-Nische, die er für sich entdeckt hat.
Heute ist ein neuer Tag, es ist Montag früh, Lars sitzt mit einem Kaffee an seinem Küchentisch und ist bereit mit der Arbeit an seinem neuen Produkt zu beginnen.

Die Umsetzung der Features geht flott voran und nach einigen Stunden hat Lars bereits das grundsätzliche Setup des Projektes fertiggestellt. Er beginnt mit der Umsetzung der ersten Features.
Während die Codezeilen fast wie von selbst auf dem Bildschirm landen fällt Lars auf, dass er eine wichtige Sache noch nicht bedacht hat.
Er arbeitet bisher ohne Versionskontrollsystem.
Kein professioneller Softwareentwickler würde jemals ohne Versionskontrolle arbeiten. Aber die Systeme, mit denen Lars bisher gearbeitet hat reizen ihn nicht.
Aus seiner täglichen Arbeit kennt er sich bestens mit Subversion und CVS aus, aber beide Systeme scheinen ihm für sein Projekt ungeeignet und zu aufwendig im Setup.
Sowohl Subversion als auch CVS sind sogenannte zentralisierte Versionskontrollsysteme. Für diese Systeme wird üblicherweise ein Server benötigt, der aufgesetzt, konfiguriert und gewartet werden muss.
Da Lars momentan allein arbeitet scheint ihm dieser Ansatz zu kompliziert. Hinzu kommt leider, dass Lars keine Zeit hat sich in mögliche andere Systeme einzuarbeiten.
Er braucht etwas einfaches, schnelles und vor allem, ein System das er versteht.

Was Lars eigentlich haben möchte sind einfache Snapshots des Projektes zu bestimmten Zeitpunkten. Beispielsweise immer dann, wenn er ein neues Feature abgeschlossen oder er einen wichtigen Meilenstein erreicht hat möchte er einen Snapshots erstellen und diesen Stand somit einfrieren.
Lars überlegt, was der einfachste Weg ist, diese Snapshots in seinem Projekt zu erstellen.
Für den Anfang reicht es Lars, wenn er einfach seinen kompletten Workspace mit allen Dateien die sich aktuell darin befinden in ein eigenes Verzeichnis ausserhalb des Workspaces sichert.
Er erzeugt sich also einen Ordner Snapshots, und in diesem Ordner einen ersten Ordner Snapshot-1, in den er den kompletten Inhalt seines Workspaces kopiert, nachdem das initiale Setup abgeschlossen ist.
Das einfachste System der Welt aber es erfüllt seinen Zweck.

Zusätzlich legt Lars in jedem Snapshot-Verzeichnis eine kleine Datei message.txt ab. In dieser Datei hinterlegt er das Datum an dem der Snapshot erstellt wurde sowie eine kleine Nachricht, was sich mit diesem Snapshot geändert hat.
Auf diese Art und Weise kann Lars sehr einfach ein Changelog seines Projektes erstellen, indem er über die Snapshots iteriert und die message.txt ausliest.
Zusätzlich kann er sich einen bestimmten Snapshot zurück in den Workspace holen, indem er ihn einfach direkt wieder dorthin zurück kopiert.

<code>16.10.2014
Initial Project Setup</code>

Lars kommt gut voran, denn er arbeitet wie besessen, schläft kaum noch, merkt keinen Unterscheid zwischen Tag und Nacht und vergisst jeglichen Tagesrhytmus.
Und so ist er nach wenigen Wochen bereits bei Snapshot-100 angelangt.
Der Snapshot-100 ist die erste Version, die Lars als Feature-Complete betrachten würde. Feature-Complete zumindest für eine erste Version – es könnte bereits Kunden geben, die
durchaus bereit sein könnten hierfür Geld auszugeben.

Er überlegt sich, eine Version 0.9 zu releasen. Damit wäre den Kunden klar, dass das System noch nicht perfekt ist.
Er telefoniert kurzerhand mit einigen seiner potentiellen Kunden und tatsächlich sind einige dabei, die Interesse an der Lösung haben und bereit sind, hierfür Geld auszugeben.

Lars möchte diesen besonderen Snapshot jetzt irgendwie markieren, denn er möchte sich nicht im Kopf merken, welche Snapshots jetzt als Releases an Kunden ausgespielt wurden. Für einen, evtl. sogar zwei Snapshots würde das noch funktionieren, definitiv aber nicht für mehr.
Wieder denkt Lars nach und überlegt sich, was die einfachste Möglichkeit wäre, einen Snapshot als Release zu markieren.
Die Systeme mit denen Lars bisher gearbeitet hat kennen das Konzept der Tags.
Tags sind unveränderliche Marken in der Historie, die wichtige Meilensteine markieren.
Genau dieses Konzept möchte Lars auch in seinem eigenen System etablieren.
Er erzeugt hierfür einfach einen neuen Ordner Tags, ebenfalls wieder ausserhalb des Workspaces.
In diesem Verzeichnis Tags erstellt er eine neue Datei Release-0.9.txt.
In dieser Datei steht nichts anderes als die Referenz auf den Snapshot, der das entsprechende Release markiert.

<code>Snapshot-100</code>

Um jetzt zu überprüfen, welche Snapshots bereits an Kunden ausgeliefert wurden muss Lars lediglich im Tags-Ordner die Dateien Release-* öffnen und sieht sofort die richtige Referenz.
Um zu erfahren, welche Version die neueste ist und damit welche Version aktuell beim Kunden liegt öffnet er einfach die Release-* Datei mit dem höchsten Rang und prüft, welche Referenz dort abgelegt ist.
Lars legt außerdem fest, dass Tags, die einmal erstellt wurden genauso wie Snapshots nicht veränderbar sind.
Der Release_0.9-Tag ist also für alle Zeit mit dem Snapshot-100 verbunden.

Lars ist zufrieden und arbeitet schnell weiter. Lars hat viele neue Ideen für Features und schon am Abend ist er bei Snapshot 103 angelangt.

Am darauffolgenden Tag klingelt plötzlich sein Telefon. Diesen Klingelton kennt Lars nicht. Als er abhebt hat er einen aufgeregten Kunden am Apparat, der sich über ein Problem in der Plattform beschwert. Lars hat seinen ersten Bug-Report. Das Problem ist trivial und Lars sieht sofort, wie es zu beheben ist. Er möchte schon direkt mit dem Bugfix beginnen und stellt plötzlich fest, dass sein Versionskontrollsystem eine kleine Lücke aufweist.

Er ist aktuell bei Snapshot 103, und hat bereits mit der Implementierung eines großen neuen Features begonnen. Dieses Feature ist noch nicht abgeschlossen und kann keinesfalls bereits an seinen Kunden ausgeliefert werden. Es wäre also sinnvoll, den Bugfix auf Basis des letzten Releases, also Snapshot-100 zu machen.

Wie aber lautet die Versionsnummer dieses Bugfix-Releases? Eine einfache Variante wäre Snapshot-104.
Bisher war es einfach zu erkennen, was der aktuellste verfügbare Snapshot war. Lars musste hiefür einfach den mit der höchsten Version wählen.
Das ist jetzt leider nicht mehr so einfach möglich, denn der neue Snapshot 104 hat zwar eine höhere Version, ist aber von den implementierten Features schon weit hinter
dem Snapshot 103 zurück.
Die Versionen sind jetzt zwar immer noch eindeutig, aber sagen nichts mehr darüber aus, was der Snapshot tatsächlich beinhaltet.

Da der Bugfix schnell an seine Kunden ausgeliefert werden muss bleibt es zunächst bei Snapshot 104 und Lars erstellt ein neues Bugfix-Release.
Natürlich erstellt er im gleichen Moment auch ein weiteres Tag.
Er legt also im Verzeichnis Tags eine weitere Datei Release-0.9.1.txt an.

<code>Snapshot-104</code>

Nachdem das Release ausgespielt ist atmet Lars zunächst einmal tief durch.
Das erste Problem ist gelöst. Wie aber soll er jetzt das Problem mit den Snapshots lösen?
Er sitzt an seinem Küchentisch und sinniert über die Problematik und es will ihm keine richtig gute Lösung einfallen.
Sein Blick schweift aus dem Fenster. Draussen geht ein leichter Herbstwind und er sieht den Apfelbaum in seinem Garten leicht im Wind hin und herwiegen.
Seine Gedanken schweifen ab und er überlegt sich unbewusst, dass ein Baum tatsächlich eine sehr interessante Konstruktion der Natur ist.
Egal wie weit man einem Ast folgt, egal wieviele Verästelungen man passiert – eins ist immer ganz einfach – den Weg zurück zum Stamm zu finden.
Und plötzlich fällt der Groschen und er schlägt sich mit der flachen Hand auf die Stirn.
Das grundsätzliche Problem seines Systems ist, dass er Versionen linear verwaltet.
Was er aber stattdessen braucht ist eine Baumstruktur.

Der Snapshot 104 war der erste Snapshot, der sich vom Stamm entfernt hat, wie ein erster kleiner Ast, der plötzlich gewachsen ist.
Was sein System aber im Gegensatz zum Baum im Garten nicht bietet ist von diesem Ast aus einen einfachen Weg zurück zum Stamm zu finden.

Branches

Jetzt da Lars das eigentliche Problem identifiziert hat ist eine Lösung schnell gefunden.
Für jeden Snapshot merkt sich Lars in der message.txt zusätzlich, welcher Snapshot der unmittelbare Vorgänger war.
Für den Snapshot 104 ist dies der Snapshot 100, für den Snapshot 103 ist dies der Snapshot 102.

branches.002

Ohne es zu wissen hat Lars eine extrem einfache und effiziente Art und Weise gefunden, parallele Entwicklung auf Zweigen zu realisieren.
Der Snapshot 104 ist genau betrachtet nichts anderes als der oberste Commit in seinem Release-Branch, auch wenn es diesen Branch offiziell bisher gar nicht gibt.
Der Snapshot 103 ist der oberste Commit in seiner Mainline.
Lars kann jetzt ohne Probleme auf Basis des Snapshot 104 einen weiteren Bugfix-Release erstellen, indem er den den Snapshot 104 einfach in seinen Workspace kopiert, die notwendigen Änderungen vornimmt und einen weiteren Snapshot erstellt, der als Parent den bereits existierenden Snapshot 104 referenziert.
Seine Mainline, also der Snapshot 103 ist hiervon überhaupt nicht betroffen.
Lars kann auf Basis des Snapshot 103 problemlos weitere neue Features implementieren, ohne dass sein Release-Branch davon betroffen ist.
Änderungen eines Snapshots können maximal diejenigen Snapshots betreffen, die über eine Parent-Referenz erreichbar sind.
Der Snapshot 105 ist von Snapshot 104 über keinen Weg erreichbar, genauso umgekehrt.

Das hantieren mit den Versionsnummern ist aber unbequem, denn Lars muss sich merken, dass seine Mainline derzeit auf Snapshot 105 steht. Diese Information existiert nur
in Lars Kopf. Lars findet aber grundsätzlich die Idee auf Branches zu arbeiten sehr attraktiv. Sollten es aber mehr Branches werden kann sich Lars unmöglich für alle Branches merken, auf welchen Snapshots diese aktuell stehen.
Er behilft sich mit einem sehr einfachen Workaround.
Er erstellt wieder ausserhalb seines Workspaces ein neues Verzeichnis /branches und in diesem Verzeichnis die beiden Dateien master.txt und release.txt.
In diesen beiden Dateien befindet sich nichts weiter als eine Referenz auf den jeweiligen Snapshot auf dem der Branch aktuell steht.

In der Datei master.txt steht also nichts weiter als:

<code>Snapshot-105</code>

Analog steht in der Datei release.txt:

<code>Snapshot-104</code>

Um auf Basis des letzten Releases also einfach einen neuen Bugfix-Release zu erstellen muss Lars lediglich in der Datei release.txt die Version des letzten Snapshot auslesen,
diesen in den Workspace kopieren und den Bug fixen. Lars darf nur keinesfalls vergessen nach jedem Erstellen eines neuen Snapshot die jeweilige Referenz in der master bzw. der release.txt Datei zu aktualisieren und zwar auf den gerade eben neu angelegten Snapshot.

Lars ist wirklich erstaunt, wie einfach und gut dieses Prinzip der Branches funktioniert.
Er wundert sich, dass ausser ihm scheinbar bisher noch niemand auf diese Idee gekommen ist.

branches.002

Um beispielsweise einen neuen Branch auf Basis des Snapshot 105 zu erstellen erzeugt Lars lediglich eine neue Datei feature-4711.txt im Ordner /branches.
In diese Datei schreibt er Snapshot 105. Damit ist klar, dass ein Branch mit dem Namen feature-4711 existiert, der aktuell auf dem Snapshot 105 steht.
Wechselt er jetzt auf diesen Branch, indem er den Snapshot aus feature-4711.txt in seinem Workspace kopiert kann er auf dieser Basis den neuen Snapshot 108 erstellen
und die Änderung betrifft zunächst nur den Branch und hat keinerlei Einfluss auf die Mainlinen oder den Release-Branch.

Lars hat die ganze Nacht hindurch gearbeitet und quält sich morgens völlig übermüdet aus dem Bett.
Er setzt sich sofort wieder an seinen Rechner und möchte weiterarbeiten. Er kann sich aber nicht mehr erinnern, was er gestern Nacht überhaupt implementieren wollte.
Er muss zunächst einmal wieder scharf nachdenken um überhaupt zu wissen, welcher Snapshot und welcher Branch da aktuell in seinem Workspace sind.
Das ärgert Lars so sehr, dass er sich hierfür eine Lösung überlegt.
Er müsste sich einfach irgendwo merken, was denn aktuell in seinem Workspace für eine Version liegt.
Er erzeugt sich ausserhalb des Workspaces eine neue Datei HEAD.txt. Wann immer Lars einen Branch und somit einen bestimmten Snapshot in seinen Workspace holt, dann
schreibt der den Namen des ausgecheckten Branches in diese Datei.
So ist immer klar, welche Version aktuell im Workspace liegt.
Holt sich Lars also beispielsweise den Feature-4711-Branch in seinen Workspace, schreibt er gleichzeitig feature-4711 in die Datei HEAD.
So kann er auch im völlig übernächtigten Zustand schnell herausfinden, woran er eigentlich gerade arbeitet.
Wechselt er jetzt vom Feature-4711-Branch auf den Release-Branch muss er nur sicherstellen, dass in der Datei HEAD anschließend release und nicht mehr feature-4711 steht.

Manchmal ist es auch notwendig, dass sich Lars einen Snapshot holt, der nicht auf einem aktuellen Branch liegt.
Nehmen wir an, der master-Branch steht aktuell auf dem Snapshot 109. Lars wundert sich über eine bestimmte Stelle im Code und er möchte sich gerne anschauen, wie der Stand im Source-Code vor dem Snapshot 109 war. Er kopiert sich also den Snapshot 108 in seinen Workspace.
Der Workspace steht also aktuell nicht auf einem Branch, sondern lediglich auf einer bestimmten Snapshot-Version.
Das passt noch nicht so ganz ins Konzept, denn bisher geht er davon aus, dass immer ein Branch ausgecheckt ist.
In diesem Fall wählt er den einfachsten Weg und schreibt in die Datei HEAD nicht den Namen des Branches sondern die tatsächliche Snapshot-Version.
Lars nennt das einen Detached HEAD, denn der ausgecheckte Snapshot ist nicht mit einem Branch verbunden.

Kollaboration

Einige Tage später stellt Lars fest, dass er den Aufwand zur Entwicklung der Plattform unterschätzt hat, denn er kommt nicht so schnell voran wie seine Kunden das erwarten.
Lars entschliesst sich, Hilfe zu holen. Er kennt Markus, einen sehr guten Entwickler in München, mit dem er bereits in mehreren Projekten zusammengearbeitet hat.
Nach einem kurzen Telefonat ist Markus von der Idee überzeugt und ist bereit mit einzusteigen.
Lars erklärt Markus kurz am Telefon das Konzept seiner selbstgestrickten Versionsverwaltung. Markus ist zunächst etwas skeptisch, dennoch willigt er ein, das System zu benutzen.
Lars schickt Markus also seinen Workspace, alle Snapshots und Tags in einem Zip-Archiv per Mail.

Die beiden Entwickler einigen sich kurz auf Arbeitspakete, die es zu erledigen gilt und arbeiten dann aufgrund der geografischen Trennung unabhängig voneinander.
Sie vereinbaren, jeden Tag abends kurz zu telefonieren und die jeweils neuesten Stände abzugleichen.
Sowohl Lars als auch Markus haben einen sehr produktiven Tag und jeder erzählt dem anderen abends stolz, was erreicht wurde.
Es stellt sich heraus, dass sowohl Lars als auch Markus jeweils drei neue Features umgesetzt haben. Lars ist mittlerweil bei Snapshot 109 angelangt. Markus …. auch.

Jetzt erst fällt Lars auf, dass die linearen und aufsteigenden Versionen, die er aus Subversion und CVS kennt zwar für klassische Systeme mit einem Server in der Mitte funktionieren, nicht jedoch für das dezentrale System, das sich Lars ausgedacht hat. Sowohl Lars als auch Markus arbeiten lokal und ohne Verbindung zum jeweils anderen. Eine Synchronisation der Stände findet nur abends statt, nachdem die beiden telefoniert haben.

Auf diese Art und Weise aber sind ständige Versionskonflikte vorprogrammiert. Lars denkt nach und versucht eine Lösung für dieses Problem zu finden.
Auf der Suche geht er nocheinmal die Liste an bisher erstellten Snapshots durch und versucht herauszufinden, was einen Snapshot eindeutig identifizieren und somit als Version dienen könnte.
Lars wird schnell fündig, denn was einen Snapshot eindeutig identifiziert ist die message.txt, die für jeden Snapshot angelegt wird.

<code>27.10.2014 17:13
Markus (markus@IT-Rockstar.de)
Customer Service IF
parent Snapshot-105</code>

Seit Markus mit an Bord ist haben sich die beiden Entwickler zusätzlich darauf geeinigt, dass sowohl der Name und die E-Mailadresse zusätzlich in die message.txt mit aufgenommen wird.
So ist sofort ersichtlich, wer einen Snapshot wann, auf welcher Basis und mit welchen Änderungen erstellt hat.
Wie aber kann Lars auf Basis dieser message.txt jetzt eine Version erstellen?
Die erste Idee die Lars einfällt ist mit einer Hash-Funktion zu arbeiten. Eine gute Hash-Funktion garantiert, dass die generierten Werte eindeutig sind und keine Konflikte auftreten können.
Die Wahrscheinlichkeit, dass ein Snapshot mit einer eine identischen message.txt vom gleichen Autor, zur gleichen Zeit aber mit unterschiedlichen Änderungen gespeichert wird kann getrost als unwahrscheinlich betrachtet werden.

Lars verwendet den SHA-1 Hash-Algorithmus und berechnet aus einer Datei message.txt beispielsweise den Hashwert 1a1a98e5b306ee1804c2106f919aa09e84d99356.
Dieser Hashwert ist zwar schwerer zu lesen als ein Snapshot-102, ist aber genauso eindeutig und deshalb als Version ganz brauchbar.
Lars startet also einen Konvertierungsprozess und konvertiert alle Versionen in die entsprechenden Hashwerte.
Hierbei konvertiert er alle Snapshots im /snapshots-Verzeichnis, alle Referenzen in den Dateien im Ordner /branches und auch alle Referenzen im Ordner /tags.

Eine typische message.txt sieht also nach der Konvertierung so aus.

<code>27.10.2014 17:13
Markus (markus@IT-Rockstar.de)
Customer Service IF
parent 1d22431e22ed6234ecd079f94d5277005f22b219</code>

Die Parent-Referenz ist jetzt ebenfalls einfach nur ein Hashwert des jeweiligen Parent-Snapshots.
Weder Lars noch Markus stört es aber besonders, dass die Versionen jetzt schwerer zu lesen sind als zuvor, denn üblicherweise arbeiten sie nur noch mit den
Branch-Namen wie master und nicht mit dem entsprechenden Hashwert.

Merges

Damit ist das Problem der Versionskonflikte ein für allemal gelöst. Sowohl Lars als auch Markus können fortan völlig problemlos und unabhängig voneinander arbeiten.
Wenn sie sich abends synchronisieren schickt Markus alle neuen Snapshots an Lars, der sich die Änderungen zunächst in einen Workspace kopiert und ein Code-Review vornimmt und anschließend die Änderungen von Markus in den master übernimmt.
Wie aber kann Lars die Änderungen von Markus übernehmen?
Nach kurzen Nachdenken scheint die Lösung auf der Hand zu liegen.
Es gibt bereits fertige diff-Algorithmen, die mit fast jedem Betriebssystem ausgeliefert werden. Lars untersucht einfach, welche Dateien sich bei Markus im Vergleich zu seinem letzten Snapshot geändert haben.
Anschließend erstellt Lars einen neuen Snapshot auf Basis seines letzten Snapshots und übernimmt in diesen zusätzlich alle Änderungen von Markus.
Für die meisten Dateien ist das kein Problem, da sie entweder von Lars oder aber Markus bearbeitet wurden, nicht jedoch von beiden.
Beide haben jedoch Änderungen an der pom.xml des Projektes vorgenommen, da für beide neuen Features neue Dependencies notwendig waren.
Für die pom.xml übernimmt Lars sowohl seine als auch die Änderungen von Markus und stellt anschließend sicher, dass das Projekt immer noch baut.
Lars fällt bei dieser Gelegenheit auf, dass der neue Snapshot nicht nur einen, sondern sogar zwei Parents hat, da sowohl Lars letzter Commit als auch Karls letzter Commit
als Basis dieses Snapshots dienen.

<code>28.10.2014 17:13
Lars (lars@IT-Rockstar.de)
Merged Karls changes
parent 6f66e0ce3f8c0b98f0b17ec5a5b1d7b509fe57c9
parent 1d22431e22ed6234ecd079f94d5277005f22b219</code>

Rebase

Die beiden kommen sehr gut voran und das Produkt entwickelt sich rasend schnell weiter.
Es wurden schon weitere Releases an die Kunden gegeben und alle sind sehr zufrieden mit dem Portal.
Markus arbeitet derzeit an einem sehr großen neuen Feature, das bisher kein Konkurrent anbietet.
Markus hat hierfür auf einem eigenen Branch bereits drei Snapshots erstellt.
Nach wie vor telefonieren Lars und Markus jeden abend um sich zu synchronisieren.
Beim heutigen Telefonat stellt sich heraus, dass Lars ein wichtiges Refactoring vorgenommen hat, das auch Markus gerne auf seinem Branch haben würde.
Markus denkt sich sogar, es wäre wünschenswert gewesen, wenn er seinen Branch direkt erst heute auf dem letzten Snapshot von Lars erstellt hätte und nicht schon gestern.
Der große Vorteil wäre, dass die Änderungen von Lars schon ab dem ersten Snapshot für Markus verfügbar gewesen wären.
Leider geht das nicht – oder etwa doch?
Markus spricht mit Lars darüber und Lars fängt sofort wieder an nachzudenken.

Was wäre, wenn Markus einfach den letzten Snapshot von Lars als Grundlage für seine beiden Snapshots verwendet.
Markus findet die Idee gut. Lars schickt Markus seine letzten Snapshots und Markus überlegt, wie er seine bereits erzeugten Snapshots so umschreiben kann,
dass sie auf den letzten Snapshots von Lars basieren.
Markus hat zwei Snapshots erstellt, die aufeinander basieren.
Er nimmt sich also den ersten Snapshot und macht zunächst ein Diff, um festzustellen, wie stark sich sein Snapshot von dem von Lars unterscheidet.
Markus kopiert den Snapshot von Lars in seinen Workspace und übernimmt zusätzlich alle Änderungen aus seinem Snapshot ebenfalls in den Workspace.
Markus gefällt das sehr, denn jetzt sieht es so aus, als hätte er die Änderungen direkt auf der Basis des Refactorings von Lars gemacht.
Markus erstellt aus dem neuen Workspace jetzt einen komplett neuen Snapshot. Dieser neue Snapshot hat natürlich einen komplett anderen Hashwert als sein voriger
Snapshot, denn der Parent-Zeiger in der message.txt hat sich geändert, und schon die kleineste Änderung an der Datei hat zur Folge dass ein komplett neuer Hashwert entsteht.
Anschließend übernimmt Karl die Änderungen aus seinem zweiten Snapshot ebenfalls in den Workspace und erstellt auf dieser Basis einen zweiten, neuen Snapshot, der ebenfalls wieder
einen neuen Hashwert erhält.

Seine beiden alten Snapshots kann Markus jetzt getrost löschen, denn sie werden nicht mehr gebraucht, da alle Änderungen in den beiden neuen Snapshots bereits vorhanden sind.
Markus schickt die beiden neuen Snapshots an Lars, der die Änderungen anschließend in den master-Branch übernimmt.

No space left on device

Einige Wochen später stellt Markus fest, dass durch die ganzen erzeugten Snapshots und die enormen Datenmengen seine Festplatte langsam an ihre Grenzen stösst.
Auch wenn sich pro Feature und Snapshot nur eine einzige Datei ändert wird aktuell für jeden Snapshot jede Datei dupliziert und in einem neuen Snapshot gespeichert.
Die gespeicherten Daten sind unglaublich redundant.
Als Markus abends mit Lars telefoniert spricht er dieses Problem an und die beiden überlegen, wie sie die Speichernutzung effizienter gestalten können.
Sie könnten natürlich im ersten Schritt anfangen, und alle Snapshots komprimieren. Das würde die Symptome lindern, aber nicht das Problem lösen.
Früher oder später werden die beiden wieder an die Grenzen der Platte stossen, da jeder Snapshot nach wie vor eine große Menge Speicherplatz verbraucht.

Lars verspricht Markus darüber nachzudenken. Er sitzt noch lange an seinem Küchentisch und grübelt. Draussen ist es längst dunkel und der Mond steht schon hoch am Himmel.
Lars hämmert seinen Kopf gegen die Tischplatte, denn er kommt einfach nicht auf eine saubere Lösung für dieses Problem.

Er geht noch einmal die Fakten durch. Pro Snapshot ändern sich durchschnittlich nur ganz wenige Dateien, die meisten Dateien bleiben identisch.
Was wäre, wenn pro Snapshot nicht die Dateien selbst, sondern Referenzen auf Dateien gespeichert werden würden?
Lars findet die Idee gut und macht ein kleines Experiment.

Zunächst erzeugt er einen neuen Ordner /objects.

Anschließend berechnet er für jede Datei in Snapshot-1 den SHA-1-Hashwert.
Er komprimiert diese Datei mit einem passenden Algorithmus und kopiert sie in das neue objects-Verzeichnis.
Im letzten Schritt benennt er die Datei noch um in den Hashwert, den er zuvor berechnet hat.
Im nächsten Schritt erstellt Lars für jedes Verzeichnis im Projekt eine temporäre Textdatei, in die er für jede Datei in diesem Verzeichnis einen Eintrag der folgenden Art vornimmt.

<code>blob 041cb757d223fdc2a9108af9b44034d33b063d79 pom.xml
tree 28d2f9e028c26ddc195e3c38281b1627f0509cd5 /src</code>

Diese Datei ist nichts anderes als eine Auflistung aller Inhalte eines Verzeichnisses mit den jeweiligen berechneten Hashwerten.
Für das obige Beispiel also speichert Lars, dass im Root-Verzeichnis eine Datei pom.xml mit dem Hashwert 041cb757d223fdc2a9108af9b44034d33b063d79 liegt, als auch
ein weiteres Unterverzeichnis, was er als tree markiert mit dem Hashwert 28d2f9e028c26ddc195e3c38281b1627f0509cd5 und dem Namen src.
Anschließend berechnet Lars noch den Hashwert über das komplette Verzeichnis und speichert diese Datei unter diesem Hashwert ebenfalls im objects-Verzeichnis.

Eine ähnliche Datei erstellt er für das hier referenzierte Unterverzeichnis src und speichert auch diese Datei unter ihrem Hashwert im objects-Verzeichnis.
Der urprünglichen Snapshot-Verzeichnis legt Lars nicht mehr die rohen Daten ab, sondern nur noch die Referenz auf das Root-Verzeichnislisting. Von diesem Listing aus kann
Lars alle Dateien für diesen Snapshot anhand der hinterlegten Hashwerte aus dem objects-Verzeichnis laden.

Nachdem der erste Snapshot konvertiert ist betrachtet Lars sein Werk. Er ist sich noch nicht ganz sicher, ob er viel gewonnen hat.

Er betrachtet den zweiten Snapshot und er führt nochmals diesselbe Prozedur durch.
Er berechnet die Hashwerte der vorhandenen Dateien und stellt für die meisten Dateien fest, dass die gezippten Dateien mit den Hashwerten bereits im objects-Verzeichnis vorhanden waren.
Nur die Dateien, die sich tatsächlich mit diesem Snapshot geändert hatten haben auch neue Hashwerte erhalten. Alle anderen Dateien muss Lars nicht doppelt speichern sondern kann sie einfach auch im zweiten Snapshot referenzieren.

Diese Prozedur führt er für alle Snapshots durch mit dem Ergebnis, dass nur noch ein minimaler Bruchteil des ursprünglichen Speicherplatzes verbraucht wird.

Schlussendlich

Durch die Änderung ist das System sowohl performanter als auch einfacher zu handhaben. Lars und Markus sind mittlerweile sehr zufrieden mit dem Funktionsumfang.
Das Projekt wird ein voller Erfolg.
Eines Tages surft Lars durch Zufall im Internet und stösst auf ein System, dessen Konzepte ihm sehr bekannt vorkommen.

GIT

Zufälligerweise ist Lars durch logisches kombinieren genau auf die gleichen Konzepte gekommen, die auch im dezentralen Versionskontrollsystem Git zur Anwendung kommen.

Effective Trainings & Consulting - Martin Dilger



Hat Ihnen dieser Blog-Eintrag gefallen? Ich stelle in diesem Blog Informationen über Tools, Frameworks und Werkzeuge zur Verfügung, die mich produktiver machen. Vielleicht kann ich auch Ihnen helfen, produktiver zu werden.


Ich unterstütze Sie als freier Mitarbeiter bei der Entwicklung von Software-Projekten, Agiler Arbeit sowie Schulungen / Fortbildungen.


Jeden Tag ein bisschen produktiver - ab heute