Monorepos und große Repositories¶
In einem großen Projekt kann es sinnvoll sein, einzelne Komponenten in separaten Repositories zu pflegen. Manchmal schafft dies jedoch unnötige Komplexität, z.B. welche Versionen der Repositories miteinander kompatibel sind. In diesen Fällen kann es sinnvoll sein, alle Teile eines Projekts in einem monolithischen Repository oder Monorepo zu halten.
Definition¶
In einem Monorepo enthält das Repository mehr als ein logisches Projekt (z.B. einen iOS-Client und eine Webanwendung).
Diese Projekte können unabhängig voneinander gebaut, getestet oder deployt werden.
Diese Projekte sind meist nur lose miteinander verbunden oder können auf andere Weise miteinander verbunden werden, z.B. über Tools zur Verwaltung von Abhängigkeiten.
Das Repository enthält viele Commits, Zweige und/oder Tags. Oder es enthält viele und/oder große Dateien.
Mit Tausenden von Commits von hunderten Autoren in tausenden von Dateien pro Monat ist das Linux-Kernel-Repository riesig.
Vor- und Nachteile¶
Ein Vorteil von Monorepos kann sein, dass die Aufwände um zu bestimmen, welche Versionen des einen Projekts mit welchen Versionen des anderen Projekts kompatibel sind, deutlich verringert sein könnten. Dies ist zumindest immer dann der Fall, wenn alle Projekte eines Repository von nur einem Entwicklerteam bearbeitet werden. Dann empfiehlt sich, mit jedem Merge wieder eine lauffähige Version zu erhalten auch wenn die API zwischen den beiden Projekten geändert wurde.
Als Nachteil können sich jedoch Performance-Einbußen erweisen. Diese können z.B. entstehen durch:
- eine große Anzahl an Commits
Da Git DAGs (directed acyclic graphs) verwendet, um die Historie eines Projekts darzustellen, werden alle Operationen, die diesen Graphen durchlaufen, also z.B.
git log
odergit blame
, langsam werden.- eine große Anzahl von Git-Referenzen
Eine große Anzahl von Branches und Tags verlangsamen Git ebenfalls. Mit
git ls-remote
könnt ihr euch die Referenzen eines Repository anzeigen lassen und mitgit gc
werden lose Referenzen in einer einzigen Datei zusammengefasst.Jede Operation, die den Commit-Verlauf eines Repositories durchlaufen und die einzelnen Referenzen berücksichtigen muss, wie z.B. bei
git branch --contains <commit>
, werden bei einem Repo mit vielen Referenzen langsam.- eine große Anzahl an versionierten Dateien
Der Index des Directory Cache (
.git/index
) wird von Git verwendet um zu ermitteln, ob die Datei verändert wurde. Dabei verlangsamen sich mit zunehmender Anzahl an Dateien viele Vorgänge, wie z.B.git status
undgit commit
.- große Dateien
Große Dateien in einem Teilbaum oder einem Projekt verringern die Leistung des gesamten Repository.
Strategien für große Repositories¶
Die Designziele von Git, die es so erfolgreich und beliebt gemacht haben, stehen manchmal im Widerspruch zu dem Wunsch, es auf eine Weise zu verwenden, für die es nicht konzipiert wurde. Dennoch gibt es eine Reihe von Strategien, die bei der Arbeit mit großen Repositories hilfreich sein können:
git clone --depth
¶
Auch wenn die Schwelle, ab der eine Historie als riesig eingestuft wird, ziemlich hoch ist, kann es immer noch mühsam sein, sie zu klonen. Dennoch können wir lange Historien nicht immer vermeiden, wenn sie aus rechtlichen oder regulatorischen Gründen beibehalten werden müssen.
Die Lösung für einen schnellen Clone eines solchen Repositories besteht darin,
nur die jüngsten Revisionen zu kopieren. Mit der Shallow-Option von git
clone
könnt ihr nur die letzten N
Commits der Historie abrufen,
z.B. git clone --depth N REMOTE-URL
.
Tipp
Auch Build-Systeme, die mit eurem Git-Repository verbunden sind, profitieren von solchen Shallow Clones!
Shallow Clones waren in Git bisher eher selten, da einige Operationen Anfangs kaum unterstützt wurden. Seit einiger Zeit (in den Versionen 1.9 und höher) könnt ihr jetzt sogar von einem Shallow Clone aus Pull- und Push-Vorgänge in Repositories durchführen.
git filter-branch
¶
Für große Repositories, in denen viele Binärdateien versehentlich übertragen
wurden, oder alte Assets, die nicht mehr benötigt werden, ist git
filter-branch
eine gute Lösung um die gesamte Historie durchzugehen und
Dateien nach vordefinierten Mustern herauszufiltern, zu ändern oder zu
überspringen.
Es ist ein sehr leistungsfähiges Werkzeug, sobald ihr herausgefunden habt, wo
euer Projektarchiv schwer ist. Es gibt auch Hilfsskripte, um große Objekte zu
identifizieren: git filter-branch --tree-filter 'rm -rf
/PATH/TO/BIG/ASSETS'
.
Warnung
git filter-branch
schreibt allerdings die gesamte Historie eures Projekts
um, d.h., dass sich einerseits alle Commit-Hashes ändern
und andererseits, dass jedes Teammitglied das aktualisierte Repository neu
klonen muss.
Siehe auch
git clone --branch
¶
Ihr könnt den Umfang der geklonten Historie auch begrenzen, indem ihr einen
einzelnen Zweig klont, etwa mit git clone REMOTE-URL --branch
BRANCH-NAME --single-branch FOLDER
.
Dies kann nützlich sein, wenn ihr mit langlaufenden und abweichenden Zweigen arbeitet, oder wenn ihr viele Zweige habt und nur mit einigen davon arbeiten müsst. Wenn ihr jedoch nur eine wenige Zweige mit wenigen Unterschieden habt, werdet ihr damit jedoch wahrscheinlich keinen großen Unterschied feststellen.
Git LFS¶
Git LFS ist eine Erweiterung, die Pointer auf große Dateien in eurem Repository speichert, anstatt die Dateien selbst; diese werden auf einem entfernten Server gespeichert, wodurch die Zeit für das Klonen eures Projektarchivs drastisch verkürzt wird. Git LFS greift dabei auf die nativen Push-, Pull-, Checkout- und Fetch-Operationen von Git zu, um die Objekte zu übertragen und zu ersetzen, d.h., dass ihr mit großen Dateien in eurem Repository wie gewohnt arbeiten könnt.
Ihr könnt Git LFS installieren mit
$ sudo apt install git-lfs
$ brew install git-lfs
Git LFS lässt sich mit git for windows mitinstallieren.
Anschließend könnt ihr Git LFS in eurem Repository installieren mit
$ git lfs install
Updated Git hooks.
Git LFS initialized.
Um nun Git LFS auf bestimmte Dateitypen anzuwenden, könnt ihr z.B. folgendes angeben:
$ git lfs track "*.pdf"
Tracking "*.pdf"
Dies erstellt in eurer .gitattributes
-Datei folgende Zeile:
*.pdf filter=lfs diff=lfs merge=lfs -text
Schließlich sollter ihr die .gitattributes
-Datei mit Git verwalten:
$ git add .gitattributes
git-sizer¶
git-sizer berechnet verschiedene Metriken für ein lokales Git-Repository und kennzeichnet diejenigen, die euch Probleme oder Unannehmlichkeiten bereiten könnten, z.B.:
$ git-sizer
Processing blobs: 1903
Processing trees: 4126
Processing commits: 1055
Matching commits to trees: 1055
Processing annotated tags: 2
Processing references: 5
| Name | Value | Level of concern |
| ---------------------------- | --------- | ------------------------------ |
| Biggest objects | | |
| * Blobs | | |
| * Maximum size [1] | 35.8 MiB | *** |
[1] 9fe7b8048891965e476aac0410e08e050fd21354 (refs/heads/main:docs/workspace/pandas/descriptive-statistics.ipynb)
Installation¶
Ruft die Releases-Seite auf und ladet die ZIP-Datei herunter, die eurer Plattform entspricht.
Entpackt die Datei.
Verschiebt die ausführbare Datei (
git-sizer
odergit-sizer.exe
) in eurenPATH
.
$ brew install git-sizer
Git file system monitor (FSMonitor)¶
git status
und git add
sind langsam, weil sie den gesamten Arbeitsbaum
nach Änderungen durchsuchen müssen. Mit der Funktion git fsmonitor--daemon
,
die ab Git-Version 2.36 zur Verfügung steht, werden diese Befehle beschleunigt,
indem der Umfang der Suche reduziert wird:
$ time git status
Auf Branch master
Ihr Branch ist auf demselben Stand wie 'origin/master'.
real 0m1,969s
user 0m0,237s
sys 0m1,257s
$ git config core.fsmonitor true
$ git config core.untrackedcache true
$ time git status
Auf Branch master
Ihr Branch ist auf demselben Stand wie 'origin/master'.
real 0m0,415s
user 0m0,171s
sys 0m0,675s
$ git fsmonitor--daemon status
fsmonitor-daemon beobachtet '/srv/jupyter/linux'
Scalar¶
scalar
, ein Repository-Management-Tool für große Repositories von Microsoft, ist seit Version
2.38 Teil der Git-Kerninstallation. Um es zu verwenden, könnt ihr entweder ein
neues Repository mit scalar clone /path/to/repo
klonen oder scalar
auf einen bestehenden Klon mit scalar register /path/to/repo
anwenden.
Weitere Optionen von scalar clone
sind:
-b
,--branch BRANCH
Branch, der nach dem Klonen ausgecheckt werden soll.
--full-clone
Vollständiges Arbeitsverzeichnis beim Klonen erstellen.
--single-branch
Lade nur Metadaten des Branches herunter, der ausgecheckt wird.
Mit scalar list
könnt ihr sehen, welche Repositories derzeit von Scalar
verfolgt werden und mit scalar unregister /path/to/repo
wird das
Repository aus dieser Liste entfernt.
Standardmäßig ist die Sparse-Checkout-Funktion aktiviert und es
werden nur die Dateien im Stammverzeichnis des Git-Repositorys angezeigt.
Verwendet git sparse-checkout set
, um die Menge der Verzeichnisse zu
erweitern, die ihr sehen möchtet, oder git sparse-checkout disable
, um alle
Dateien anzuzeigen. Wenn ihr nicht wisst, welche Verzeichnisse im Repository
verfügbar sind, könnt ihr git ls-tree -d --name-only HEAD
ausführen, um die
Verzeichnisse im Stammverzeichnis zu ermitteln, oder git ls-tree -d
--name-only HEAD /path/to/repo
, um die Verzeichnisse in
/path/to/repo
zu ermitteln.
Siehe auch
Um Sparse-Checkout nachträglich zu aktivieren, führt git sparse-checkout init
--cone
aus. Dadurch werden eure Sparse-Checkout-Patterns so initialisiert,
dass sie nur mit den Dateien im Stammverzeichnis übereinstimmen.
Aktuell sind neben sparse-checkout
noch die folgende Funktionen für
scalar
verfügbar:
Partielles Klonen mit git clone --depth und git filter-branch
Die Konfiguration von scalar
wird aktualisiert, wenn neue Funktionen in Git
eingeführt werden. Um sicherzustellen, dass ihr immer die neueste Konfiguration
verwendet, solltet ihr scalar reconfigure /PATH/TO/REPO
nach einer
neuen Git-Version ausführen, um die Konfiguration eures Repositorys zu
aktualisieren oder scalar reconfigure -a
, um alle eure mit Scalar
registrierten Repositories auf einmal zu aktualisieren.
Siehe auch