Frage Trennen Sie das Unterverzeichnis in ein separates Git-Repository


Ich habe ein Git Repository, das eine Anzahl von Unterverzeichnissen enthält. Jetzt habe ich festgestellt, dass eines der Unterverzeichnisse nicht mit dem anderen verknüpft ist und in ein separates Repository getrennt werden sollte.

Wie kann ich dies tun, während ich den Verlauf der Dateien im Unterverzeichnis behalte?

Ich denke, ich könnte einen Klon machen und die unerwünschten Teile von jedem Klon entfernen, aber ich nehme an, dies würde mir den kompletten Baum geben, wenn ich eine ältere Revision auschecke usw. Das mag akzeptabel sein, aber ich würde es vorziehen, das zu tun Zwei Repositorys haben keinen gemeinsamen Verlauf.

Um es klar zu machen, habe ich folgende Struktur:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Aber ich möchte das stattdessen:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

1595
2017-12-11 13:57


Ursprung


Antworten:


Aktualisieren: Dieser Prozess ist so üblich, dass das Git-Team es mit einem neuen Tool viel einfacher gemacht hat, git subtree. Siehe hier: Trennen Sie das Unterverzeichnis in ein separates Git-Repository


Sie möchten Ihr Repository klonen und dann verwenden git filter-branch um alles außer dem Unterverzeichnis zu markieren, das Sie in Ihrem neuen Repo haben möchten, um Müll zu sammeln.

  1. So klonen Sie Ihr lokales Repository:

    git clone /XYZ /ABC
    

    (Hinweis: Das Repository wird mit Hilfe fester Links geklont, aber das ist kein Problem, da die fest verbundenen Dateien nicht in sich selbst geändert werden - neue werden erstellt.)

  2. Lassen Sie uns nun die interessanten Zweige beibehalten, die wir ebenfalls umschreiben möchten, und entfernen Sie dann den Ursprung, um zu vermeiden, dass Sie dorthin drängen und dass alte Commits nicht durch den Ursprung referenziert werden:

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    oder für alle entfernten Zweige:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Jetzt möchten Sie vielleicht auch Tags entfernen, die keine Beziehung zum Teilprojekt haben; Sie können das später auch tun, aber Sie müssen möglicherweise Ihr Repo erneut zurückschneiden. Ich habe es nicht getan und habe eine WARNING: Ref 'refs/tags/v0.1' is unchanged für alle Tags (da sie alle nicht mit dem Teilprojekt zusammenhängen); Außerdem wird nach dem Entfernen solcher Tags mehr Speicherplatz beansprucht. Anscheinend git filter-branch sollte in der Lage sein, andere Tags umzuschreiben, aber das konnte ich nicht bestätigen. Wenn Sie alle Tags entfernen möchten, verwenden Sie git tag -l | xargs git tag -d.

  4. Verwenden Sie dann filter-branch und reset, um die anderen Dateien auszuschließen, damit sie bereinigt werden können. Lass uns auch hinzufügen --tag-name-filter cat --prune-empty um leere Commits zu entfernen und Tags umzuschreiben (beachten Sie, dass diese ihre Signatur entfernen müssen):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    oder alternativ nur den HEAD-Zweig neu schreiben und Tags und andere Zweige ignorieren:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Löschen Sie dann die Backup-Reflogs, damit der Speicherplatz wirklich wiederhergestellt werden kann (obwohl die Operation jetzt destruktiv ist).

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    und jetzt haben Sie ein lokales Git-Repository des ABC-Unterverzeichnisses mit all seiner Geschichte erhalten.

Hinweis: Für die meisten Anwendungen, git filter-branch sollte in der Tat den hinzugefügten Parameter haben -- --all. Ja das ist wirklich --Raum--  all. Dies muss der letzte Parameter für den Befehl sein. Wie Matli herausfand, hält dies die Projektzweige und Tags im neuen Repo.

Bearbeiten: Verschiedene Vorschläge aus den Kommentaren wurden eingefügt, um sicherzustellen, dass das Repository tatsächlich geschrumpft ist (was vorher nicht immer der Fall war).


