Veröffentlicht am: 30. April 2024

9 Minuten Lesezeit

Was ist neu in Git 2.45.0?

Hier sind einige Highlights der Beiträge des Git-Teams von GitLab und der breiteren Git-Community zur neuesten Git-Version, darunter reftables und bessere Tools für Referenzen.

Das Git-Projekt hat kürzlich [Git-Version

2.45.0](https://lore.kernel.org/git/[email protected]/ "Git Version

2.45.0") veröffentlicht. Werfen wir einen Blick auf die Highlights dieser

Version, die Beiträge des Git-Teams von GitLab und der gesamten

Git-Community enthält.

Reftables: Ein neues Backend zum Speichern von Referenzen

Jedes Git-Repository muss zwei grundlegende Datenstrukturen erfassen:

  • Den Objektgraphen, der die Daten deiner Dateien, die Verzeichnisstruktur,

Commit-Nachrichten und Tags speichert.

  • Referenzen, die auf diesen Objektgraphen verweisen, um bestimmte Objekte

mit einem leichter verständlichen Namen zu verknüpfen. Ein Branch ist zum

Beispiel eine Referenz, deren Name mit dem Präfix refs/heads/ beginnt.

Über 6,4 Mio. Builds pro Monat: So transformiert Siemens seine Softwareentwicklung mit GitLab Über 40.000 Entwickler(innen) bei Siemens nutzen GitLab, um weltweit zusammenzuarbeiten und jeden Monat mehr als 6,4 Millionen Software-Versionen automatisch bereitzustellen. Erfahre, wie eine offene DevOps-Kultur und eine zentrale Plattform die Effizienz und Sicherheit steigern. Erfolgsstory lesen

Das Format, in dem Referenzen in einem Repository auf der Festplatte

gespeichert werden, ist seit der Einführung von Git weitgehend unverändert

geblieben und wird als „files“-Format bezeichnet. Jedes Mal, wenn du eine

Referenz erstellst, legt Git eine sogenannte „lose Referenz“ an. Das ist

eine einfache Datei in deinem Git-Repository, deren Pfad mit dem Namen der

Referenz übereinstimmt. Zum Beispiel:



$ git init .


Initialized empty Git repository in /tmp/repo/.git/



# Das Updaten einer Referenz veranlasst Git dazu, eine “lose Referenz”


# zu erstellen. Diese lose Referenz ist eine einfache Datei, welche die


# Objekt-ID des Commits enthält.


$ git commit --allow-empty --message "Initial commit"


[main (root-commit) c70f266] Initial commit


$ cat .git/refs/heads/main


c70f26689975782739ef9666af079535b12b5946



# Wenn man eine zweite Referenz erstellt, wird diese als zweite lose


# Referenz gespeichert.


$ git branch feature


$ cat .git/refs/heads/feature


c70f26689975782739ef9666af079535b12b5946


$ tree .git/refs


.git/refs/


├── heads


│   ├── feature


│   └── main


└── tags



3 directories, 2 files


Von Zeit zu Zeit packt Git diese Referenzen in ein „gepacktes“ Dateiformat,

damit es effizienter wird, Referenzen nachzuschlagen. Zum Beispiel:



# Wenn man Referenzen packt, erstellt Git eine “packed-refs” Datei.


# Diese Datei enthält die sortierte Liste von vorher losen Referenzen.


# Die losen Referenzen existieren nicht mehr.


$ git pack-refs --all


$ cat .git/refs/heads/main


cat: .git/refs/heads/main: No such file or directory


$ cat .git/packed-refs


# pack-refs with: peeled fully-peeled sorted


c70f26689975782739ef9666af079535b12b5946 refs/heads/feature


c70f26689975782739ef9666af079535b12b5946 refs/heads/main


Dieses Format ist zwar ziemlich simpel, hat aber Einschränkungen:

  • In großen Mono-Repos mit vielen Referenzen stießen wir auf Probleme mit

der Skalierbarkeit. Das Löschen von Referenzen ist besonders ineffizient, da

die gesamte „packed-refs“-Datei neu geschrieben werden muss, um die

gelöschte Referenz zu entfernen. In unseren größten Repositorys kann dies

dazu führen, dass bei jedem Löschen einer Referenz mehrere Gigabyte an Daten

neu geschrieben werden müssen.

  • Da mehrere Dateien gelesen werden müssen, um alle Referenzen des Repos zu

ermitteln, ist dies atomar nicht möglich. Sobald andere Prozesse existieren,

die in das Repo schreiben wollen, kann es dadurch zu Inkonsistenzen kommen.

  • Es ist unmöglich, atomar mehrere Referenzen gleichzeitig zu schreiben,

weil dafür mehrere Dateien erstellt oder aktualisiert werden müssen.

  • Das Packen von Referenzen lässt sich nicht gut skalieren, weil die gesamte

„packed-refs“-Datei neu geschrieben werden muss.

  • Da lose Referenzen den Dateisystempfad als Namen verwenden, unterliegen

sie dem dateisystemspezifischen Verhalten. So können z. B. Dateisysteme, die

Groß- und Kleinschreibung nicht unterscheiden, keine Referenzen speichern,

bei denen nur die Groß- und Kleinschreibung unterschiedlich ist.

Um diese Probleme zu lösen, wurde mit Git v2.45.0 ein neues

„reftable“-Backend eingeführt, das ein neues Binärformat zum Speichern von

Referenzen verwendet. Dieses neue Backend wird schon sehr lange entwickelt:

es wurde ursprünglich von [Shawn

Pearce](https://sfconservancy.org/blog/2018/jan/30/shawn-pearce/ "Shawn

Pearce") im Juli 2017 vorgeschlagen und zunächst in

JGit implementiert. Das

Gerrit-Projekt nutzt

es bereits ausgiebig. Im Jahr 2021 hat [Han-Wen

Nienhuys](https://hanwen.home.xs4all.nl/ "Han-Wen Nienhuys") die Bibliothek

in Git hochgeladen, die es ermöglicht, das reftable-Format zu lesen und zu

schreiben.

Das neue „reftable“-Backend, das wir in Git v2.45.0 hochgeladen haben,

bringt nun endlich die reftable-Bibliothek und Git zusammen, so dass du das

neue Format als Speicher-Backend in deinen Git-Repositorys verwenden kannst.

Wenn du mindestens Git v2.45.0 verwendest, kannst du neue Repositorys mit

dem „reftable“-Format erstellen, indem du den Schalter

--ref-format=reftable entweder an git-init(1) oder an git-clone(1)

übergibst. Zum Beispiel:



$ git init --ref-format=reftable .


Initialized empty Git repository in /tmp/repo/.git/


$ git rev-parse --show-ref-format


reftable


$ find -type f .git/reftable/


.git/reftable/0x000000000001-0x000000000001-01b5e47d.ref


.git/reftable/tables.list



$ git commit --allow-empty --message "Initial commit"


$ find -type f .git/reftable/


.git/reftable/0x000000000001-0x000000000001-01b5e47d.ref


.git/reftable/0x000000000002-0x000000000002-87006b81.ref


.git/reftable/tables.list


Wie du siehst, werden die Referenzen jetzt im Verzeichnis .git/reftable

statt in .git/refs gespeichert. Die Referenzen und die Reflogs werden in

„tables“ gespeichert. Das sind die Dateien, die auf .ref enden. Die Datei

tables.list enthält die Liste aller derzeit aktiven Tabellen. Die

technischen Details rund um die Funktionsweise werden wir in einem separaten

Blogbeitrag erklären. Bleib dran!

Das „reftable“-Backend ist als vollwertiger Ersatz für das „files“-Backend

gedacht. Aus der Sicht der Benutzer(innen) sollte also alles gleich

funktionieren.

Dieses Projekt wurde von [Patrick Steinhardt](https://gitlab.com/pks-gitlab

"Patrick Steinhardt") geleitet. Dank gebührt auch Shawn Pearce, dem Erfinder

des Formats, und Han-Wen Nienhuys, dem Autor der reftable-Bibliothek.

Bessere Tools für Referenzen

Das Format „reftable“ löst zwar viele der Probleme, die wir haben, es bringt

allerdings auch einige neue Probleme mit sich. Eines der wichtigsten

Probleme ist die Zugänglichkeit der darin enthaltenen Daten.

Mit dem „files“-Backend kannst du im schlimmsten Fall deine normalen

Unix-Tools verwenden, um den Zustand der Referenzen zu überprüfen. Sowohl

die „gepackten“ als auch die „losen“ Referenzen enthalten menschenlesbare

Daten, die man leicht verstehen kann. Das ist beim „reftable“-Format anders,

da es sich um ein Binärformat handelt. Daher muss Git alle notwendigen Tools

bereitstellen, um Daten aus dem neuen „reftable“-Format zu extrahieren.

Auflisten aller Referenzen

Das erste Problem bestand darin, dass es im Grunde unmöglich ist, alle

Referenzen zu ermitteln, die ein Repository kennt. Das ist zunächst etwas

rätselhaft: Du kannst über Git Referenzen erstellen und ändern, aber es kann

nicht alle Referenzen auflisten, die es kennt?

Während es problemlos alle „normalen“ Referenzen auflisten kann, die mit dem

Präfix refs/ beginnen, verwendet Git auch sogenannte Pseudo-Referenzen.

Diese Dateien befinden sich direkt im Stammverzeichnis des

Git-Verzeichnisses und wären zum Beispiel Dateien wie .git/MERGE_HEAD. Das

Problem dabei ist, dass diese Pseudo-Referenzen neben anderen Dateien

liegen, die Git speichert, wie z. B. .git/config.

Während einige Pseudo-Referenzen bekannt und daher leicht zu identifizieren

sind, gibt es theoretisch keine Grenzen dafür, welche Referenzen Git

schreiben kann. Nichts hält dich davon ab, eine Referenz mit dem Namen

„foobar“ zu erstellen.

Zum Beispiel:



$ git update-ref foobar HEAD


$ cat .git/foobar


f32633d4d7da32ccc3827e90ecdc10570927c77d


Das Problem des „files“-Backends ist, dass es Referenzen nur aufzählen kann,

indem es Verzeichnisse durchsucht. Um herauszufinden, dass es sich bei

.git/foobar tatsächlich um eine Referenz handelt, müsste Git die Datei

öffnen und prüfen, ob sie wie eine Referenz formatiert ist oder nicht.

Das „reftable“-Backend hingegen kennt sämtliche Referenzen, die es enthält:

Sie sind in seinen Datenstrukturen kodiert, so dass es lediglich diese

Referenzen dekodieren und zurückgeben muss. Aufgrund der Einschränkungen des

„files“-Backends gibt es jedoch kein Tool, mit dem du alle vorhandenen

Referenzen ermitteln kannst.

Um dieses Problem zu lösen, haben wir git-for-each-ref(1) mit dem neuen

Flag --include-root-refs ausgestattet, das auch alle Referenzen auflistet,

die im Stammverzeichnis der Referenznamen-Hierarchie existieren. Zum

Beispiel:



$ git for-each-ref --include-root-refs


f32633d4d7da32ccc3827e90ecdc10570927c77d commit    HEAD


f32633d4d7da32ccc3827e90ecdc10570927c77d commit    MERGE_HEAD


f32633d4d7da32ccc3827e90ecdc10570927c77d commit    refs/heads/main


Für das „files“-Backend wird dieses neue Flag nach dem Best-Effort-Prinzip

behandelt, d.h. es werden alle Referenzen aufgenommen, die mit einem

bekannten Pseudo-Referenznamen übereinstimmen. Für das „reftable“-Backend

können wir einfach alle bekannten Referenzen auflisten.

Dieses Projekt wurde von [Karthik Nayak](https://gitlab.com/knayakgl

"Karthik Nayak") geleitet.

Auflisten aller reflogs

Jedes Mal, wenn du einen Branch aktualisierst, zeichnet Git diese

Branch-Aktualisierung standardmäßig in einem sogenannten reflog auf. Dieses

reflog ermöglicht es dir, Änderungen an diesem Branch rückgängig zu machen,

falls du eine unbeabsichtigte Änderung vorgenommen hast, und kann daher sehr

hilfreich sein.

Mit dem „files“-Backend werden diese Protokolle in deinem

.git/logs-Verzeichnis gespeichert:



$ find -type f .git/logs/


.git/logs/HEAD


.git/logs/refs/heads/main


Tatsächlich ist die Auflistung der Dateien in diesem Verzeichnis die einzige

Möglichkeit, um herauszufinden, welche Referenzen überhaupt ein reflog

haben. Dies ist ein Problem für das „reftable“-Backend, das diese Logs

zusammen mit den Referenzen speichert. Folglich gibt es keine Möglichkeit

mehr, herauszufinden, welche reflogs im Repository überhaupt existieren,

wenn du das „reftable“ Format verwendest.

Dies ist jedoch nicht wirklich die Schuld des „reftable“-Formats, sondern

eine Lücke in den von Git bereitgestellten Tools. Um diese Lücke zu

schließen, haben wir einen neuen Unterbefehl list für git-reflog(1)

eingeführt, mit dem du alle vorhandenen reflogs auflisten kannst:



$ git reflog list


HEAD


refs/heads/main


Dieses Projekt wurde von [Patrick Steinhardt](https://gitlab.com/pks-gitlab

"Patrick Steinhardt") geleitet.

Effizienteres Packen von Referenzen

Um effizient zu bleiben, müssen Git-Repositorys regelmäßig gewartet werden.

Normalerweise wird diese Wartung durch verschiedene Git-Befehle ausgelöst,

die Daten in die Git-Repositorys schreiben, indem sie den Befehl `git

maintenance run --auto` ausführen. Dieser Befehl optimiert nur die

Datenstrukturen, die tatsächlich optimiert werden müssen, damit Git keine

Computeressourcen verschwendet.

Eine Datenstruktur, die von der Git-Wartung optimiert wird, ist die

Referenzdatenbank, die mit dem Befehl git pack-refs --all optimiert wird.

Für das „files“-Backend bedeutet dies, dass alle Referenzen in die

„packed-refs“-Datei gepackt und die losen Referenzen gelöscht werden,

während für das „reftable“-Backend alle Tabellen in einer einzigen Tabelle

zusammengefasst werden.

Im Hinblick auf das „files“-Backend können wir nicht viel besser vorgehen.

Da die gesamte „packed-refs“-Datei ohnehin neu geschrieben werden muss, ist

es sinnvoll, alle losen Referenzen zu packen.

Für das „reftable“-Backend ist dies jedoch suboptimal, da sich das

„reftable“-Backend selbst optimiert. Jedes Mal, wenn Git eine neue Tabelle

an das „reftable“-Backend anhängt, führt es eine automatische Komprimierung

durch und führt die Tabellen nach Bedarf zusammen. Deshalb sollte sich die

Referenzdatenbank immer in einem gut optimierten Zustand befinden, sodass

das Zusammenführen aller Tabellen vergebliche Mühe wäre.

In Git v2.45.0 haben wir daher einen neuen Modus git pack-refs --auto

eingeführt, der das Referenz-Backend auffordert, nach Bedarf zu optimieren.

Während das „files“-Backend auch bei gesetztem --auto-Flag weiterhin

gleich funktioniert, verwendet das „reftable“-Backend die gleichen

Heuristiken, die es bereits für seine automatische Komprimierung verwendet.

In der Praxis sollte dies in den meisten Fällen kein Problem darstellen.

Außerdem wurde git maintenance run --auto so angepasst, dass das Flag

--auto an git-pack-refs(1) übergeben wird, um diesen neuen Modus

standardmäßig zu verwenden.

Dieses Projekt wurde von [Patrick Steinhardt](https://gitlab.com/pks-gitlab

"Patrick Steinhardt") geleitet.

Weiterlesen

In diesem Blogbeitrag ging es vor allem um das neue „reftable“-Backend, das

es uns ermöglicht, in großen Repositorys mit vielen Referenzen besser zu

skalieren, sowie um die zugehörigen Tools, die wir parallel dazu eingeführt

haben, damit es gut funktioniert. Natürlich hat die Git-Community mit dieser

Version auch verschiedene Leistungsverbesserungen, Fehlerbehebungen und

kleinere Funktionen eingeführt. Diese kannst du in der [offiziellen

Versionsankündigung](https://lore.kernel.org/git/[email protected]/

"offiziellen Versionsankündigung") des Git-Projekts nachlesen.

Wir möchten gern von dir hören

Hat dir dieser Blogbeitrag gefallen oder hast du Fragen oder Feedback? Erstelle ein neues Diskussionsthema im GitLab-Community-Forum und tausche deine Eindrücke aus.
Share your feedback

Mehr als 50 % der Fortune-100-Unternehmen vertrauen GitLab

Stelle jetzt bessere Software schneller bereit

Erlebe, was dein Team mit der intelligenten

DevSecOps-Plattform erreichen kann.