Der Grundgedanke des Teilbaum-Merge (engl. Subtree-Merge) besteht darin, dass du zwei Projekte hast und eines der Projekte auf ein Unterverzeichnis des anderen Projekts verweist. Wenn du einen Teilbaum-Merge durchführst, ist Git so versiert zu erkennen, dass das ein Unterverzeichnis ein Teilbaum des anderen ist und es entsprechend merged.
Wir werden ein Beispiel durcharbeiten, bei dem ein separates Projekt in ein bestehendes Projekt eingefügt und dann der Code des zweiten Projekts in ein Unterverzeichnis des ersten Projekts gemerged wird.
Zunächst werden wir die Anwendung „Rack“ zu unserem Projekt hinzufügen. Wir werden das Rack-Projekt in unserem eigenen Projekt als Remote-Referenz einbinden und es dann in einem eigenen Branch auschecken:
$ git remote add rack_remote https://github.com/rack/rack
$ git fetch rack_remote --no-tags
warning: no common commits
remote: Counting objects: 3184, done.
remote: Compressing objects: 100% (1465/1465), done.
remote: Total 3184 (delta 1952), reused 2770 (delta 1675)
Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done.
Resolving deltas: 100% (1952/1952), done.
From https://github.com/rack/rack
* [new branch] build -> rack_remote/build
* [new branch] master -> rack_remote/master
* [new branch] rack-0.4 -> rack_remote/rack-0.4
* [new branch] rack-0.9 -> rack_remote/rack-0.9
$ git checkout -b rack_branch rack_remote/master
Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master.
Switched to a new branch "rack_branch"Jetzt haben wir das Root-Verzeichnis des Rack-Projekts in unserem rack_branch und unser eigenes Projekt im master Branch.
Wenn du die beiden Branches prüfst, kannst du sehen, dass sie unterschiedliche Projekt-Roots haben:
$ ls
AUTHORS KNOWN-ISSUES Rakefile contrib lib
COPYING README bin example test
$ git checkout master
Switched to branch "master"
$ ls
READMEDas ist irgendwie ein merkwürdiges Konzept. Nicht alle Branches in deinem Repository müssen unbedingt Branches desselben Projektes sein. Es ist nicht allgemein üblich, weil es selten hilfreich ist. Allerdings ist es ziemlich wahrscheinlich, dass die Branches völlig unterschiedliche Verläufe enthalten.
In unserem Fall wollen wir das Rack-Projekt als Unterverzeichnis in unser Projekt master einbringen.
Das können wir in Git mit git read-tree tun.
Mehr über den Befehl read-tree und seiner Verwandten erfährst du in ch10-git-internals.asc. Vorab solltest du erfahren, dass er den Root-Tree eines Branchs in deine aktuelle Staging-Area und dein aktuelles Arbeitsverzeichnis einliest.
Wir sind gerade zu deinem Branch master zurückgewechselt und ziehen den Branch rack_branch in das Unterverzeichnis rack unseres master Branchs des Hauptprojektes:
$ git read-tree --prefix=rack/ -u rack_branchBei einem Commit sieht es so aus, als befänden sich alle Rack-Dateien unterhalb dieses Unterverzeichnisses — als ob wir sie aus einem Tarball hineinkopiert hätten. Interessant ist, dass wir Änderungen in einem der Branches relativ leicht mit anderen Branches mergen können. Falls das Rack-Projekt aktualisiert wird, können wir die Änderungen einbinden, indem wir zu diesem Branch wechseln und pullen:
$ git checkout rack_branch
$ git pullDann können wir diese Änderungen wieder in unserem Branch master zusammenführen.
Um die Änderungen zu pullen und die Commit-Nachricht vorzubereiten, verwendet man die Option --squash sowie die Option -Xsubtree der rekursiven Merge-Strategie.
Die rekursive Strategie ist hier die Voreinstellung, aber wir fügen sie der Klarheit halber ein.
$ git checkout master
$ git merge --squash -s recursive -Xsubtree=rack rack_branch
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requestedAlle Änderungen aus dem Rack-Projekt werden in das Projekt gemerged und können lokal committet werden.
Du kannst auch das Gegenteil tun — Änderungen im Unterverzeichnis rack deines master Branchs vornehmen und sie dann später in deinem Branch rack_branch mergen, um sie den Autoren zu übermitteln oder sie zum Upstream zu pushen.
Dadurch haben wir einen Workflow, der dem Submodul-Workflow ähnelt, ohne Submodule zu verwenden (die wir in ch07-git-tools.asc behandeln werden). Wir können Branches mit anderen verwandten Projekten in unserem Repository vorhalten und sie gelegentlich in unserem Projekt mergen. In gewisser Weise ist das nützlich. Beispielsweise kann der gesamte Code an einen einzigen Ort übermittelt werden. Allerdings gibt es auch Nachteile. Es ist etwas komplizierter und somit leichter, Fehler bei der Re-Integration von Änderungen zu machen oder versehentlich einen Branch in ein nicht relevantes Repository zu pushen.
Eine weitere etwas eigenartige Eigenschaft ist, dass du den Unterschied zwischen dem, was in deinem Unterverzeichnis rack steht, und dem Code in deinem Branch rack_branch nicht mit dem normalen diff Befehl erhalten können (um zu prüfen, ob du sie mergen musst).
Stattdessen musst du git diff-tree auf dem Branch, mit dem du vergleichen willst, ausführen:
$ git diff-tree -p rack_branchUm den Inhalt deines rack Unterverzeichnisses mit dem Branch master auf dem Remote-Server zu vergleichen, kannst du auch folgendes ausführen:
$ git diff-tree -p rack_remote/master