Layer 8Ehrlicher Tech-Support

git clone: Unterordner eines Submoduls bleibt leer

Nach git clone ist ein Unterverzeichnis leer, der Build findet nichts. Submodule holt nur --recurse-submodules oder submodule update --init.

Aufwand:
ca. 10 Min.
Voraussetzung:
Ein Terminal mit git, Zugriff auf das Repository des Submoduls (Lese-Recht, SSH-Schlüssel oder Token), Die Datei .gitmodules im Hauptprojekt
Gilt für:
Git 2.54.0 (Dokumentation von git-clone und git-submodule auf git-scm.com, Stand 17.10.2026)

Das Symptom

Ein Projekt wird frisch geklont, der Build startet und bricht ab: eine Headerdatei fehlt, ein Theme-Ordner ist leer, ein Node-Paket unter vendor/ enthält nichts außer dem Verzeichnis selbst. Im Webinterface des Repositorys ist der Ordner dagegen sichtbar, mit einem kleinen Pfeil und einem Commit-Hash daneben. Der Kollege, bei dem alles läuft, hat das Projekt vor zwei Jahren geklont und weiß nicht mehr, was er damals getan hat.

Die Reflexe sind bekannt: den fehlenden Ordner aus einem Zip-Download nachfüllen, die Dateien aus dem Rechner des Kollegen kopieren oder das Submodul-Repository separat klonen und von Hand an die richtige Stelle schieben. Alles davon baut einmal und bricht beim nächsten Update.

Die „Layer 8“-Wahrheit

Der leere Ordner ist ein Submodul, und ein Submodul ist kein Inhalt, sondern ein Verweis. Das Hauptprojekt speichert in .gitmodules die Adresse des fremden Repositorys und im eigenen Baum den Commit-Hash, der dort gelten soll. Mehr nicht. Ein gewöhnliches git clone holt das Hauptprojekt und lässt die Verweise, wo sie sind. Git tut hier exakt, was es soll; es wurde nur nicht gebeten, mehr zu tun.

Die Dokumentation von git clone sagt, was die Bitte heißt. Die Option –recurse-submodules bewirkt: „After the clone is created, initialize and clone submodules within based on the provided …“ – gemeint ist ein Pfadmuster, das Git pathspec nennt. Ohne Pfadmuster gilt: „all submodules are initialized and cloned“. Und die Seite verrät auch, was das intern ist, nämlich dasselbe wie ein git submodule update –init –recursive unmittelbar nach dem Klonen. Genau diesen Befehl führt man also aus, wenn der Klon schon steht. Die Dokumentation von git submodule beschreibt update als den Schritt, der die registrierten Submodule auf den vom Hauptprojekt erwarteten Stand bringt, „by cloning missing submodules, fetching missing commits in submodules and updating the working tree of the submodules“. Die Option –init erspart den sonst getrennten Befehl git submodule init, der die Adressen aus .gitmodules nach .git/config überträgt.

Zweiter Stolperstein: Nach dem Aktualisieren steht das Submodul auf einem detached HEAD. Auch das ist beabsichtigt. Das Hauptprojekt zeichnet einen Commit auf, keinen Zweig, und die Dokumentation legt die Standardmethode fest: „If neither is given, a checkout is performed.“ Bei checkout wird der aufgezeichnete Commit im Submodul ohne Zweig ausgecheckt. Das garantiert, dass jeder Klon denselben Stand bekommt, unabhängig davon, was im Submodul-Repository inzwischen auf dem Hauptzweig passiert ist. Wer das Submodul weiterentwickeln will, legt dort einen Zweig an; wer nur bauen will, lässt es.

Dritter Stolperstein, meist Monate später: Das Submodul zieht auf einen anderen Server um. .gitmodules wird aktualisiert, aber die beim ersten init in .git/config kopierte Adresse bleibt stehen, und update läuft gegen die alte. Dafür gibt es git submodule sync, das laut Dokumentation die Konfiguration an .gitmodules angleicht, ausdrücklich für den Fall geänderter Adressen.

