Veröffentlicht am: 16. Juni 2025

12 Minuten Lesezeit

Was gibt es Neues in Git 2.50.0?

Beiträge des Git-Teams von GitLab und der Git-Community, inklusive der Befehl git-diff-pairs(1) und die Option git-rev-list(1) für gebündelte Referenz-Updates.

Das Git-Projekt hat kürzlich Git Version 2.50.0 veröffentlicht. Werfen wir einen Blick auf die Highlights dieser Veröffentlichung, die Beiträge des Git-Teams von GitLab und der gesamten Git-Community enthält.

Neuer Befehl git-diff-pairs(1)

Diffs sind das Herzstück jeder Code Review und zeigen alle Änderungen, die zwischen zwei Revisionen vorgenommen wurden. GitLab zeigt Diffs an verschiedenen Stellen an, am häufigsten aber auf der Registerkarte „Änderungen“ (in englischer Sprache verfügbar) eines Merge Requests.

Im Hintergrund wird die Diff-Generierung von git-diff(1) verwendet. Ein Beispiel:


$ git diff HEAD~1 HEAD

Dieser Befehl gibt das vollständige Diff für alle geänderten Dateien zurück. Dies kann eine Herausforderung für die Skalierbarkeit darstellen, vor allem, wenn die Anzahl der Dateien, die innerhalb einer Reihe von Revisionen geändert wurden, sehr groß ist. Dies kann dazu führen, dass der Befehl selbst auferlegte Zeitlimits für das GitLab-Backend erreicht. Bei großen Änderungen wäre es besser, wenn

es eine Möglichkeit gäbe, die Diff-Berechnung in kleinere, leichter verarbeitbare Blöcke zu unterteilen.

Eine Möglichkeit dafür ist die Verwendung von

git-diff-tree(1) (in englischer Sprache verfügbar), um Informationen

über alle geänderten Dateien abzurufen:


$ git diff-tree -r -M --abbrev HEAD~ HEAD

:100644 100644 c9adfed339 99acf81487 M      Documentation/RelNotes/2.50.0.adoc

:100755 100755 1047b8d11d 208e91a17f M      GIT-VERSION-GEN

Git bezeichnet diese Ausgabe als „unbearbeitetes“ Format (in englischer Sprache verfügbar).

Kurz gesagt, listet jede Zeile der Ausgabe Dateipaare und die dazugehörigen Metadaten

darüber auf, was sich zwischen dem Anfangscode und der letzten Revision geändert hat. Im Vergleich zur

Erzeugung der „Patch“-Ausgabe für große Änderungen verläuft dieser Prozess relativ

schnell und liefert eine Zusammenfassung aller Änderungen. Dieser Befehl kann optional eine Umbenennungserkennung durchführen, indem das Flag -M angehängt wird. So kannst du überprüfen, ob identifizierte Änderungen auf eine Dateiumbenennung zurückzuführen sind.

Mit diesen Informationen könnten wir git-diff(1) verwenden, um jedes der

Dateipaar-Diffs einzeln zu erstellen. Zum Beispiel können wir die Blob-IDs

direkt angeben:


$ git diff 1047b8d11de767d290170979a9a20de1f5692e26 208e91a17f04558ca66bc19d73457ca64d5385f

Wir können diesen Vorgang für jedes der Dateipaare wiederholen, aber es ist nicht sehr effizient, für jede einzelne Datei einen

separaten Git-Prozess zu starten.

Außerdem verliert das Diff bei der Verwendung von Blob-IDs einige Kontextinformationen,

wie den Änderungsstatus und die Dateimodi, die im übergeordneten

Baumobjekt gespeichert sind. Was wir wirklich möchten, ist ein Mechanismus, um „unbearbeitete“ Dateipaarinformationen einzuspeisen und

die entsprechende Patch-Ausgabe zu generieren.

Mit der Version 2.50 bietet Git einen neuen integrierten Befehl mit der Bezeichnung

