In meinem Fall hatte ich ein my-plugin
Repository und ein main-project
Repository, und ich wollte so tun, als wäre my-plugin
das immer im plugins
Unterverzeichnis von entwickelt worden main-project
.
Grundsätzlich habe ich den Verlauf des my-plugin
Repositorys so umgeschrieben , dass die gesamte Entwicklung im plugins/my-plugin
Unterverzeichnis stattfand. Dann habe ich die Entwicklungsgeschichte von my-plugin
in die main-project
Geschichte aufgenommen und die beiden Bäume zusammengeführt. Da war noch kein plugins/my-plugin
Verzeichnis in dermain-project
Repository , war dies eine triviale Zusammenführung ohne Konflikte. Das resultierende Repository enthielt den gesamten Verlauf beider Originalprojekte und hatte zwei Wurzeln.
TL; DR
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
Lange Version
Erstellen Sie zunächst eine Kopie des my-plugin
Repositorys, da wir den Verlauf dieses Repositorys neu schreiben werden.
Navigieren Sie nun zum Stammverzeichnis des my-plugin
Repositorys, überprüfen Sie (wahrscheinlich master
) Ihren Hauptzweig und führen Sie den folgenden Befehl aus. Natürlich sollten Sie ersetzen my-plugin
und plugins
was auch immer Ihre tatsächlichen Namen sind.
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
Nun zu einer Erklärung. git filter-branch --tree-filter (...) HEAD
führt den (...)
Befehl für jedes Commit aus, von dem aus erreichbar ist HEAD
. Beachten Sie, dass dies direkt mit den für jedes Commit gespeicherten Daten zusammenhängt, sodass wir uns nicht um die Begriffe "Arbeitsverzeichnis", "Index", "Staging" usw. kümmern müssen.
Wenn Sie einen filter-branch
fehlgeschlagenen Befehl ausführen , bleiben einige Dateien im .git
Verzeichnis zurück, und beim nächsten Versuch filter-branch
wird dies beanstandet, es sei denn, Sie geben die -f
Option an filter-branch
.
Was den eigentlichen Befehl angeht, hatte ich nicht viel Glück, das bash
zu tun, was ich wollte, also verwende ich stattdessen zsh -c
, zsh
um einen Befehl auszuführen. Zuerst setze ich die extended_glob
Option, die die ^(...)
Syntax im mv
Befehl glob_dots
aktiviert , sowie die Option, mit der ich Punktedateien (wie .gitignore
) mit einem glob ( ^(...)
) auswählen kann .
Als nächstes benutze ich den mkdir -p
Befehl sowohl zu schaffen plugins
und plugins/my-plugin
zugleich.
Schließlich verwende ich die Funktion zsh
"Negative Glob" ^(.git|plugins)
, um alle Dateien im Stammverzeichnis des Repositorys mit Ausnahme .git
des neu erstellten my-plugin
Ordners abzugleichen. (Ein Ausschluss ist hier .git
möglicherweise nicht erforderlich, aber der Versuch, ein Verzeichnis in sich selbst zu verschieben, ist ein Fehler.)
In meinem Repository enthielt das anfängliche Festschreiben keine Dateien, sodass der mv
Befehl beim ersten Festschreiben einen Fehler zurückgab (da nichts zum Verschieben verfügbar war). Deshalb habe ich ein hinzugefügt || true
, git filter-branch
damit nicht abgebrochen wird.
Die --all
Option weist filter-branch
an, den Verlauf für alle Zweige im Repository neu zu schreiben , und das Extra --
muss angeben git
, dass er als Teil der Optionsliste für neu zu schreibende Zweige und nicht als Option für sich filter-branch
selbst interpretiert werden soll .
Navigieren Sie nun zu Ihrem main-project
Repository und überprüfen Sie den Zweig, in den Sie zusammenführen möchten. Fügen Sie Ihre lokale Kopie des my-plugin
Repositorys (mit geändertem Verlauf) als Remote von hinzu main-project
:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Sie haben jetzt zwei nicht verwandte Bäume in Ihrem Commit-Verlauf, die Sie mithilfe von:
$ git log --color --graph --decorate --all
Verwenden Sie zum Zusammenführen Folgendes:
$ git merge my-plugin/master --allow-unrelated-histories
Beachten Sie, dass in Git vor 2.9.0 die --allow-unrelated-histories
Option nicht vorhanden ist. Wenn Sie eine dieser Versionen verwenden, lassen Sie einfach die Option weg: Die Fehlermeldung, die dies --allow-unrelated-histories
verhindert, wurde auch in 2.9.0 hinzugefügt.
Sie sollten keine Zusammenführungskonflikte haben. Wenn Sie dies tun, bedeutet dies wahrscheinlich, dass entweder der filter-branch
Befehl nicht ordnungsgemäß funktioniert hat oder bereits ein plugins/my-plugin
Verzeichnis vorhanden war main-project
.
Stellen Sie sicher, dass Sie eine erklärende Commit-Nachricht für zukünftige Mitwirkende eingeben, die sich fragen, was Hackery vor sich hat, um ein Repository mit zwei Wurzeln zu erstellen.
Mit dem obigen git log
Befehl können Sie das neue Festschreibungsdiagramm visualisieren, das zwei Root-Festschreibungen enthalten sollte . Beachten Sie, dass nur der master
Zweig zusammengeführt wird . Dies bedeutet, dass Sie, wenn Sie wichtige Arbeit an anderen my-plugin
Zweigen haben, die Sie in den main-project
Baum einbinden möchten, die my-plugin
Fernbedienung erst löschen sollten , wenn Sie diese Zusammenführungen durchgeführt haben. Wenn Sie dies nicht tun, befinden sich die Commits aus diesen Zweigen weiterhin im main-project
Repository, einige sind jedoch nicht erreichbar und können möglicherweise nicht ordnungsgemäß gespeichert werden. (Außerdem müssen Sie von SHA auf sie verweisen, da durch das Löschen einer Fernbedienung die Fernverfolgungszweige entfernt werden.)
Optional können Sie my-plugin
die my-plugin
Fernbedienung entfernen , nachdem Sie alles zusammengeführt haben, was Sie behalten möchten :
$ git remote remove my-plugin
Sie können jetzt die Kopie des my-plugin
Repositorys, dessen Verlauf Sie geändert haben, sicher löschen . In meinem Fall habe ich dem realen my-plugin
Repository auch einen Verfallshinweis hinzugefügt, nachdem die Zusammenführung abgeschlossen und verschoben wurde.
Getestet unter Mac OS X El Capitan mit git --version 2.9.0
und zsh --version 5.2
. Ihr Kilometerstand kann variieren.
Verweise: