Wie extrahiere ich ein Git-Unterverzeichnis und mache daraus ein Submodul?

119

Ich habe vor einigen Monaten ein Projekt gestartet und alles in einem Hauptverzeichnis gespeichert. In meinem Hauptverzeichnis "Projekt" gibt es mehrere Unterverzeichnisse, die verschiedene Dinge enthalten: Projekt / Papier enthält ein in LaTeX Project geschriebenes Dokument / Quellcode / RailsApp enthält meine Rails-App.

"Project" ist GITified und es gab viele Commits sowohl im "paper" - als auch im "RailsApp" -Verzeichnis. Da ich cruisecontrol.rb für meine "RailsApp" verwenden möchte, frage ich mich, ob es eine Möglichkeit gibt, aus "RailsApp" ein Submodul zu machen, ohne den Verlauf zu verlieren.

Cœur
quelle
2
Auch eine sehr gute Antwort: stackoverflow.com/questions/359424/…
Rehno Lindeque

Antworten:

122

Heutzutage gibt es einen viel einfacheren Weg als die manuelle Verwendung des Git-Filter-Zweigs: Git-Teilbaum

Installation

HINWEIS git-subtree ist jetzt Teil von git(wenn Sie Contrib installieren) ab 1.7.11, sodass Sie es möglicherweise bereits installiert haben. Sie können dies durch Ausführen überprüfen git subtree.


So installieren Sie git-subtree von der Quelle (für ältere Versionen von git):

git clone https://github.com/apenwarr/git-subtree.git

cd git-subtree
sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree

Oder wenn Sie die Manpages und alles wollen

make doc
make install

Verwendung

Teilen Sie einen größeren in kleinere Stücke:

# Go into the project root
cd ~/my-project

# Create a branch which only contains commits for the children of 'foo'
git subtree split --prefix=foo --branch=foo-only

# Remove 'foo' from the project
git rm -rf ./foo

# Create a git repo for 'foo' (assuming we already created it on github)
mkdir foo
pushd foo
git init
git remote add origin [email protected]:my-user/new-project.git
git pull ../ foo-only
git push origin -u master
popd

# Add 'foo' as a git submodule to `my-project`
git submodule add [email protected]:my-user/new-project.git foo

Für eine detaillierte Dokumentation (Manpage) lesen Sie bitte git-subtree.txt.

Apenwarr
quelle
10
Git Teilbaum Felsen!
Simon Woodside
3
Aber ist es nicht der Sinn von git-subtree, die Verwendung von Submodulen zu vermeiden? Ich meine, Sie sind zwar der Autor des Git-Teilbaums (es sei denn, es liegt eine Kollision mit dem Spitznamen vor), aber es sieht so aus, als hätte sich der Git-Teilbaum geändert, obwohl der von Ihnen angezeigte Befehl noch gültig zu sein scheint. Verstehe ich das richtig?
Blaisorblade
17
Git-Teilbaum ist jetzt Teil von Git (wenn Sie Contrib installieren) ab 1.7.11
Jeremy
8
Nun git rm -rf ./fooentfernt foovon HEADaber nicht Filter my-projectist voller Geschichte. Dann git submodule add [email protected]:my-user/new-project.git foomacht nur fooein Submodul ab HEAD. In dieser Hinsicht filter-branchist die Skripterstellung überlegen, da sie es ermöglicht, "so zu tun, als ob Subdir von Anfang an ein Submodul wäre"
Gregory Pakosz,
thx for this - git subtree docs nur ein bisschen verwirrend, und das ist (für mich) das offensichtlich nützlichste, was ich damit machen wollte ...
hwjp
38

Kasse Git Filter-Zweig .

Der ExamplesAbschnitt der Manpage zeigt, wie Sie ein Unterverzeichnis in ein eigenes Projekt extrahieren und dabei den gesamten Verlauf beibehalten und den Verlauf anderer Dateien / Verzeichnisse verwerfen (genau das, wonach Sie suchen).

So schreiben Sie das Repository so um, dass es so aussieht, als wäre foodir/es sein Projektstamm gewesen, und verwerfen alle anderen Verlaufsdaten:

   git filter-branch --subdirectory-filter foodir -- --all

So können Sie beispielsweise ein Bibliotheksunterverzeichnis in ein eigenes Repository verwandeln.
Beachten Sie, --dass filter-branchOptionen von Revisionsoptionen getrennt werden und dass --allalle Zweige und Tags neu geschrieben werden müssen.