git-diff-pairs(1) (in englischer Sprache verfügbar. Dieser Befehl

akzeptiert „unbearbeitete“ formatierte Dateipaarinformationen als Eingabe auf stdin, um exakt zu bestimmen, welche Patches ausgegeben werden sollen. Das folgende Beispiel zeigt, wie dieser Befehl

verwendet werden kann:


$ git diff-tree -r -z -M HEAD~ HEAD | git diff-pairs -z

Bei dieser Nutzung ist die resultierende Ausgabe identisch mit der Verwendung von git-diff(1).

Durch einen separaten Befehl zur Generierung der Patch-Ausgabe kann die „unbearbeitete“ Ausgabe von

git-diff-tree(1) in kleinere Chargen von Dateipaaren aufgeteilt und separaten

git-diff-pairs(1)-Prozessen zugeführt werden. Dies löst das zuvor erwähnte

Skalierbarkeitsproblem, da die Diffs nicht länger alle auf einmal berechnet werden müssen. Zukünftige

GitLab-Versionen könnten auf diesem Mechanismus aufbauen, um die Leistung der

Diff-Generierung zu verbessern, insbesondere wenn es sich um große Änderungssätze

handelt. Weitere Informationen zu dieser Änderung findest du im entsprechenden

Mailinglisten-Thread.

Dieses Projekt wurde von Justin Tobler geleitet.

Gesammelte Referenz-Updates

Mit dem Git-Befehl git-update-ref(1) (in englischer Sprache verfügbar)

kannst du Referenzaktualisierungen durchführen. Bei Verwendung mit dem Flag --stdin können

mehrere Referenzaktualisierungen in einer einzigen Transaktion gebündelt werden, indem Anweisungen für jede Referenzaktualisierung

angegeben werden, die auf stdin durchgeführt werden soll.

Die Massenaktualisierung von Referenzen auf diese Weise zeigt auch ein atomares Verhalten, bei dem ein

einzelner Fehler bei der Referenzaktualisierung eine Transaktion abbricht und

Referenzen nicht aktualisiert werden. Hier ist ein Beispiel für dieses Verhalten:


# Erstelle ein Repository mit drei leeren Commits und einem Branch mit dem Namen „foo“

$ git init

$ git commit --allow-empty -m 1

$ git commit --allow-empty -m 2

$ git commit --allow-empty -m 3

$ git branch foo


# Gib die Commit-IDs aus

$ git rev-list HEAD

cf469bdf5436ea1ded57670b5f5a0797f72f1afc

5a74cd330f04b96ce0666af89682d4d7580c354c

5a6b339a8ebffde8c0590553045403dbda831518


# Versuche, eine neue Referenz zu erstellen und die vorhandene Referenz in der Transaktion zu aktualisieren.

# Es wird erwartet, dass die Aktualisierung fehlschlägt, da die angegebene alte Objekt-ID nicht richtig ist.

$ git update-ref --stdin <<EOF

> create refs/heads/bar cf469bdf5436ea1ded57670b5f5a0797f72f1afc

> update refs/heads/foo 5a6b339a8ebffde8c0590553045403dbda831518 5a74cd330f04b96ce0666af89682d4d7580c354c

> EOF

fatal: cannot lock ref 'refs/heads/foo': is at cf469bdf5436ea1ded57670b5f5a0797f72f1afc but expected 5a74cd330f04b96ce0666af89682d4d7580c354c


# Die Referenz „bar“ wurde nicht erstellt.

$ git switch bar

fatal: invalid reference: bar

Im Vergleich zur einzelnen Aktualisierung vieler Referenzen ist die Massenaktualisierung

auch viel effizienter. Das ist zwar grundsätzlich eine gute Lösung, aber es kann bestimmte

Umstände geben, unter denen es akzeptabel ist, wenn ein Teil der angeforderten Referenzaktualisierungen

fehlschlägt, wir aber dennoch die Effizienzvorteile von

Massenaktualisierungen nutzen möchten.

Ab dieser Version verfügt git-update-ref(1) über die neue Option --batch-updates, mit

der die Aktualisierungen auch dann fortgesetzt werden können, wenn eine oder mehrere Referenzaktualisierungen

fehlschlagen. In diesem Modus werden einzelne Fehler im folgenden Format gemeldet:


rejected SP (<old-oid> | <old-target>) SP (<new-oid> | <new-target>) SP <rejection-reason> LF

Dadurch können erfolgreiche Referenzaktualisierungen fortgesetzt werden, während gleichzeitig angegeben wird, unter welchen Umständen Aktualisierungen abgelehnt wurden und aus welchem Grund. Wir verwenden noch einmal das gleiche beispielhafte Repository wie im vorherigen Beispiel:


# Versuche, eine neue Referenz zu erstellen und die vorhandene Referenz in der Transaktion zu aktualisieren.

$ git update-ref --stdin --batch-updates <<EOF

> create refs/heads/bar cf469bdf5436ea1ded57670b5f5a0797f72f1afc

> update refs/heads/foo 5a6b339a8ebffde8c0590553045403dbda831518 5a74cd330f04b96ce0666af89682d4d7580c354c

> EOF

rejected refs/heads/foo 5a6b339a8ebffde8c0590553045403dbda831518 5a74cd330f04b96ce0666af89682d4d7580c354c incorrect old value provided


# Die Referenz „bar“ wurde erstellt, obwohl die Aktualisierung auf „foo“ abgelehnt wurde.

$ git switch bar

Switched to branch 'bar'

Mit der Option --batch-updates war die Referenzerstellung diesmal erfolgreich,

obwohl die Aktualisierung nicht funktioniert hat. Diese Patch-Serie legt den Grundstein für

zukünftige Leistungsverbesserungen in git-fetch(1) und git-receive-pack(1),

wenn Referenzen in großer Zahl aktualisiert werden. Weitere Informationen findest du im

Mailinglisten-Thread

Dieses Projekt wurde von Karthik Nayak geleitet.

Neue Filteroption für git-cat-file(1)

Mit git-cat-file(1) (in englischer Sprache verfügbar) ist es möglich,

Informationen für alle im Repository enthaltenen Objekte über die Option

--batch–all-objects auszugeben. Zum Beispiel:


# Richte ein einfaches Repository ein.

$ git init

$ echo foo >foo

$ git add foo

$ git commit -m init


# Erstelle ein nicht erreichbares Objekt.

$ git commit --amend --no-edit


# Verwende git-cat-file(1), um Informationen über alle Objekte einschließlich nicht erreichbarer Objekte auszugeben.

$ git cat-file --batch-all-objects --batch-check='%(objecttype) %(objectname)'

commit 0b07e71d14897f218f23d9a6e39605b466454ece

tree 205f6b799e7d5c2524468ca006a0131aa57ecce7

blob 257cc5642cb1a054f08cc83f2d943e56fd3ebe99

commit c999f781fd7214b3caab82f560ffd079ddad0115

In einigen Situationen möchte ein(e) Benutzer(in) möglicherweise alle Objekte im

Repository durchsuchen, aber nur eine Teilmenge basierend auf einem bestimmten Attribut ausgeben. Wenn

wir beispielsweise nur die Objekte anzeigen möchten, die Commits sind, könnten wir

grep(1) verwenden:


$ git cat-file --batch-all-objects --batch-check='%(objecttype) %(objectname)' | grep ^commit

commit 0b07e71d14897f218f23d9a6e39605b466454ece

commit c999f781fd7214b3caab82f560ffd079ddad0115

Das funktioniert zwar, aber ein Nachteil beim Filtern der Ausgabe ist, dass

git-cat-file(1) nach wie vor alle Objekte im Repository durchlaufen muss, auch

diejenigen, an denen wir nicht interessiert sind. Dies kann ziemlich ineffizient sein.

Mit dieser Version verfügt git-cat-file(1) jetzt über die Option --filter, die nur jene Objekte

anzeigt, die den angegebenen Kriterien entsprechen. Dies ähnelt der gleichnamigen Option

für git-rev-list(1), unterstützt jedoch nur eine Teilmenge der

Filter. Die folgenden Filter werden unterstützt: blob:none, blob:limit= und

object:type=. Ähnlich wie im vorherigen Beispiel können Objekte mit Git direkt nach

ihrem Typ gefiltert werden:


$ git cat-file --batch-all-objects --batch-check='%(objecttype) %(objectname)' --filter='object:type=commit'

commit 0b07e71d14897f218f23d9a6e39605b466454ece

commit c999f781fd7214b3caab82f560ffd079ddad0115

Es ist nicht nur praktisch, dass Git die Verarbeitung übernimmt, sondern bei großen

Repositories mit vielen Objekten ist dies möglicherweise auch effizienter. Wenn ein

Repository über Bitmap-Indizes verfügt, kann Git Objekte eines bestimmten Typs effizient

nachschlagen und so das Durchsuchen der

Paketierungsdatei vermeiden, wodurch die Geschwindigkeit deutlich erhöht wird. Benchmarks, die im

Chromium-Repository durchgeführt wurden, zeigen signifikante Verbesserungen:


Benchmark 1: git cat-file --batch-check --batch-all-objects --unordered --buffer --no-filter Time (mean ± σ):     82.806 s ±  6.363 s    [User: 30.956 s, System: 8.264 s] Range (min … max):   73.936 s … 89.690 s    10 runs

Benchmark 2: git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=object:type=tag Time (mean ± σ):      20.8 ms ±   1.3 ms    [User: 6.1 ms, System: 14.5 ms] Range (min … max):    18.2 ms …  23.6 ms    127 runs

Benchmark 3: git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=object:type=commit Time (mean ± σ):      1.551 s ±  0.008 s    [User: 1.401 s, System: 0.147 s] Range (min … max):    1.541 s …  1.566 s    10 runs

Benchmark 4: git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=object:type=tree Time (mean ± σ):     11.169 s ±  0.046 s    [User: 10.076 s, System: 1.063 s] Range (min … max):   11.114 s … 11.245 s    10 runs

Benchmark 5: git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=object:type=blob Time (mean ± σ):     67.342 s ±  3.368 s    [User: 20.318 s, System: 7.787 s] Range (min … max):   62.836 s … 73.618 s    10 runs

Benchmark 6: git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=blob:none Time (mean ± σ):     13.032 s ±  0.072 s    [User: 11.638 s, System: 1.368 s] Range (min … max):   12.960 s … 13.199 s    10 runs

Summary git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=object:type=tag 74.75 ± 4.61 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=object:type=commit 538.17 ± 33.17 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=object:type=tree 627.98 ± 38.77 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=blob:none 3244.93 ± 257.23 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --filter=object:type=blob 3990.07 ± 392.72 times faster than git cat-file --batch-check --batch-all-objects --unordered --buffer --no-filter

Interessanterweise zeigen diese Ergebnisse, dass die Berechnungszeit jetzt mit

der Anzahl der Objekte für einen bestimmten Typ skaliert, anstatt mit der Anzahl der gesamten Objekte

in der Paketierungsdatei. Den ursprünglichen (englischsprachigen) Mailinglisten-Thread findest du

hier.

Dieses Projekt wurde von Patrick Steinhardt geleitet.

Verbesserte Leistung beim Generieren von Bundles

Git bietet die Möglichkeit, über den Befehl

git-bundle(1) (in englischer Sprache verfügbar) ein Archiv eines Repositories zu generieren, das einen

bestimmten Satz von Referenzen und zugehörigen erreichbaren Objekten enthält. Dieser Vorgang

wird von GitLab verwendet, um Repository-Backups zu erstellen, und ist auch ein Teil des

Bundle-URI (in englischer Sprache verfügbar)-Mechanismus.

Bei großen Repositories mit Millionen von Referenzen kann dieser Vorgang Stunden oder sogar Tage

dauern. Zum Beispiel lagen die Backup-Zeiten für das Haupt-GitLab-Repository

(gitlab-org/gitlab), bei

etwa 48 Stunden. Die Untersuchung zeigte einen Leistungsengpass, der

auf die Art zurückzuführen war, wie Git eine Überprüfung durchführte, um zu vermeiden, dass doppelte Referenzen

in das Bundle aufgenommen wurden. Die Implementierung verwendete eine verschachtelte for-Schleife, um alle aufgelisteten Referenzen zu durchlaufen und zu

vergleichen, was zu einer Zeitkomplexität von O(N^2) führte. Die Skalierbarkeit

ist sehr schlecht, wenn die Anzahl der Referenzen in einem Repository zunimmt.

In dieser Version wurde dieses Problem behoben, indem die verschachtelten Schleifen durch eine

Datenzuordnungsstruktur ersetzt wurden, was die Geschwindigkeit erheblich erhöht. Der folgende Benchmark zeigt

die Leistungssteigerung beim Erstellen eines Bundles mit einem Repository, das

100 000 Referenzen enthält:


Benchmark 1: bundle (refcount = 100000, revision = master) Time (mean ± σ):     14.653 s ±  0.203 s    [User: 13.940 s, System: 0.762 s] Range (min … max):   14.237 s … 14.920 s    10 runs

Benchmark 2: bundle (refcount = 100000, revision = HEAD) Time (mean ± σ):      2.394 s ±  0.023 s    [User: 1.684 s, System: 0.798 s] Range (min … max):    2.364 s …  2.425 s    10 runs

Summary bundle (refcount = 100000, revision = HEAD) ran 6.12 ± 0.10 times faster than bundle (refcount = 100000, revision = master)

Weitere Informationen findest du in unserem Blogbeitrag

Wie wir die Backup-Zeiten für GitLab-Repos von 48 Stunden auf 41 Minuten verringerten (in englischer Sprache verfügbar).

Den ursprünglichen englischsprachigen Mailinglisten-Thread findest du

hier.

Dieses Projekt wurde von Karthik Nayak geleitet.

Bessere Auflösung von URI-Bundles

Durch den Bundle-URI (in englischer Sprache verfügbar)-Mechanismus in Git können den Clients

Orte zum Abrufen von Bundles zur Verfügung gestellt werden, um

Klone und Abrufe zu beschleunigen. Wenn ein Client ein Bundle herunterlädt, werden Referenzen

unter refs/heads/* zusammen mit

den zugehörigen Objekten aus dem Bundle in das Repository kopiert. Ein Bundle kann zusätzliche Referenzen

außerhalb von refs/heads/* enthalten, wie z. B. refs/tags/*, die einfach ignoriert werden, wenn

die Bundle-URI beim Klonen verwendet wird.

In Git 2.50 wird diese Einschränkung aufgehoben und alle Referenzen, die mit

refs/* übereinstimmen und im heruntergeladenen Bundle enthalten sind, werden kopiert.

Scott Chacon, der diese Funktionalität beigesteuert hat,

demonstriert den Unterschied beim Klonen von

gitlab-org/gitlab-foss:


$ git-v2.49 clone --bundle-uri=gitlab-base.bundle https://gitlab.com/gitlab-org/gitlab-foss.git gl-2.49

Cloning into 'gl2.49'...

remote: Enumerating objects: 1092703, done.

remote: Counting objects: 100% (973405/973405), done.

remote: Compressing objects: 100% (385827/385827), done.

remote: Total 959773 (delta 710976), reused 766809 (delta 554276), pack-reused 0 (from 0)

Receiving objects: 100% (959773/959773), 366.94 MiB | 20.87 MiB/s, done.

Resolving deltas: 100% (710976/710976), completed with 9081 local objects.

Checking objects: 100% (4194304/4194304), done.

Checking connectivity: 959668, done.

Updating files: 100% (59972/59972), done.


$ git-v2.50 clone --bundle-uri=gitlab-base.bundle https://gitlab.com/gitlab-org/gitlab-foss.git gl-2.50

Cloning into 'gl-2.50'...

remote: Enumerating objects: 65538, done.

remote: Counting objects: 100% (56054/56054), done.

remote: Compressing objects: 100% (28950/28950), done.

remote: Total 43877 (delta 27401), reused 25170 (delta 13546), pack-reused 0 (from 0)

Receiving objects: 100% (43877/43877), 40.42 MiB | 22.27 MiB/s, done.

Resolving deltas: 100% (27401/27401), completed with 8564 local objects.

Updating files: 100% (59972/59972), done.

Wenn wir diese Ergebnisse vergleichen, sehen wir, dass Git 2.50 43 887 Objekte

(40,42 MiB) abruft, nachdem das Bundle extrahiert wurde, während Git 2.49

insgesamt 959 773 Objekte (366,94 MiB) abruft. Git 2.50 ruft etwa 95 % weniger

Objekte und 90 % weniger Daten ab, was vorteilhaft sowohl für den Client als auch für den Server ist. Der

Server muss viel weniger Daten für den Client verarbeiten und der Client muss weniger Daten

herunterladen und extrahieren. In dem von Scott angegebenen Beispiel führte dies zu einer

Beschleunigung um 25 %.

Weitere Informationen findest du im entsprechenden englischsprachigen

Mailinglisten-Thread.

TDiese Patch-Serie wurde von Scott Chacon beigesteuert.

Weiterlesen

In diesem Artikel werden nur einige der Beiträge von GitLab und

der größeren Git-Community für diese neueste Veröffentlichung vorgestellt. Mehr darüber erfährst du in

der offiziellen Veröffentlichungsankündigung des Git-Projekts. Sieh dir auch

unsere letzten Blogbeiträge zu Git-Releases (in englischer Sprache verfügbar)

an, um weitere wichtige Beiträge von GitLab-Teammitgliedern zu entdecken.

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.