Hier erfährst du, wie Shallow Fetch, Blob-Filter und Sparse Checkout CI-Builds deutlich beschleunigen und wann jede Option die richtige Wahl ist.
Ein Repository wächst mit jedem Commit. Irgendwann landen Binaries in der History, Libraries werden direkt commitet, oder das Projekt entwickelt sich zu einem Monorepo mit Dutzenden von Anwendungen. Ab diesem Punkt werden CI-Pipelines (Continuous Integration) spürbar langsamer, nicht wegen des eigentlichen Builds, sondern wegen des Git-Checkouts am Anfang. Was wie ein technisches Detail klingt, summiert sich bei mehreren Pipelines und Stages schnell auf viele Minuten verschwendeter CI-Zeit pro Commit.
Standardmäßig klont Git beim CI-Checkout die vollständige History, alle Dateiinhalte aller Versionen und den gesamten Working Tree. Mit drei gezielten Optimierungen lässt sich das deutlich reduzieren: Shallow Fetch für die History, Fetch-Filters für die Dateiinhalte und Sparse Checkout für den Working Tree.
Im besten Fall verwendest du auch gleich Git LFS (Large File Storage) für große Dateien, damit die Blobs gar nicht erst im Repository landen. Mehr dazu in unserem Artikel: Git: Attributes, Ignore und LFS.
Drei Faktoren, die den Checkout bremsen
Jeder Git-Clone überträgt im Standard drei Dinge: die History (alle Commits und Trees aller Branches), die Blobs (Dateiinhalte aller Versionen) und den Working Tree (die auf Disk geschriebenen Dateien). Jeder dieser Faktoren lässt sich unabhängig optimieren.
Faktor
Was darin steckt
Lösung
History
Alle Commits und Trees aller Branches
Shallow Fetch (fetchDepth: 1)
Blobs
Dateiinhalte aller Versionen
Fetch Filter (blob:none)
Working Tree
Alle ausgecheckten Dateien inkl. irrelevanter Pfade
Sparse Checkout
Die drei Optionen lassen sich auch kombinieren. Ein Build-Job, der nur kompiliert und Tests ausführt, braucht weder vollständige History noch Dateien aus anderen Projekten des Monorepos. Welche Kombination sinnvoll ist, hängt davon ab, was der jeweilige Job tatsächlich liest.
Shallow Fetch
Mit --depth N holt Git beim Clone nur die letzten N Commits. In GitHub Actions ist fetch-depth: 1 bereits der Standard in actions/checkout@v4. In Azure Pipelines ist der Default abhängig wann die Pipeline angelegt wurde. Der Standard für YAML-Pipelines ist hier auch 1. Die offizielle Parameterliste von actions/checkout und die Checkout-Task-Dokumentation für Azure Pipelines beschreiben alle verfügbaren Optionen.
# GitHub Actions – nur letzter Commit (ist in actions/checkout@v4 bereits der Default)- uses: actions/checkout@v4 with: fetch-depth: 1# GitHub Actions – vollständige History- uses: actions/checkout@v4 with: fetch-depth: 0
fetch-depth: 0 bzw. fetchDepth: 0 ist nötig für Jobs, die git log, Changelog-Tools oder Merge-Base-Berechnungen verwenden.
Wenn Diffs fehlschlagen
Ein häufiges Problem taucht bei Pull-Request-Validierungen (PR) auf: git diff origin/main..HEAD liefert ein leeres oder falsches Ergebnis, weil ein flacher Clone keinen gemeinsamen Vorfahren kennt. Auch git merge-base origin/main HEAD schlägt dann mit no merge base found fehl. Der GitHub Blog-Artikel zu Partial Clone und Shallow Clone erklärt, warum das so ist.
# Reproduktion des Problems: leeres oder falsches Ergebnis nach flachem Clonegit clone --depth 1 https://github.com/org/repo.gitcd repogit fetch origin main --depth 1git diff origin/main..HEAD # schlägt fehl oder liefert falschen Outputgit merge-base origin/main HEAD # Error: "no merge base found"
Ein gezieltes git fetch --unshallow vor dem Diff-Schritt löst das, ohne die gesamte Pipeline auf fetchDepth: 0 umstellen zu müssen. Die offizielle git-fetch-Dokumentation beschreibt --unshallow ausführlich.
# Azure Pipelines – Unshallow nur bei Pull Request Buildssteps: - checkout: self fetchDepth: 1 - script: git fetch --unshallow displayName: 'Unshallow for diff detection' condition: eq(variables['Build.Reason'], 'PullRequest')
Beim blobless Clone mit --filter=blob:none überträgt Git sofort alle Commits und Trees, lädt Blob-Inhalte aber erst beim tatsächlichen Lesen nach. Für Jobs, die primär mit dem Git-Graphen arbeiten, also z.B. Versionsnummern aus Tags lesen oder Changelogs generieren, spart das erheblich Netzwerkzeit. Die offizielle Dokumentation zu Partial Clone beschreibt den Filtermechanismus im Detail.
# Azure Pipelines – blobless Clone (erfordert Git ≥ 2.27 auf dem Agent)steps: - checkout: self fetchDepth: 0 fetchFilter: blob:none
blob:none ist kein Ersatz für Sparse Checkout. Wenn der Build viele Dateien liest, lädt Git die Blobs on demand nach, was bei großen Projekten viele einzelne Requests erzeugt. Für Self-Hosted Agents empfiehlt sich außerdem protocol.version=2 (Git Wire Protocol v2, das Partial-Clone-Filter effizienter überträgt), damit das Filter-Protokoll effizient funktioniert.
Sparse Checkout: Cone und Non-Cone
Sparse Checkout begrenzt den Working Tree auf ausgewählte Pfade. Das reduziert Disk I/O erheblich, besonders in Monorepos, in denen ein einzelner Build-Job nur einen Teilbaum des Repositories benötigt. Eine ausführliche Einführung mit Vergleich beider Modi bietet der GitHub Blog-Artikel zu Sparse Checkout in Monorepos.
Cone Mode (Standard seit Git 2.26): Pattern entsprechen Verzeichnissen und werden per Prefix-Matching auf Tree-Ebene ausgewertet. Schnell und für die meisten CI-Jobs ausreichend.
Non-Cone Mode: Glob-Patterns ähnlich wie in .gitignore. Flexibler, aber langsamer bei großen Trees.
Cone Mode
Non-Cone Mode
Pattern-Typ
Verzeichnispfade
Glob-Patterns
Geschwindigkeit
Schnell (Prefix-Match auf Tree-Ebene)
Langsamer (Pattern-Match je Datei)
Granularität
Ganze Verzeichnisse
Beliebige Pfade und Dateien
Empfehlung
Für die meisten CI-Jobs
Wenn Feingranularität nötig
# Cone Mode – nur src/ und docs/ auscheckengit clone --no-checkout https://github.com/org/repo.gitcd repogit sparse-checkout init --conegit sparse-checkout set src/app docsgit checkout main
Cone Mode – Was wird ausgecheckt:
Alle Dateien und Verzeichnisse unter src/app und docs/, plus Root-Dateien:
Sowohl GitHub Actions als auch Azure DevOps unterstützen Sparse Checkout sowohl im Cone- als auch im Non-Cone Mode direkt in ihren Checkout-Tasks.
Beide können in Verbindung mit Fetch Depth und auch Fetch Filter verwendet werden, um die Checkout-Zeit weiter zu reduzieren.
# Azure Pipelinessteps: - checkout: self fetchDepth: 1 fetchFilter: blob:none # Cone-mode, prio over sparseCheckoutPatterns sparseCheckoutDirectories: | src docs # None-cone mode, ignored if sparseCheckoutDirectories is set sparseCheckoutPatterns: | src/app/** docs/*
Fallstricke kennen
Bevor du alles auf fetchDepth: 1 umstellst, gibt es einige Szenarien zu beachten.
Tools mit History-Bedarf: Semantic Release, Conventional Changelog und ähnliche Tools benötigen die vollständige History ab dem letzten Git-Tag. Für solche Jobs ist fetch-depth: 0 oder git fetch --shallow-since=<last-tag-date> nötig. Es empfiehlt sich, dafür einen dedizierten Release-Job zu definieren, der explizit die vollständige History holt, anstatt alle Build-Jobs darauf zu verpflichten.
Git-Versionen auf Self-Hosted Agents:blob:none erfordert Git ≥ 2.27, der Cone Mode ≥ 2.26. Auf älteren Agents lässt sich die Version mit git --version prüfen. Als Fallback für ältere Versionen ist eine manuelle Konfiguration über .git/info/sparse-checkout und core.sparseCheckout=true möglich.
Sparse Checkout ist kein Sicherheitsmerkmal: Ausgesparte Pfade fehlen im Working Tree, sind aber weiterhin in der Git-Datenbank vorhanden. Sparse Checkout reduziert, was auf Disk landet, verhindert aber keinen Zugriff auf die Git-Objekte selbst.
CI-Caches mit Git-State: Wenn .git per actions/cache zwischengespeichert wird, kann ein nachfolgendes git fetch --unshallow den Cache-State veralten lassen. Den Cache-Key so wählen, dass er den aktuellen Checkout-Zustand abbildet, oder .git vom Caching ausschließen.
Zur schnellen Diagnose nach dem Checkout helfen diese Kommandos:
# Wie viele Commits wurden geladen?git rev-list --count HEAD# Welche Pfade sind aktiv ausgecheckt?git sparse-checkout list# Wurde die Merge-Base korrekt gefunden?git merge-base origin/main HEAD
Empfehlungen auf einen Blick
Die drei Optimierungen greifen an unterschiedlichen Stellen und ergänzen sich gut. Als Orientierung:
fetchDepth: 1 als Standard für alle einfachen Build- und Test-Jobs
fetch-depth: 0 / fetchDepth: 0 nur für Jobs mit History-Bedarf (Release, Changelog, Diff-basierte Analysen)
blob:none ergänzen, wenn Commits und Trees benötigt werden, aber nicht alle Dateiinhalte sofort gelesen werden
Sparse Checkout im Cone Mode für Monorepos aktivieren, in denen der Job nur einen Teilbaum benötigt
git fetch --unshallow vor Diff-Schritten einplanen, wenn fetchDepth: 1 gesetzt ist
Eine nützliche Ergänzung ist ein Logging-Schritt am Anfang der Pipeline, der die aktuelle Checkout-Konfiguration dokumentiert und Veränderungen über Zeit sichtbar macht:
Florian ist Solution Architect und Microsoft Most Valuable Professional (MVP) für Azure & Azure IoT mit langjähriger Erfahrung im Bereich DevOps, Cloud und Digitalisierung. Er unterstützt Unternehmen dabei, effiziente und effektive Lösungen zu entwickeln, die ihre digitalen Projekte nachhaltig zum Erfolg führen.
Durch gezieltes Überspringen von Pipeline-Stages bei unverändertem Code sparst du Zeit, reduzierst Nebenwirkungen und schonst Agent-Ressourcen – ideal für Monorepo-Pipelines mit Infrastructure, Database und App-Deployments.
Die strukturierte Ablage von Pipelines in Azure DevOps und GitHub ermöglicht eine einfache Versionierung und verbessert die Handhabung von Änderungen. Durch den Einsatz von Templates wird die Wiederverwendbarkeit gefördert und Prozesse wie Lizenz- und Sicherheitschecks integrierbar.
Azure Policy scheitert selten an der Definition selbst, sondern an zu großen Scopes, unklaren Ausnahmen und unkontrollierten Rollouts. Dieser Artikel zeigt, wie du Regeln sicher aktivierst und typische Betriebsfehler vermeidest.
Wer Azure Policy im größeren Maßstab betreibt, kommt um Policy as Code kaum herum. Definitionen, Initiatives, Assignments und Ausnahmen sollten gemeinsam versioniert und als konsistentes Regelwerk verwaltet werden.
Vom klassischen Windows-Installer über OTA-Update-Frameworks bis zum Firmware-Flasher: ein Überblick aller relevanten Update-Mechanismen und wie du sie CRA-konform umsetzt.
Ihr nutzt immer noch Passwörter und Access Keys in euren Azure Apps? Hier zeigen wir euch, wie ihr mit Azure Managed Identity endlich damit Schluss machen könnt.