Pat Notz
quelle
1
Das hat bei mir gut funktioniert. Der einzige Nachteil, den ich bemerkte, war, dass das Ergebnis eine einzelne Hauptniederlassung mit allen Commits war.
Aceofspades
@aceofspades: Warum ist das ein Nachteil?
Naught101
2
Für mich ist der springende Punkt beim Extrahieren von Commits aus einem Git-Repo, dass ich die Geschichte beibehalten möchte.
Aceofspades
13

Eine Möglichkeit, dies zu tun, ist die Umkehrung - entfernen Sie alles außer der Datei, die Sie behalten möchten.

Erstellen Sie im Grunde genommen eine Kopie des Repositorys und git filter-branchentfernen Sie dann alles außer den Dateien / Ordnern, die Sie behalten möchten.

Zum Beispiel habe ich ein Projekt, aus dem ich die Datei tvnamer.pyin ein neues Repository extrahieren möchte :

git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD

Damit wird git filter-branch --tree-filterjedes Commit durchlaufen, der Befehl ausgeführt und der resultierende Verzeichnisinhalt erneut festgeschrieben. Dies ist äußerst destruktiv (Sie sollten dies also nur für eine Kopie Ihres Repositorys tun!) Und kann eine Weile dauern (ca. 1 Minute bei einem Repository mit 300 Commits und ca. 20 Dateien).

Der obige Befehl führt nur das folgende Shell-Skript für jede Revision aus, das Sie natürlich ändern müssten (damit Ihr Unterverzeichnis stattdessen ausgeschlossen wird tvnamer.py):

for f in *; do
    if [ $f != "tvnamer.py" ]; then
        rm -rf $f;
    fi;
done

Das größte offensichtliche Problem ist, dass alle Festschreibungsnachrichten hinterlassen werden, auch wenn sie nicht mit der verbleibenden Datei zusammenhängen. Das Skript git-remove-empty-commits behebt dieses Problem.

git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

Sie müssen das -fForce-Argument verwenden, das filter-brancherneut ausgeführt wird, wenn etwas drin ist refs/original/(was im Grunde eine Sicherung ist).

Natürlich wird dies niemals perfekt sein, zum Beispiel wenn Ihre Commit-Nachrichten andere Dateien erwähnen, aber es ist ungefähr so ​​nah, wie es ein Git-Strom zulässt (soweit mir sowieso bekannt ist).

Führen Sie dies immer nur auf einer Kopie Ihres Repositorys aus! - aber zusammenfassend, um alle Dateien außer "thisismyfilename.txt" zu entfernen:

git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD
git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'
dbr
quelle
4
git filter-branchhat (heutzutage?) eine eingebaute Option, um leere Commits zu entfernen, nämlich --prune-empty. Eine bessere Anleitung finden Sie git filter-branchin den Antworten auf diese Frage: stackoverflow.com/questions/359424/…
Blaisorblade
4

Sowohl CoolAJ86- als auch Apenwarr- Antworten sind sehr ähnlich. Ich ging zwischen den beiden hin und her und versuchte, Teile zu verstehen, die in beiden fehlten. Unten ist eine Kombination von ihnen.

Navigieren Sie zuerst mit Git Bash zum Stammverzeichnis des zu teilenden Git-Repos. In meinem Beispiel hier ist das~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add origin [email protected]:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add [email protected]:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

Unten finden Sie eine Kopie von oben, wobei die anpassbaren Namen ersetzt wurden und stattdessen https verwendet werden. Stammordner ist jetzt~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add origin https://github.com/Feddas/SoArchitecture.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package
ShawnFeatherly
quelle
3

Wenn Sie eine Teilmenge von Dateien in ein neues Repository übertragen möchten, aber den Verlauf beibehalten möchten, erhalten Sie im Grunde einen vollständig neuen Verlauf. Dies würde im Grunde wie folgt funktionieren:

  1. Erstellen Sie ein neues Repository.
  2. Führen Sie für jede Revision Ihres alten Repositorys die Änderungen an Ihrem Modul in das neue Repository ein. Dadurch wird eine "Kopie" Ihres vorhandenen Projektverlaufs erstellt.

Es sollte etwas unkompliziert sein, dies zu automatisieren, wenn es Ihnen nichts ausmacht, ein kleines, aber haariges Skript zu schreiben. Einfach, ja, aber auch schmerzhaft. Die Leute haben in der Vergangenheit in Git die Geschichte neu geschrieben. Sie können danach suchen.

Alternativ: Klonen Sie das Repository und löschen Sie das Papier im Klon. Löschen Sie die App im Original. Dies würde eine Minute dauern, es wird garantiert funktionieren und Sie können zu wichtigeren Dingen zurückkehren, als zu versuchen, Ihre Git-Geschichte zu bereinigen. Machen Sie sich keine Sorgen über den Festplattenspeicher, den redundante Kopien des Verlaufs belegen.

Dietrich Epp
quelle