.. SPDX-FileCopyrightText: 2020 cusy GmbH .. .. SPDX-License-Identifier: BSD-3-Clause 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, :abbr:`z.B. (zum Beispiel)` 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 (:abbr:`z.B. (zum Beispiel)` 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, :abbr:`z.B. (zum Beispiel)` ü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 :abbr:`z.B. (zum Beispiel)` 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 :abbr:`z.B. (zum Beispiel)` ``git log`` oder ``git 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 mit ``git 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 :abbr:`z.B. (zum Beispiel)` bei ``git branch --contains ``, 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 :abbr:`z.B. (zum Beispiel)` ``git status`` und ``git 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: ``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 :samp:`{N}` Commits der Historie abrufen, :abbr:`z.B. (zum Beispiel)` :samp:`git clone --depth {N} {REMOTE-URL}`. .. tip:: 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: ``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: :samp:`git filter-branch --tree-filter 'rm -rf {/PATH/TO/BIG/ASSETS}'`. .. warning:: ``git filter-branch`` schreibt allerdings die gesamte Historie eures Projekts um, :abbr:`d.h. (das heißt)`, dass sich einerseits alle Commit-Hashes ändern und andererseits, dass jedes Teammitglied das aktualisierte Repository neu klonen muss. .. seealso:: * `How to tear apart a repository: the Git way `_ .. _git-clone-branch: ``git clone --branch`` ~~~~~~~~~~~~~~~~~~~~~~ Ihr könnt den Umfang der geklonten Historie auch begrenzen, indem ihr einen einzelnen Zweig klont, etwa mit :samp:`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, :abbr:`d.h. (das heißt)`, dass ihr mit großen Dateien in eurem Repository wie gewohnt arbeiten könnt. Ihr könnt Git LFS installieren mit .. tab:: Debian/Ubuntu .. code-block:: console $ sudo apt install git-lfs .. tab:: macOS .. code-block:: console $ brew install git-lfs .. tab:: Windows Git LFS lässt sich mit `git for windows `_ mitinstallieren. Anschließend könnt ihr Git LFS in eurem Repository installieren mit .. code-block:: console $ git lfs install Updated Git hooks. Git LFS initialized. Um nun Git LFS auf bestimmte Dateitypen anzuwenden, könnt ihr :abbr:`z.B. (zum Beispiel)` folgendes angeben: .. code-block:: console $ git lfs track "*.pdf" Tracking "*.pdf" Dies erstellt in eurer :file:`.gitattributes`-Datei folgende Zeile: .. code-block:: *.pdf filter=lfs diff=lfs merge=lfs -text Schließlich sollter ihr die :file:`.gitattributes`-Datei mit Git verwalten: .. code-block:: console $ 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, :abbr:`z.B. (zum Beispiel)`: .. code-block:: console $ 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 ~~~~~~~~~~~~ .. tab:: Windows, Linux #. Ruft die `Releases `_-Seite auf und ladet die ZIP-Datei herunter, die eurer Plattform entspricht. #. Entpackt die Datei. #. Verschiebt die ausführbare Datei (:file:`git-sizer` oder :file:`git-sizer.exe`) in euren ``PATH``. .. tab:: macOS $ brew install git-sizer .. _fsmonitor: 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: .. code-block:: $ 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' .. seealso:: * `Improve Git monorepo performance with a file system monitor `_ * `Scaling monorepo maintenance `_ * `fsmonitor-watchman `_ 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 :samp:`scalar clone {/path/to/repo}` klonen oder ``scalar`` auf einen bestehenden Klon mit :samp:`scalar register {/path/to/repo}` anwenden. Weitere Optionen von ``scalar clone`` sind: ``-b``, :samp:`--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 :samp:`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 :samp:`git ls-tree -d --name-only HEAD {/path/to/repo}`, um die Verzeichnisse in :samp:`{/path/to/repo}` zu ermitteln. .. seealso:: `git ls-tree `_ 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: * :ref:`FSMonitor ` * `multi-pack-index (MIDX) `_ * `commit-graph `_ * `Git maintenance `_ * Partielles Klonen mit :ref:`git-clone-depth` und :ref:`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 :samp:`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. .. seealso:: * `Git - scalar Documentation `_