Was der Diagnose hilft, ist git submodule status. Die Dokumentation erklärt die Vorzeichen vor dem Hash: Minus für nicht initialisiert, Plus für einen ausgecheckten Commit, der nicht zu dem im Index des Hauptprojekts passt, U für Merge-Konflikte. Eine Liste voller Minuszeichen direkt nach dem Klonen ist kein Schaden, sondern der normale Ausgangszustand. Der Fehler liegt nicht in Git und nicht im Repository, sondern in der Annahme, dass ein Klon alles enthält, was im Webinterface zu sehen ist.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Prüfen, ob der leere Ordner ein Submodul ist

    Ein leeres Verzeichnis direkt nach dem Klonen ist verdächtig, denn Git speichert leere Verzeichnisse gar nicht. Steht der Pfad in .gitmodules, ist die Sache klar. git submodule status zeigt zusätzlich den Zustand: Ein Minus vor dem Hash bedeutet laut Dokumentation, dass das Submodul nicht initialisiert ist. Damit ist der Build-Fehler erklärt, bevor jemand den Ordner von Hand aus einem Zip befüllt und sich damit die spätere Versionspflege ruiniert.

    cat .gitmodules
    git submodule status
    
  2. 2.

    Submodule nachträglich holen

    git submodule update --init --recursive registriert alle noch nicht initialisierten Submodule, klont die fehlenden und checkt den im Hauptprojekt aufgezeichneten Commit aus. --recursive sorgt dafür, dass auch Submodule innerhalb von Submodulen geholt werden. Der Vorgang braucht Zugriff auf die Repositorys der Submodule; scheitert er mit einem Berechtigungsfehler, liegt es an der Adresse oder am Schlüssel, nicht am Befehl. Die Adresse steht in .gitmodules und kann relativ zum Hauptprojekt angegeben sein.

    git submodule update --init --recursive
    git submodule status
    
  3. 3.

    Beim nächsten Mal gleich richtig klonen

    Mit --recurse-submodules erledigt git clone beides in einem Zug. Die Dokumentation von git clone beschreibt das Ergebnis: „Submodules are initialized and cloned using their default settings.“ Wird kein Pfadmuster angegeben, gilt das für alle Submodule. Die Option ist unwirksam, wenn der Klon keinen Arbeitsbaum hat, also bei --bare oder --no-checkout; dort gibt es schlicht nichts, worin ein Submodul ausgecheckt werden könnte.

    git clone --recurse-submodules <adresse> projekt
    
  4. 4.

    Den detached HEAD im Submodul richtig einordnen

    Nach dem Aktualisieren steht jedes Submodul auf dem Commit, den das Hauptprojekt verlangt, und zwar ohne Zweig. Das ist die dokumentierte Standardmethode checkout. Wer im Submodul Änderungen machen will, wechselt dort zuerst auf einen Zweig, sonst hängen die Commits an keinem Zweig. Wer nur bauen will, lässt es, wie es ist. Alternativen wie rebase oder merge lassen sich per submodule.<name>.update einstellen; die Kommandozeile hat laut Dokumentation Vorrang vor dieser Konfiguration.

    cd pfad/zum/submodul
    git switch -c meine-aenderung
    
  5. 5.

    Adressänderung beim Submodul nachziehen

    Zieht das Submodul auf einen anderen Server um, ändert sich .gitmodules, aber nicht die bereits in .git/config eingetragene Adresse. Dann scheitert update mit einem Verbindungsfehler, obwohl .gitmodules stimmt. git submodule sync übernimmt die Adresse aus .gitmodules in die Konfiguration; die Dokumentation nennt genau diesen Fall, dass sich Submodul-Adressen upstream ändern. Danach noch einmal update ausführen.

    git submodule sync --recursive
    git submodule update --init --recursive
    

Häufige Fragen (FAQ)

?Warum ist der Ordner nach dem Klonen leer, obwohl er im Repository sichtbar ist?

Weil der Ordner kein Teil dieses Repositorys ist, sondern ein Submodul: Das Hauptprojekt speichert nur die Adresse in .gitmodules und einen Commit-Hash, nicht die Dateien. Ein gewöhnliches git clone holt das Hauptprojekt und legt für jedes Submodul ein leeres Verzeichnis an. Die Dokumentation von git clone beschreibt, was die Option --recurse-submodules zusätzlich tut: „After the clone is created, initialize and clone submodules within based on the provided <pathspec>.“ Ohne diese Option passiert dieser zweite Schritt nicht, und die Dateien fehlen.

?Wie hole ich die Submodule nachträglich, ohne neu zu klonen?

Mit git submodule update --init, bei verschachtelten Submodulen zusätzlich --recursive. Die Dokumentation von git submodule beschreibt update so: Es bringt die registrierten Submodule auf den Stand, den das Hauptprojekt erwartet, „by cloning missing submodules, fetching missing commits in submodules and updating the working tree of the submodules“. Die Option --init erledigt dabei den sonst separaten Schritt git submodule init: „Initialize all submodules for which git submodule init has not been called so far before updating.“ Laut derselben Seite ist das gleichwertig damit, was git clone --recurse-submodules nach dem Klonen ausführt.

?Warum steht das Submodul danach auf einem detached HEAD?

Weil das die dokumentierte Standardeinstellung ist. Das Hauptprojekt zeichnet einen bestimmten Commit auf, keinen Zweig. Die Dokumentation von git submodule update sagt für den Fall, dass weder Kommandozeile noch submodule.<name>.update etwas anderes vorgeben: „If neither is given, a checkout is performed.“ Und für checkout gilt: Der im Hauptprojekt aufgezeichnete Commit wird im Submodul „on a detached HEAD“ ausgecheckt. Das ist kein Fehler, sondern die Zusicherung, dass jeder Klon exakt denselben Stand des Submoduls bekommt. Wer im Submodul arbeiten will, legt dort bewusst einen Zweig an.

?Woran erkenne ich, welche Submodule noch fehlen?

git submodule status listet die Submodule mit dem Commit-Hash und ihrem Pfad; die Dokumentation erklärt die Vorzeichen: ein Minus, wenn das Submodul nicht initialisiert ist, ein Plus, wenn der ausgecheckte Commit nicht dem im Index des Hauptprojekts entspricht, und ein U bei Merge-Konflikten. Ein Minus vor jedem Eintrag direkt nach dem Klonen ist also das erwartete Bild und zugleich die Diagnose: initialisieren und aktualisieren, dann verschwindet das Vorzeichen.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWLästigdetached HEAD

Git: detached HEAD state

Git meldet „detached HEAD“? Du stehst nicht auf einem Branch, sondern direkt auf einem Commit — Änderungen hier gehen ohne Gegenmaßnahme leicht verloren.

SWAnfängerfehlerfatal: not a git repository

git: fatal: not a git repository

git meldet 'fatal: not a git repository (or any of the parent directories)'? Du bist im falschen Ordner oder .git fehlt. So kommst du zurück ins Repo.

SWLästigNot possible to fast-forward, aborting.

git pull: Not possible to fast-forward, aborting

git pull bricht ab, sobald eigener und entfernter Zweig auseinandergelaufen sind. Laut Git 2.55 ist --ff-only die Voreinstellung, nicht der Merge.

SWLästigCould not resolve dependencies

Maven: Could not resolve dependencies

Maven findet eine Abhängigkeit nicht? Meist fehlt das Repository, ein Proxy blockt, oder ein halb geladenes Paket verstopft den Cache.