Git Subtree vs. Submodule: welche Strategie für welchen Anwendungsfall
AI generated
git
HEAD
Git · Repository-Struktur
Git Subtree vs. Submodule
welche Strategie für welchen Anwendungsfall wirklich passt

Sobald ein Projekt Code aus einem anderen Repository einbinden muss, stehen zwei eingebaute Git-Mechanismen zur Auswahl: Submodule und Subtree. Beide lösen dasselbe Grundproblem, externe Historie im eigenen Repository referenzierbar zu machen, aber mit vollkommen unterschiedlichen Konsequenzen für Checkout, CI-Pipelines und die Frage, wer welchen Code wo pflegt. Dieser Artikel vergleicht beide Ansätze anhand konkreter Workflows und ordnet ein, wann welche Variante in einem Magento-Kontext sinnvoll ist.

10 Min. Lesezeit Git Repository-Struktur

1. Das Grundproblem: externer Code im eigenen Repository

Fast jedes größere Projekt erreicht irgendwann den Punkt, an dem Code aus einem anderen Repository eingebunden werden muss, sei es eine gemeinsam genutzte Bibliothek, ein Theme oder ein internes Toolset. Die naive Lösung, den fremden Code einfach zu kopieren und ins eigene Repository einzuchecken, funktioniert kurzfristig, verliert aber sofort den Bezug zur Originalhistorie und macht spätere Updates zur manuellen Fleissarbeit.

Git bringt für genau dieses Problem zwei native Mechanismen mit: Submodule verwaltet einen Verweis auf einen exakten Commit eines fremden Repositories, Subtree kopiert den fremden Code inklusive Historie direkt in einen Unterordner des eigenen Repositories. Beide Ansätze sind seit Jahren stabiler Bestandteil von Git, unterscheiden sich aber so grundlegend in ihrer Arbeitsweise, dass die Wahl selten beliebig ist.

2. Wie Git Submodule intern funktioniert

Ein Submodule ist technisch gesehen kein Ordner voller Dateien im üblichen Sinn, sondern ein spezieller Verweis-Eintrag, der auf einen konkreten Commit-Hash eines fremden Repositories zeigt. Die Datei .gitmodules im Wurzelverzeichnis speichert dabei den Pfad und die Remote-URL jedes eingebundenen Submodules, während das Hauptrepository selbst nur den referenzierten Commit-Hash mitführt, nicht den Inhalt.

Das hat einen entscheidenden Vorteil: Das Hauptrepository bleibt klein, weil die eigentliche Historie des fremden Codes getrennt bleibt. Der Nachteil zeigt sich beim Checkout, denn ein normales git clone lädt Submodule standardmäßig nicht mit, sondern legt nur einen leeren Ordner an. Wer den vollständigen Stand braucht, muss explizit git submodule update --init --recursive ausführen, ein Schritt, der in CI-Pipelines gerne vergessen wird und dann zu rätselhaften fehlenden Dateien führt.


# Submodule einbinden
git submodule add https://example.com/shared-library.git vendor/shared-library

# Nach einem frischen Clone: Submodule-Inhalte nachladen
git clone https://example.com/hauptprojekt.git
cd hauptprojekt
git submodule update --init --recursive

# Submodule auf den neuesten Commit im referenzierten Branch bringen
git submodule update --remote vendor/shared-library

3. Wie Git Subtree intern funktioniert

Subtree geht den entgegengesetzten Weg: Statt eines Verweises wird der komplette Inhalt des fremden Repositories per Merge in einen Unterordner des Hauptrepositories geschrieben. Nach dem Einbinden liegen alle Dateien direkt im Arbeitsverzeichnis, so als wären sie von Anfang an Teil des Projekts gewesen. Es gibt keine separate .gitmodules-Datei und keinen zweiten Checkout-Schritt.

Der Preis dafür ist eine größere Historie im Hauptrepository, weil die komplette Commit-Historie des fremden Projekts beim Einbinden mit hereinkommt, sofern man nicht mit --squash arbeitet. Für die meisten Teams überwiegt der praktische Vorteil: Jeder, der klont, hat sofort den vollständigen Code, ohne zusätzliche Befehle, und Werkzeuge, die Submodule nicht kennen, funktionieren trotzdem einwandfrei.