1155
2017-07-25 17:10



Der einfache Weg ™

Es stellt sich heraus, dass dies eine so häufige und nützliche Übung ist, dass die Oberherren von git es wirklich leicht gemacht haben, aber du musst eine neuere Version von git haben (> = 1.7.11 Mai 2012). Siehe die Blinddarm wie installiere ich den neuesten git. Außerdem gibt es eine Praxisbeispiel in dem Walkthrough unten.

  1. Bereiten Sie das alte Repo vor

    pushd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Hinweis:  <name-of-folder> darf KEINE führenden oder nachgestellten Zeichen enthalten. Zum Beispiel der Ordner namens subproject MUSS als bestanden werden subprojectNicht ./subproject/

    Hinweis für Windows-Benutzer: Wenn Ihre Ordnertiefe> 1 ist, <name-of-folder> muss * nix style folder separator (/) haben. Zum Beispiel der Ordner namens path1\path2\subproject MUSS als bestanden werden path1/path2/subproject

  2. Erstellen Sie das neue Repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Verknüpfen Sie das neue Repo mit Github oder wo auch immer

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Aufräumen, wenn gewünscht

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Hinweis: Dadurch bleiben alle historischen Referenzen im Repository erhalten Blinddarm Wenn Sie sich wirklich Sorgen machen, dass Sie ein Passwort eingegeben haben, oder Sie die Dateigröße verringern müssen .git Mappe.

...

Walkthrough

Dies sind die gleiche Schritte wie oben, aber befolge meine genauen Schritte für mein Repository anstatt zu verwenden <meta-named-things>.

Hier ist ein Projekt, das ich für die Implementierung von JavaScript-Browsermodulen in Knoten habe:

tree ~/Code/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

Ich möchte einen einzelnen Ordner aufteilen, btoa, in ein separates Git-Repository

pushd ~/Code/node-browser-compat/
git subtree split -P btoa -b btoa-only
popd

Ich habe jetzt einen neuen Zweig, btoa-only, das hat nur zugesagt btoa und ich möchte ein neues Repository erstellen.

mkdir ~/Code/btoa/
pushd ~/Code/btoa/
git init
git pull ~/Code/node-browser-compat btoa-only

Als nächstes erstelle ich ein neues Repo auf Github oder Bitbucket, oder was auch immer, und füge es hinzu origin (übrigens, "Herkunft" ist nur eine Konvention, nicht Teil des Befehls - Sie könnten es "Remote-Server" oder was auch immer Sie möchten)

git remote add origin git@github.com:node-browser-compat/btoa.git
git push origin -u master

Glücklicher Tag!

Hinweis: Wenn Sie mit a ein Repo erstellt haben README.md, .gitignore und LICENSE, müssen Sie zuerst ziehen:

git pull origin -u master
git push origin -u master

Zuletzt möchte ich den Ordner aus dem größeren Repo entfernen

git rm -rf btoa

...

Blinddarm

Neueste Git auf OS X

Um die neueste Version von Git zu erhalten:

brew install git

Um für OS X zu brauen:

http://brew.sh

Neuester Git auf Ubuntu

sudo apt-get update
sudo apt-get install git
git --version

Wenn das nicht funktioniert (Sie haben eine sehr alte Version von Ubuntu), versuchen Sie es

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

Wenn das immer noch nicht funktioniert, versuchen Sie es

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

Danke an rui.araujo aus den Kommentaren.

Löschen Sie Ihre Geschichte

Standardmäßig entfernt das Entfernen von Dateien aus git diese nicht wirklich von git, es bestätigt nur, dass sie nicht mehr da sind. Wenn Sie die historischen Referenzen tatsächlich entfernen möchten (d. H. Sie haben ein Kennwort festgelegt), müssen Sie Folgendes tun:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

Danach können Sie überprüfen, ob Ihre Datei oder Ihr Ordner überhaupt nicht mehr im Git-Verlauf angezeigt wird

git log -- <name-of-folder> # should show nothing

