Submodule wurden in Git eingeführt um Repositorys zu verknüpfen. In einem Hauptrepository kann man unter einem Verzeichnis ein Subrepository einbinden. Dabei wird in dem Verzeichnis ein normaler Klone des Subrepositorys erzeugt. Im Hauptrepository wird sich lediglich die URL zu dem Subrepository und das eingebundene Commit gemerkt.
Die Arbeit mit Submodulen erscheint vielen Entwicklern sehr umständlich und fehleranfällig.
Das beginnt schon beim Klonen eines Repositorys mit Submodulen,
da sind zusätzlich ein git submodule init
und git submodule update
notwendig.
Will man eine neue Version eines Submodules einbinden muss man erst innerhalb des Submodules
ein Wechsel des Commits durchführen und anschliessend im Hauptrepository ein neues Commit anlegen.
Wenn Änderungen direkt im eingebundenen Submodulen durchgeführt werden, müssen diese erst durch ein separates Commit abgeschlossen werden. Erst dann kann das Commit auf dem Hauptrepository durchgeführt werden.
Beim git push
müssen Submodule ebenso separat behandelt werden. Häufig wird das Push
für ein Submodul vergessen. Beim nächsten Fetch auf dem Hauptrepository bekommen dann andere
Entwickler eine Fehlermeldung, da das Submodule-Commit nicht gefunden wird.
Mit dem subtree
-Befehl kann man auch ein Subrepository in ein Hauptrepository importieren
und den Inhalt des Subrepositorys unter einem beliebigen Verzeichnis (--prefix
) einbinden.
$ cd my-repos
$ git subtree add --prefix html5-boilerplate \
--squash https://github.com/h5bp/html5-boilerplate.git v2.0
$ git log --oneline --graph
* f4663b8 Merge commit '8c7ad50a1ff614f0af931e3819a1ed83c41d7ec7' as 'html5-..'
|\
| * 8c7ad50 Squashed 'html5-boilerplate/' content from commit 8bfbd13
* a5ade29 init
Im Gegensatz zu Submodulen befinden sich die Dateien komplett im Hauptrepository. Dadurch müssen beim Klonen und Arbeiten mit dem Hauptrepository keine besonderen Befehle benutzen werden.
Das Subrepository-Verzeichnis kann jederzeit auf eine andere Version des Subrepositorys aktualisiert werden.
$ git subtree pull --prefix html5-boilerplate \
--squash https://github.com/h5bp/html5-boilerplate.git master
$ git log --oneline --graph
* e84d6e5 Merge commit '7547e36dab36201a5f27f2f5e56fe3412c820175'
|\
| * 7547e36 Squashed 'html5-boilerplate/' changes from 8bfbd13..02bdaea
* | f4663b8 Merge commit '8c7ad50a1ff614f0af931e3819a1ed83c41d7ec7' as 'html5-..'
|\ \
| |/
| * 8c7ad50 Squashed 'html5-boilerplate/' content from commit 8bfbd13
* a5ade29 init
Werden Änderungen an den Dateien des Subrepositorys im Hauptrepository durchgeführt, können diese durch ein normales Commit festgeschrieben werden.
$ edit html5-boilerplate/404.html
$ git commit -am "typo"
$ git log --oneline --graph -3
* 2bc032c typo
* e84d6e5 Merge commit '7547e36dab36201a5f27f2f5e56fe3412c820175'
|\
| * 7547e36 Squashed 'html5-boilerplate/' changes from 8bfbd13..02bdaea
Anschliessend ist es möglich diese Änderungen in Commits des Subrepositorys zu überführen.
$ git subtree split --prefix html5-boilerplate --branch html5-boilerplate/changes
Dabei legt der subtree
-Befehl für jedes Commit mit Änderungen
der Subrepository-Dateien ein neues Commit auf Basis der Subrepositorys-Commits an.
Also fast wie beim normalen Rebasing, nur, dass die Verzeichnisstruktur angepasst wird und
Commits die keine relevanten Änderungen enthalten ignoriert werden.
Am Ende wird ein neuer Branch angelegt, der auf die neuen Commits zeigt.
$ git log --oneline --graph -3 html5-boilerplate/changes
* 6b061ad typo
* 02bdaea Docs: add info about using Metro tiles
* 5ed4dad Update CHANGELOG
Diesen Branch kann nun ganz normal mit dem originalen Subrepository zusammengeführt werden.
Es ist auch möglich die Änderungen gleich nach dem Split in ein anderes Repository zu übertragen: subtree push
.
Wenn man Abhängigkeiten zwischen Modulen ausserhalb des Git-Repositorys zu verwalten kann, z.B. mit Maven oder Ivy, sollte man es auch ausserhalb tun. Weder Submodule noch Subtrees ersetzen eine einfache Abhängigkeitsverwaltung.
Wenn allerdings der Sourcecode in das eigene Projekt eingebunden werden muss oder häufig gleichzeitige Änderungen am eigenem Projekt und an einem Submodul vorgenommen werden muss, dann ist Subtree ein gutes Werkzeug.
Im Vergleich zu Submodulen, ist bei Subtrees das normale Arbeiten weniger umständlich und fehleranfällig:
Tipp: Man kann übrigens auch den subtree split
-Befehl nutzen um erstmalig ein Submodul aus einem großen Repository
zu extrahieren.
Der subtree
-Befehl ist erst seit dem Release 1.7.11 offizieller Bestandteil
der Git-Distribution. Allerdings nur optional und nur im contrib
-Verzeichnis zu finden.
Für ältere Versionen kann man es als separates Shell-Skript von
GitHub laden.
Es gibt leider noch einen Bug mit Annotated-Tags. Dieser Bug wurde bisher nur
in diesem GitHub-Commit behoben.