# Fremdes Repository als Subtree einbinden, Historie komprimiert
git subtree add --prefix=vendor/shared-library \
  https://example.com/shared-library.git main --squash

# Aenderungen aus dem Original-Repository nachziehen
git subtree pull --prefix=vendor/shared-library \
  https://example.com/shared-library.git main --squash

4. Praktischer Alltag mit Submodulen

Der tägliche Umgang mit Submodulen bringt eine zusätzliche mentale Ebene mit sich, denn ein Submodule-Ordner befindet sich fast immer in einem sogenannten Detached-HEAD-Zustand: Er zeigt auf einen konkreten Commit, nicht auf einen Branch. Wer im Submodule Aenderungen vornimmt, muss dort explizit auf einen Branch wechseln, committen und pushen, bevor der Verweis im Hauptrepository überhaupt aktualisiert werden kann.

Vergisst jemand im Team, den aktualisierten Submodule-Verweis im Hauptrepository zu committen, arbeiten andere Entwickler unbemerkt mit einem veralteten Stand weiter. In der Praxis hilft git status im Hauptrepository dabei, solche Abweichungen sichtbar zu machen, weil Git einen veränderten Submodule-Commit als modifizierte Datei ausweist.

5. Praktischer Alltag mit Subtree, inklusive Rücksynchronisation

Weil Subtree-Code wie normaler Projektcode aussieht, können Entwickler direkt darin ändern, ohne an Submodule-Besonderheiten zu denken. Die eigentliche Herausforderung liegt darin, solche lokalen Aenderungen später zurück in das Original-Repository zu bringen. Dafür existiert git subtree push, das die relevanten Commits aus dem Unterordner herausfiltert und in das fremde Repository schiebt.

Bei Repositories mit vielen parallelen Aenderungen kann dieser Rücksynchronisationsschritt spürbar langsamer werden, weil Git die komplette Historie nach relevanten Aenderungen im jeweiligen Prefix durchsuchen muss. Für Projekte, die nur gelegentlich Updates aus der Quelle ziehen und selten selbst zurückliefern, ist das kein praktisches Problem, bei sehr aktiver bidirektionaler Zusammenarbeit lohnt sich ein Blick auf dedizierte Werkzeuge wie git-subtree-Wrapper oder alternative Monorepo-Strategien.


# Lokale Aenderungen im Subtree-Ordner zurück ins Original-Repository schieben
git subtree push --prefix=vendor/shared-library \
  https://example.com/shared-library.git feature/lokale-anpassung

6. Auswirkungen auf CI-Pipelines und Checkout-Zeit

In CI-Umgebungen macht sich der Unterschied besonders deutlich bemerkbar. Ein Standard-Checkout ohne zusätzliche Konfiguration reicht bei Subtree vollständig aus, weil der Code physisch im Repository liegt. Bei Submodulen muss die Pipeline-Konfiguration explizit wissen, dass rekursiv initialisiert werden muss, sei es über GIT_SUBMODULE_STRATEGY: recursive in GitLab CI oder eine entsprechende Checkout-Action-Option bei GitHub Actions.

Wird dieser Schritt vergessen, scheitert der Build oft erst spät und mit einer schwer zu deutenden Fehlermeldung über fehlende Dateien, nicht mit einem offensichtlichen Git-Fehler. Teams, die Submodule einsetzen, sollten diesen Schritt deshalb explizit in der Pipeline-Dokumentation und in Onboarding-Anleitungen für neue Entwickler festhalten.

7. Subtree und Submodule in Magento-Projekten

In der Magento-Welt taucht die Frage typischerweise bei eigenen Hyvä-Kindthemes, gemeinsam genutzten Modulen über mehrere Mandanten hinweg oder bei internen Composer-Paketen auf, die noch nicht über ein privates Packagist-Repository verteilt werden. Für ein Theme, das mehrere Shops mit identischer Basis, aber kleinen Store-spezifischen Anpassungen nutzen, bietet sich Subtree an, weil Entwickler direkt im Theme-Ordner arbeiten können, ohne sich um Detached-HEAD-Zustände zu kümmern.