Aber du kann nicht "push" löscht zu github und dergleichen. Wenn Sie versuchen, erhalten Sie einen Fehler und Sie müssen git pull bevor du es kannst git push - und dann bist du wieder dabei, alles in deiner Geschichte zu haben.

Wenn Sie also den Verlauf aus dem "Ursprung" löschen möchten (dh ihn aus GitHub, BitBucket usw. löschen), müssen Sie den Repo löschen und eine bereinigte Kopie des Repos erneut laden. Aber warte - es gibt mehr! - Wenn Sie wirklich daran interessiert sind, ein Passwort oder Ähnliches loszuwerden, müssen Sie die Sicherung löschen (siehe unten).

Herstellung .git kleiner

Der oben erwähnte Löschverlauf-Befehl hinterlässt immer noch eine Reihe von Backup-Dateien - weil Git nur allzu gut darin ist, Ihnen zu helfen, Ihren Repo nicht versehentlich zu ruinieren. Es wird schließlich verwaiste Dateien über die Tage und Monate gelöscht, aber es lässt sie dort für eine Weile für den Fall, dass Sie feststellen, dass Sie versehentlich etwas gelöscht haben, was Sie nicht wollten.

Also wenn du es wirklich willst den Papierkorb leeren zu Reduziere die Klongröße von einem Repo sofort musst du all diese wirklich seltsamen Sachen machen:

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

Das heißt, ich würde empfehlen, diese Schritte nicht durchzuführen, es sei denn, Sie wissen, dass Sie müssen - nur für den Fall, dass Sie das falsche Unterverzeichnis gelöscht haben, weißt du? Die Backup-Dateien sollten nicht geklont werden, wenn Sie den Repo drücken, sie sind nur in Ihrer lokalen Kopie.

Kredit


1122
2018-06-05 13:15



Pauls Antwort erstellt ein neues Repository mit / ABC, entfernt jedoch nicht / ABC aus / XYZ. Der folgende Befehl entfernt / ABC aus / XYZ:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

Natürlich, testen Sie es zuerst in einem 'clone --no-hardlinks' Repository und folgen Sie ihm mit den Befehlen reset, gc und prune, die Paul listet.


131
2017-10-19 21:10



Ich habe herausgefunden, dass man, um den alten Verlauf aus dem neuen Repository löschen zu können, nach dem filter-branch Schritt.

  1. Mach den Klon und den Filter:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Entfernen Sie jeden Verweis auf die alte Historie. "Herkunft" hat Ihren Klon im Auge behalten, und "Original" ist, wo Filter-Zweig die alten Sachen speichert:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Selbst jetzt kann Ihre Geschichte in einer Packdatei stecken bleiben, die fsck nicht berührt. Zerreißen Sie es, erstellen Sie eine neue Packdatei und löschen Sie die nicht verwendeten Objekte:

    git repack -ad
    

Es gibt eine Erklärung dafür in dem Handbuch für Filter-Zweig.


94
2018-06-09 15:41



Bearbeiten: Bash-Skript hinzugefügt.

Die hier gegebenen Antworten funktionierten nur teilweise für mich; Viele große Dateien blieben im Cache. Was endlich funktioniert (nach Stunden in #git auf freenode):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

Bei den vorherigen Lösungen lag die Größe des Repositorys bei etwa 100 MB. Dieser brachte es auf 1,7 MB herunter. Vielleicht hilft es jemandem :)


Das folgende Bash-Skript automatisiert die Aufgabe:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

38
2017-08-20 14:11



Dies ist nicht mehr so ​​komplex, Sie können einfach die verwenden Git Filter-Zweig Befehl auf einen Klon von Ihnen Repo, um die Unterverzeichnisse, die Sie nicht wollen, und dann drücken Sie auf die neue Fernbedienung.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

21
2018-03-22 20:55



Aktualisieren: Das git-subtree-Modul war so nützlich, dass das git-Team es in den Kern gezogen hat und es geschafft hat git subtree. Siehe hier: Trennen Sie das Unterverzeichnis in ein separates Git-Repository

git-subtree kann dafür nützlich sein

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (veraltet)

http://pionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/


19
2017-08-06 15:26