Für klar abgegrenzte, selten geänderte Bibliotheken, etwa eine gemeinsame PHP-Utility-Klassensammlung, die von mehreren unabhängigen Magento-Installationen referenziert wird, ist Submodule oft die sauberere Wahl, weil der Verweis auf eine konkrete, getestete Version explizit im Hauptrepository sichtbar bleibt. In der Praxis ersetzt bei echten PHP-Abhängigkeiten aber meist ein privates Composer-Repository beide Ansätze, Subtree und Submodule bleiben dann Werkzeuge für Grenzfälle wie Themes oder Build-Konfiguration.

8. Entscheidungshilfe: welche Strategie wann

Wer häufig ändert, wenig Historie-Ballast will und möchte, dass jeder Entwickler nach einem einfachen Clone sofort arbeitsfähig ist, fährt mit Subtree in der Regel besser. Wer dagegen eine klare, versionierte Trennung zwischen eigenem Code und einer als extern behandelten Abhängigkeit braucht und bereit ist, den zusätzlichen Checkout-Schritt in Kauf zu nehmen, ist mit Submodule gut bedient.

Eine dritte Option, die in vielen Fällen beide Werkzeuge überflüssig macht, ist ein echter Paketmanager: Für PHP-Code ist das Composer mit einem privaten Repository, für JavaScript npm mit einem privaten Registry-Scope. Subtree und Submodule bleiben dann für die Fälle reserviert, in denen kein Paketmanager passt, etwa bei ganzen Theme-Verzeichnissen oder Build-Tooling, das nicht als Paket modelliert werden soll.

9. Von Submodule zu Subtree wechseln und umgekehrt

Ein bestehendes Submodule lässt sich in ein Subtree überführen, indem der Submodule-Eintrag entfernt und der Code anschliessend frisch per git subtree add eingebunden wird, allerdings ohne automatische Uebernahme der ursprünglichen Commit-Historie im Hauptrepository. Der umgekehrte Weg, von Subtree zurück zu Submodule, ist aufwendiger, weil die bereits kopierten Dateien zunächst entfernt und durch einen sauberen Submodule-Verweis ersetzt werden müssen.

In beiden Richtungen empfiehlt es sich, den Wechsel in einem eigenen Commit mit klarer Beschreibung durchzuführen und alle Teammitglieder vorab zu informieren, weil ein solcher Strukturwechsel bestehende lokale Arbeitskopien betrifft und im schlimmsten Fall zu Verwirrung über verschwundene oder verdoppelte Ordner führt.


# Bestehendes Submodule entfernen, bevor es als Subtree neu eingebunden wird
git submodule deinit -f vendor/shared-library
git rm -f vendor/shared-library
rm -rf .git/modules/vendor/shared-library
git commit -m "Submodule vendor/shared-library entfernt"

git subtree add --prefix=vendor/shared-library \
  https://example.com/shared-library.git main --squash
Merkmal Submodule Subtree
Speicherung im Hauptrepository Nur Verweis auf Commit-Hash Vollständiger Code kopiert
Checkout nach git clone Zusätzlicher Schritt submodule update --init nötig Sofort vollständig, kein Zusatzschritt
CI-Konfiguration Muss rekursiven Checkout explizit aktivieren Funktioniert mit Standard-Checkout
Aenderungen zurückliefern Im Submodule direkt committen und pushen git subtree push, kann bei grosser Historie langsam sein
Historie im Hauptrepository Bleibt schlank Wächst, besonders ohne --squash

Mironsoft

Git-Workflows, Branching-Strategien und CI-Hooks

Chaotische Git-Historie und unklare Branching-Regeln im Team?

Wir richten saubere Git-Workflows ein, klären Branching-Strategien fürs Team und automatisieren Qualitätschecks über Git-Hooks und CI-Pipelines, damit die Historie nachvollziehbar bleibt.

Workflow-Audit

Bestehende Branching-Strategie und Merge-Praxis auf Schwachstellen prüfen.

Hook-Automatisierung

Pre-Commit- und Pre-Push-Hooks für Linting, Tests und Commit-Konventionen einrichten.

Team-Schulung

Rebase, Cherry-Pick und Konfliktauflösung im Team praxisnah vermitteln.

10. Zusammenfassung

Subtree vs. Submodule: Das Wichtigste auf einen Blick

Empfehlung

Subtree für häufig geänderte, gemeinsam genutzte Themes, Submodule für stabile, versionierte Abhängigkeiten

CI-Aufwand

Submodule braucht expliziten rekursiven Checkout, Subtree funktioniert ohne Zusatzkonfiguration

Historie

Submodule hält das Hauptrepository schlank, Subtree kopiert Historie mit hinein

Magento-Praxis

Für echte PHP-Abhängigkeiten meist Composer, Subtree/Submodule bleiben für Themes und Grenzfälle

11. FAQ: Subtree vs. Submodule: Das Wichtigste auf einen Blick

1Was ist der wichtigste Unterschied zwischen Git Subtree und Git Submodule?
Submodule speichert nur einen Verweis auf einen konkreten Commit eines fremden Repositories, während Subtree den kompletten Code direkt in einen Unterordner des Hauptrepositories kopiert. Submodule braucht deshalb einen zusätzlichen Checkout-Schritt, Subtree nicht.
2Warum ist ein Submodule-Ordner nach dem Clone leer?
Git speichert im Hauptrepository nur den Verweis auf den Commit-Hash des Submodules, nicht dessen Inhalt. Erst der Befehl git submodule update --init --recursive lädt die tatsächlichen Dateien herunter und legt sie im referenzierten Ordner ab.
3Kann ich in einem Subtree-Ordner direkt Aenderungen vornehmen?
Ja, das ist einer der Hauptvorteile von Subtree. Der Code verhält sich wie normaler Projektcode, Aenderungen lassen sich normal committen und über git subtree push später in das Original-Repository zurückschieben.
4Warum funktioniert git clone ohne Zusatzoptionen bei Submodulen nicht vollständig?
Ein einfacher git clone initialisiert Submodule aus Kompatibilitäts- und Performancegründen nicht automatisch. Ohne den Parameter --recurse-submodules oder einen nachträglichen submodule update Befehl bleiben die verwiesenen Ordner leer.
5Wie aktualisiere ich ein Submodule auf den neuesten Stand des Remote-Branches?
Mit git submodule update --remote wird das Submodule auf den aktuellen Commit des in .gitmodules hinterlegten Branches gebracht. Anschliessend muss der neue Verweis im Hauptrepository committet werden, damit andere Entwickler ihn ebenfalls erhalten.
6Wird bei Subtree die komplette Historie des fremden Repositories übernommen?
Standardmäßig ja. Mit dem Parameter --squash lässt sich die eingebrachte Historie beim Einbinden und bei jedem Pull auf einen einzelnen Commit komprimieren, was das Hauptrepository deutlich schlanker hält.
7Welche Lösung ist für ein gemeinsames Hyva-Theme in mehreren Magento-Shops sinnvoller?
In der Praxis eignet sich Subtree meist besser, weil Entwickler direkt im Theme-Ordner arbeiten können, ohne sich um Detached-HEAD-Zustände zu kümmern, und weil ein einfacher Clone sofort den vollständigen Code liefert.
8Muss man in CI-Pipelines für Submodule etwas Besonderes konfigurieren?
Ja, ohne explizite Konfiguration wie GIT_SUBMODULE_STRATEGY in GitLab CI oder eine entsprechende Checkout-Action-Option bei GitHub Actions bleiben Submodule-Ordner beim Pipeline-Checkout leer, was zu schwer nachvollziehbaren Build-Fehlern führen kann.
9Ist ein privates Composer-Repository nicht die bessere Alternative für beides?
Für echte PHP-Codeabhängigkeiten mit Versionsnummern ist ein privates Composer-Repository meist die sauberste Lösung. Subtree und Submodule bleiben vor allem für Fälle relevant, die sich nicht gut als Paket modellieren lassen, etwa komplette Theme-Verzeichnisse.
10Kann ich von Submodule zu Subtree wechseln, ohne das Projekt neu aufzusetzen?
Ja, das Submodule wird mit git submodule deinit und git rm entfernt, anschliessend bindet git subtree add denselben Code frisch als Subtree ein. Die ursprüngliche Submodule-Historie im Hauptrepository geht dabei allerdings verloren.