Trennen Sie das Unterverzeichnis in ein separates Git-Repository


1758

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

Wie kann ich dies tun, während der Verlauf der Dateien im Unterverzeichnis gespeichert wird?

Ich denke, ich könnte einen Klon erstellen und die unerwünschten Teile jedes Klons entfernen, aber ich nehme an, dies würde mir beim Auschecken einer älteren Revision usw. den vollständigen Baum geben. Dies könnte akzeptabel sein, aber ich würde es vorziehen, so tun zu können, als ob der Zwei Repositorys haben keinen gemeinsamen Verlauf.

Um es klar zu machen, habe ich die folgende Struktur:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Aber ich möchte stattdessen Folgendes:

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

7
Dies ist jetzt trivial, git filter-branchsiehe meine Antwort unten.
Jeremyjjbrown

8
@jeremyjjbrown ist richtig. Dies ist nicht mehr schwierig, aber es ist schwierig, bei Google die richtige Antwort zu finden, da alle alten Antworten die Ergebnisse dominieren.
Agnel Kurian

Antworten:


1228

Update : Dieser Prozess ist so häufig, dass das Git-Team ihn mit einem neuen Tool viel einfacher gemacht hat git subtree. Siehe hier: Unterverzeichnis (Verschieben) in ein separates Git-Repository trennen


Sie möchten Ihr Repository klonen und dann git filter-branchalles außer dem Unterverzeichnis markieren, das in Ihrem neuen Repo durch Müll gesammelt werden soll.

  1. So klonen Sie Ihr lokales Repository:

    git clone /XYZ /ABC
    

    (Hinweis: Das Repository wird mithilfe von Hardlinks geklont. Dies ist jedoch kein Problem, da die fest verknüpften Dateien nicht selbst geändert werden - neue werden erstellt.)

  2. Lassen Sie uns nun die interessanten Zweige beibehalten, die wir ebenfalls neu schreiben möchten, und dann den Ursprung entfernen, um ein Verschieben zu vermeiden und sicherzustellen, dass alte Commits nicht vom 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 Filialen:

    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 möglicherweise auch Tags entfernen, die keine Beziehung zum Teilprojekt haben. Sie können dies auch später tun, müssen Ihr Repo jedoch möglicherweise erneut beschneiden. Ich habe dies nicht getan und ein WARNING: Ref 'refs/tags/v0.1' is unchangedfür alle Tags erhalten (da sie alle nichts mit dem Teilprojekt zu tun hatten ). Darüber hinaus wird nach dem Entfernen solcher Tags mehr Speicherplatz zurückgefordert. Anscheinend git filter-branchsollte es möglich sein, andere Tags umzuschreiben, aber ich konnte dies nicht überprüfen. 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. Fügen wir außerdem hinzu --tag-name-filter cat --prune-empty, um leere Commits zu entfernen und Tags neu zu schreiben (beachten Sie, dass hierdurch die Signatur entfernt werden muss):

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

    oder alternativ, um nur den HEAD-Zweig neu zu schreiben und Tags und andere Zweige zu 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 der Vorgang 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, dessen gesamter Verlauf erhalten bleibt.

Hinweis: Für die meisten Anwendungen git filter-branchsollte tatsächlich der hinzugefügte Parameter vorhanden sein -- --all. Ja das ist wirklich so --space-- all. Dies müssen die letzten Parameter für den Befehl sein. Wie Matli herausfand, bleiben die Projektzweige und -tags im neuen Repo enthalten.

Bearbeiten: Verschiedene Vorschläge aus den Kommentaren unten wurden aufgenommen, um beispielsweise sicherzustellen, dass das Repository tatsächlich verkleinert wird (was vorher nicht immer der Fall war).


29
Sehr gute Antwort. Vielen Dank! Und um wirklich genau das zu bekommen, was ich wollte, habe ich dem Befehl filter-branch "- --all" hinzugefügt.
Matli

12
Warum brauchen Sie --no-hardlinks? Das Entfernen eines Hardlinks wirkt sich nicht auf die andere Datei aus. Git-Objekte sind ebenfalls unveränderlich. Nur wenn Sie die Eigentümer- / Dateiberechtigungen ändern möchten, die Sie benötigen --no-hardlinks.
vdboor

67
Ein zusätzlicher Schritt, den ich empfehlen würde, wäre "git remote rm origin". Dies würde verhindern, dass Pushs zum ursprünglichen Repository zurückkehren, wenn ich mich nicht irre.
Tom

13
Ein weiterer Befehl zum Anhängen filter-branchist --prune-emptydas Entfernen jetzt leerer Commits.
Seth Johnson

8
Wie Paul wollte ich keine Projekt-Tags in meinem neuen Repo, also habe ich sie nicht verwendet -- --all. Ich lief git remote rm originauch und git tag -l | xargs git tag -dvor dem git filter-branchBefehl. Dadurch wurde mein .gitVerzeichnis von 60 Millionen auf ~ 300 KB verkleinert . Beachten Sie, dass ich beide Befehle ausführen musste, um die Größenreduzierung zu erhalten.
Salycrane

1321

Der einfache Weg ™

Es stellt sich heraus, dass dies eine so verbreitete und nützliche Praxis ist, dass die Overlords von Git es wirklich einfach gemacht haben, aber Sie müssen eine neuere Version von Git haben (> = 1.7.11 Mai 2012). Im Anhang erfahren Sie, wie Sie das neueste Git installieren. Außerdem finden Sie in der folgenden exemplarischen Vorgehensweise ein Beispiel aus der Praxis .

  1. Bereiten Sie das alte Repo vor

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

    Hinweis: <name-of-folder> darf KEINE führenden oder nachfolgenden Zeichen enthalten. Beispielsweise muss der Ordner mit dem Namen subprojectMUSS als subprojectNICHT übergeben werden./subproject/

    Hinweis für Windows-Benutzer: Wenn Ihre Ordnertiefe> 1 ist, <name-of-folder>muss ein Ordnertrennzeichen (/) im * nix-Stil vorhanden sein. Beispielsweise muss der Ordner mit dem Namen path1\path2\subprojectMUSS als übergeben werdenpath1/path2/subproject

  2. Erstellen Sie das neue Repo

    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Verknüpfe das neue Repo mit GitHub oder wo auch immer

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

    git rm -rf <name-of-folder>
    

    Hinweis : Dadurch bleiben alle historischen Referenzen im Repository Lesen Sie Anhang, wenn Sie tatsächlich Bedenken haben, ein Kennwort festgeschrieben zu haben, oder wenn Sie die Dateigröße Ihres .gitOrdners verringern müssen .

...

Exemplarische Vorgehensweise

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

Hier ist ein Projekt zur Implementierung von JavaScript-Browsermodulen in Node:

tree ~/node-browser-compat

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

Ich möchte einen einzelnen Ordner btoain ein separates Git-Repository aufteilen

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

Ich habe jetzt einen neuen Zweig, für den btoa-onlynur Commits vorhanden sind, btoaund ich möchte ein neues Repository erstellen.

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/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 als das hinzu origin

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

Glücklicher Tag!

Hinweis: Wenn Sie ein Repo mit einem erstellt README.md, .gitignoreund LICENSE, müssen Sie zuerst ziehen:

git pull origin master
git push origin master

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

git rm -rf btoa

...

Blinddarm

Neueste Git unter macOS

So erhalten Sie die neueste Version von Git mit Homebrew :

brew install git

Neueste 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

Durch das Entfernen von Dateien aus Git werden diese standardmäßig nicht entfernt, sondern nur festgeschrieben, dass sie nicht mehr vorhanden sind. Wenn Sie die historischen Referenzen tatsächlich entfernen möchten (dh Sie haben ein Kennwort festgeschrieben), 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

Sie können jedoch keine Löschvorgänge auf GitHub und dergleichen "pushen" . Wenn Sie es versuchen, erhalten Sie eine Fehlermeldung und müssen dies tun, git pullbevor Sie könnengit push tun - und dann haben Sie wieder alles in Ihrer Geschichte.

Wenn Sie also den Verlauf aus dem "Ursprung" löschen möchten, dh aus GitHub, Bitbucket usw. löschen möchten, müssen Sie das Repo löschen und eine beschnittene Kopie des Repos erneut senden. Aber warte - es gibt noch mehr ! - Wenn Sie wirklich Bedenken haben, ein Passwort oder ähnliches loszuwerden, müssen Sie das Backup beschneiden (siehe unten).

Herstellung .gitkleiner

Der oben erwähnte Befehl zum Löschen des Verlaufs hinterlässt immer noch eine Reihe von Sicherungsdateien - weil Git Ihnen nur allzu freundlich hilft, Ihr Repo nicht versehentlich zu ruinieren. Im Laufe der Tage und Monate werden verwaiste Dateien gelöscht, aber sie bleiben für eine Weile dort, falls Sie feststellen, dass Sie versehentlich etwas gelöscht haben, das Sie nicht wollten.

Wenn Sie also den Papierkorb wirklich leeren möchten, um die Klongröße eines Repos sofort zu verringern , müssen Sie all diese wirklich seltsamen Dinge tun:

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

Trotzdem würde ich empfehlen, diese Schritte nur auszuführen, wenn Sie wissen, dass Sie dies tun müssen - nur für den Fall, dass Sie das falsche Unterverzeichnis beschnitten haben, wissen Sie? Die Sicherungsdateien sollten nicht geklont werden, wenn Sie das Repo pushen. Sie befinden sich nur in Ihrer lokalen Kopie.

Anerkennung


16
git subtreeist immer noch Teil des Ordners 'Contrib' und wird nicht standardmäßig in allen Distributionen installiert. github.com/git/git/blob/master/contrib/subtree
Zwiebeljake

11
@krlmlr 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 Zum Aktivieren unter Ubuntu 13.04
rui.araujo

41
Wenn Sie ein Passwort in ein öffentliches Repository verschoben haben, sollten Sie das Passwort ändern, nicht versuchen, es aus dem öffentlichen Repo zu entfernen, und hoffen, dass niemand es gesehen hat.
Miles Rout

8
Diese Lösung bewahrt die Geschichte nicht.
Cœur

18
Der Befehl popdund pushdmacht dies ziemlich implizit und schwieriger zu verstehen, was es zu tun beabsichtigt ...
jones77

133

Pauls Antwort erstellt ein neues Repository, das / ABC enthält, entfernt jedoch / ABC nicht aus / XYZ. Der folgende Befehl entfernt / ABC aus / XYZ:

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

Testen Sie es natürlich zuerst in einem 'clone --no-hardlinks'-Repository und folgen Sie ihm mit den von Paul aufgelisteten Befehlen reset, gc und prune.


53
machen Sie das git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADund es wird viel schneller sein. Der Indexfilter arbeitet mit dem Index, während der Baumfilter für jedes Commit alles auschecken und bereitstellen muss .
fmarc

51
In einigen Fällen ist es übertrieben, die Geschichte des Repository XYZ durcheinander zu bringen ... nur ein einfaches "rm -rf ABC; git rm -r ABC; git commit -m'extrahiertes ABC in sein eigenes Repo" würde für die meisten Menschen besser funktionieren.
Evgeny

2
Sie möchten wahrscheinlich -f (force) für diesen Befehl verwenden, wenn Sie dies mehrmals tun, z. B. um zwei Verzeichnisse zu entfernen, nachdem sie getrennt wurden. Andernfalls wird angezeigt, dass kein neues Backup erstellt werden kann.
Brian Carlton

4
Wenn Sie die --index-filterMethode ausführen, möchten Sie dies möglicherweise auch tun git rm -q -r -f, damit bei jedem Aufruf nicht für jede gelöschte Datei eine Zeile gedruckt wird.
Eric Naeseth

1
Ich würde vorschlagen, Pauls Antwort zu bearbeiten, nur weil Pauls so gründlich ist.
Erik Aronesty

96

Ich habe festgestellt, dass Sie nach dem filter-branchSchritt etwas mehr Arbeit leisten müssen, um den alten Verlauf ordnungsgemäß aus dem neuen Repository zu löschen .

  1. Machen Sie 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 Geschichte. "Origin" verfolgte Ihren Klon und "original" ist der Ort, an dem Filter-Branch das alte Zeug speichert:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Selbst jetzt steckt Ihr Verlauf möglicherweise in einer Packdatei, die fsck nicht berührt. Zerreißen Sie es in Stücke, erstellen Sie eine neue Packdatei und löschen Sie die nicht verwendeten Objekte:

    git repack -ad
    

Eine Erklärung hierzu finden Sie im Handbuch für Filterzweige .


3
Ich denke, etwas wie git gc --aggressive --prune=nowfehlt noch, nicht wahr?
Albert

1
@Albert Der Repack-Befehl kümmert sich darum, und es würde keine losen Gegenstände geben.
Josh Lee

Ja, git gc --aggressive --prune=nowviel neues Repo reduziert
Tomek Wyderka

Einfach und elegant. Vielen Dank!
Marco Pelegrini

40

Bearbeiten: Bash-Skript hinzugefügt.

Die hier gegebenen Antworten haben nur teilweise für mich funktioniert; Viele große Dateien blieben im Cache. Was hat 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 betrug die Repository-Größe etwa 100 MB. Dieser brachte es auf 1,7 MB. 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

26

Dies ist nicht mehr so ​​komplex. Sie können einfach den Befehl git filter-branch für einen Klon Ihres Repos verwenden, um die nicht gewünschten Unterverzeichnisse auszusortieren und dann auf die neue Fernbedienung zu übertragen.

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

3
Das hat wie ein Zauber gewirkt. YOUR_SUBDIR im obigen Beispiel ist das Unterverzeichnis, das Sie behalten möchten. Alles andere wird entfernt
JT Taylor

1
Aktualisierungen basierend auf Ihrem Kommentar.
Jeremyjjbrown

2
Dies beantwortet die Frage nicht. Aus den Dokumenten geht hervor, dass dies The result will contain that directory (and only that) as its project root.tatsächlich der Fall ist, dh die ursprüngliche Projektstruktur bleibt nicht erhalten.
NicBright

2
@NicBright Können Sie Ihr Problem mit XYZ und ABC wie in der Frage veranschaulichen, um zu zeigen, was falsch ist?
Adam

@jeremyjjbrown ist es möglich, das geklonte Repo wiederzuverwenden und kein neues Repo zu verwenden, dh meine Frage hier stackoverflow.com/questions/49269602/…
Qiulang

19

Update : Das Git-Teilbaum-Modul war so nützlich, dass das Git-Team es in den Kern gezogen und erstellt hat git subtree. Siehe hier: Unterverzeichnis (Verschieben) in ein separates Git-Repository trennen

git-subtree kann hierfür nützlich sein

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

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


1
git-subtree ist jetzt Teil von Git, obwohl es sich im Contrib-Baum befindet und daher nicht immer standardmäßig installiert ist. Ich weiß, dass es von der Homebrew-Git-Formel installiert wird, aber ohne Manpage. apenwarr nennt seine version daher obsolet.
Echristopherson

19

Hier ist eine kleine Änderung an CoolAJ86 ‚s ‚The Easy Way ™‘Antwort zu spalten , um mehrere Unterordner (sagen wir mal sub1und sub2) in eine neue git - Repository.

The Easy Way ™ (mehrere Unterordner)

  1. Bereiten Sie das alte Repo vor

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Hinweis: <name-of-folder> darf KEINE führenden oder nachfolgenden Zeichen enthalten. Beispielsweise muss der Ordner mit dem Namen subprojectMUSS als subprojectNICHT übergeben werden./subproject/

    Hinweis für Windows-Benutzer: Wenn Ihre Ordnertiefe> 1 ist, <name-of-folder>muss das Ordnertrennzeichen (/) im * nix-Stil vorhanden sein. Beispielsweise muss der Ordner mit dem Namen path1\path2\subprojectMUSS als übergeben werden path1/path2/subproject. Verwenden Sie außerdem keinen mvBefehl, abermove .

    Letzte Anmerkung: Der einzigartige und große Unterschied zur Basisantwort ist die zweite Zeile des Skripts " git filter-branch..."

  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. Verbinde 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, falls gewünscht

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

    Hinweis : Dadurch bleiben alle historischen Verweise im Repository erhalten. Lesen Sie den Anhang in der ursprünglichen Antwort, wenn Sie tatsächlich Bedenken haben, ein Kennwort festgeschrieben zu haben, oder wenn Sie die Dateigröße Ihres .gitOrdners verringern müssen .


1
Dies funktionierte bei mir mit geringfügigen Änderungen. Da meine sub1und sub2Ordner mit der ursprünglichen Version nicht vorhanden waren, musste ich mein --tree-filterSkript wie folgt ändern : "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi". Für den zweiten filter-branchBefehl habe ich <sub1> durch <sub2> ersetzt, die Erstellung von <name-of-folder> weggelassen und -fdanach eingefügt, filter-branchum die Warnung einer vorhandenen Sicherung zu überschreiben.
Pglezen

Dies funktioniert nicht, wenn sich eines der Unterverzeichnisse während des Verlaufs in git geändert hat. Wie kann das gelöst werden?
Nietras

@nietras siehe Rogerdpacks Antwort. Ich habe eine Weile gebraucht, um es zu finden, nachdem ich alle Informationen in diesen anderen Antworten gelesen und aufgenommen hatte.
Adam

12

Die ursprüngliche Frage möchte, dass aus XYZ / ABC / (* -Dateien) ABC / ABC / (* -Dateien) werden. Nachdem ich die akzeptierte Antwort für meinen eigenen Code implementiert hatte, stellte ich fest, dass XYZ / ABC / (* Dateien) tatsächlich in ABC / (* Dateien) geändert wird. Die Filter-Zweig-Manpage sagt sogar:

Das Ergebnis enthält dieses Verzeichnis (und nur das) als Projektstamm . "

Mit anderen Worten, der Ordner der obersten Ebene wird um eine Ebene "nach oben" befördert. Das ist eine wichtige Unterscheidung, weil ich beispielsweise in meiner Geschichte einen Ordner der obersten Ebene umbenannt hatte. Durch das Heraufstufen von Ordnern auf einer Ebene verliert git die Kontinuität bei dem Commit, bei dem ich die Umbenennung vorgenommen habe.

Ich habe nach dem Filterzweig die Kontinuität verloren

Meine Antwort auf die Frage lautet dann, 2 Kopien des Repositorys zu erstellen und die Ordner, die Sie in jedem behalten möchten, manuell zu löschen. Die Manpage unterstützt mich damit:

[...] Vermeiden Sie die Verwendung von [diesem Befehl], wenn ein einfaches Commit ausreichen würde, um Ihr Problem zu beheben


1
Ich mag den Stil dieses Diagramms. Darf ich fragen, welches Tool Sie verwenden?
Slipp D. Thompson

3
Tower für Mac. Ich mag es wirklich. Es lohnt sich fast, für sich selbst auf den Mac zu wechseln.
MM.

2
Ja, obwohl in meinem Fall meine subfoldered targetdirhatte umbenannt irgendwann und git filter-brancheinfach es einen Tag genannt, löscht alle Commits vor dem Umbenennungs gemacht! Schockierend, wenn man bedenkt, wie geschickt Git darin ist, solche Dinge zu verfolgen und sogar einzelne Inhaltsblöcke zu migrieren!
Jay Allen

1
Oh, auch wenn sich jemand im selben Boot befindet, hier ist der Befehl, den ich verwendet habe. Vergessen Sie nicht, dass dies git rmmehrere Argumente erfordert, sodass es keinen Grund gibt, es für jede Datei / jeden Ordner auszuführen: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
Jay Allen,

7

Um Pauls Antwort zu ergänzen, stellte ich fest, dass ich HEAD in ein sauberes Repository verschieben muss, um letztendlich Speicherplatz wiederherzustellen, und dass dadurch die Größe des Verzeichnisses .git / objects / pack verringert wird.

dh

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init --bare

Machen Sie nach dem GC-Schnitt auch:

$ git push ... ABC.git HEAD

Dann können Sie tun

$ git Klon ... ABC.git

und die Größe von ABC / .git wird reduziert

Tatsächlich sind einige der zeitaufwändigen Schritte (z. B. git gc) beim Push-to-Clean-Repository nicht erforderlich, z.

$ git clone --no-hardlinks / XYZ / ABC
$ git filter-branch - Unterverzeichnisfilter ABC HEAD
$ git reset --hard
$ git push ... ABC.git HEAD

6

Der richtige Weg ist jetzt der folgende:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub hat jetzt sogar einen kleinen Artikel über solche Fälle.

Stellen Sie jedoch sicher, dass Sie Ihr ursprüngliches Repo zuerst in ein separates Verzeichnis klonen (da dadurch alle Dateien und anderen Verzeichnisse gelöscht werden und Sie wahrscheinlich damit arbeiten müssen).

Ihr Algorithmus sollte also sein:

  1. Klonen Sie Ihr Remote-Repo in ein anderes Verzeichnis
  2. Wenn Sie git filter-branchnur linke Dateien in einem Unterverzeichnis verwenden, drücken Sie auf eine neue Fernbedienung
  3. Erstellen Sie ein Commit, um dieses Unterverzeichnis aus Ihrem ursprünglichen Remote-Repo zu entfernen

6

Es scheint, dass die meisten (alle?) Antworten hier auf irgendeiner Form git filter-branch --subdirectory-filterund ihrer Art beruhen. Dies kann jedoch "meistens" in einigen Fällen funktionieren, z. B. wenn Sie den Ordner umbenannt haben, z.

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

Wenn Sie einen normalen Git-Filterstil verwenden, um "move_me_renamed" zu extrahieren, verlieren Sie den Änderungsverlauf der Datei, der von Anfang an aufgetreten ist, als es ursprünglich move_this_dir war ( ref ) war.

Es scheint also, dass die einzige Möglichkeit, den gesamten Änderungsverlauf wirklich beizubehalten (wenn Ihr Fall so ist), im Wesentlichen darin besteht, das Repository zu kopieren (ein neues Repo zu erstellen, das als Ursprung festzulegen) und dann alles andere zu zerstören und benennen Sie das Unterverzeichnis wie folgt in das übergeordnete Verzeichnis um:

  1. Klonen Sie das Multi-Modul-Projekt lokal
  2. Zweige - überprüfen Sie, was da ist: git branch -a
  3. Führen Sie eine Prüfung für jeden Zweig durch, der in die Aufteilung einbezogen werden soll, um eine lokale Kopie auf Ihrer Workstation zu erhalten: git checkout --track origin/branchABC
  4. Erstellen Sie eine Kopie in einem neuen Verzeichnis: cp -r oldmultimod simple
  5. Gehen Sie in die neue Projektkopie: cd simple
  6. Entfernen Sie die anderen Module, die in diesem Projekt nicht benötigt werden:
  7. git rm otherModule1 other2 other3
  8. Jetzt bleibt nur das Unterverzeichnis des Zielmoduls übrig
  9. Entfernen Sie das Modul-Unterverzeichnis, sodass das Modulstammverzeichnis zum neuen Projektstammverzeichnis wird
  10. git mv moduleSubdir1/* .
  11. Löschen Sie das Relikt-Unterverzeichnis: rmdir moduleSubdir1
  12. Überprüfen Sie die Änderungen jederzeit: git status
  13. Erstellen Sie das neue Git-Repo und kopieren Sie seine URL, um dieses Projekt darauf zu verweisen:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. Stellen Sie sicher, dass dies gut ist: git remote -v
  16. Schieben Sie die Änderungen auf das Remote-Repo: git push
  17. Gehen Sie zum Remote-Repo und überprüfen Sie, ob alles vorhanden ist
  18. Wiederholen Sie diesen Vorgang für alle anderen benötigten Zweige: git checkout branch2

Dies folgt dem Github-Dokument "Aufteilen eines Unterordners in ein neues Repository" in den Schritten 6 bis 11, um das Modul in ein neues Repo zu verschieben.

Dadurch sparen Sie keinen Speicherplatz in Ihrem .git-Ordner, aber der gesamte Änderungsverlauf für diese Dateien bleibt auch über Umbenennungen hinweg erhalten. Und das ist es vielleicht nicht wert, wenn nicht "viel" Geschichte verloren geht usw. Aber zumindest werden Sie garantiert keine älteren Commits verlieren!


1
Fand die Nadel im Heuhaufen! Jetzt kann ich ALLE meine Commit-Historie behalten .
Adam

5

Ich empfehle GitHubs Handbuch zum Aufteilen von Unterordnern in ein neues Repository . Die Schritte ähneln der Antwort von Paulus , aber ich fand ihre Anweisungen leichter zu verstehen.

Ich habe die Anweisungen so geändert, dass sie für ein lokales Repository gelten und nicht für ein auf GitHub gehostetes.


Aufteilen eines Unterordners in ein neues Repository

  1. Öffnen Sie Git Bash.

  2. Ändern Sie das aktuelle Arbeitsverzeichnis in den Speicherort, an dem Sie Ihr neues Repository erstellen möchten.

  3. Klonen Sie das Repository, das den Unterordner enthält.

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. Ändern Sie das aktuelle Arbeitsverzeichnis in Ihr geklontes Repository.

cd REPOSITORY-NAME
  1. Führen Sie Folgendes aus, um den Unterordner aus den übrigen Dateien im Repository herauszufiltern git filter-branch, und geben Sie folgende Informationen ein:
    • FOLDER-NAME: Der Ordner in Ihrem Projekt, aus dem Sie ein separates Repository erstellen möchten.
      • Tipp: Windows-Benutzer sollten /Ordner abgrenzen.
    • BRANCH-NAME: Der Standardzweig für Ihr aktuelles Projekt, masteroder gh-pages.

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten

Netter Beitrag, aber ich stelle fest, dass der erste Absatz des Dokuments, das Sie verlinkt haben, besagt: If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.Laut Kommentaren zu allen Antworten hier führen sowohl filter-branchdas subtreeSkript als auch das Skript zum Verlust der Historie, wo immer ein Unterverzeichnis umbenannt wurde. Gibt es etwas, das getan werden kann, um dies zu beheben?
Adam

Es wurde die Lösung gefunden, um alle Commits beizubehalten, einschließlich der vorhergehenden Umbenennungen / Verschiebungen des Verzeichnisses - es ist die Antwort von rogerdpack auf genau diese Frage.
Adam

Das einzige Problem ist, dass ich das geklonte Repo nicht mehr verwenden kann
Qiulang

5

Wenn Sie git filter-branchmit einer neueren Version von git( 2.22+vielleicht?) Ausführen , sollten Sie dieses neue Tool git-filter-repo verwenden . Dieses Tool hat mir sicherlich die Dinge vereinfacht.

Filtern mit Filter-Repo

Befehle zum Erstellen des XYZRepos aus der ursprünglichen Frage:

# create local clone of original repo in directory XYZ
tmp $ git clone git@github.com:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin git@github.com:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

Annahmen: * Remote-XYZ-Repo war vor dem Push neu und leer

Filtern und Bewegen

In meinem Fall wollte ich auch einige Verzeichnisse verschieben, um eine konsistentere Struktur zu erhalten. Anfangs habe ich diesen einfachen filter-repoBefehl ausgeführt, gefolgt von git mv dir-to-rename, aber ich habe festgestellt, dass ich mit dieser --path-renameOption einen etwas "besseren" Verlauf erhalten kann . Anstatt zuletzt geänderte 5 hours agoDateien im neuen Repo zu sehen, sehe ich jetzt last year(in der GitHub-Benutzeroberfläche), was den geänderten Zeiten im ursprünglichen Repo entspricht.

Anstatt...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

Ich bin letztendlich gelaufen ...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Anmerkungen:
  • Ich dachte, der Blog-Beitrag von Git Rev News erklärt gut die Gründe für die Erstellung eines weiteren Repo-Filter-Tools.
  • Ich habe zunächst versucht, ein Unterverzeichnis zu erstellen, das mit dem Ziel-Repo-Namen im ursprünglichen Repository übereinstimmt, und dann zu filtern (mit git filter-repo --subdirectory-filter dir-matching-new-repo-name). Dieser Befehl konvertierte dieses Unterverzeichnis korrekt in das Stammverzeichnis des kopierten lokalen Repos, führte jedoch auch zu einem Verlauf nur der drei Commits, die zum Erstellen des Unterverzeichnisses erforderlich waren. (Ich hatte nicht --pathbemerkt , dass dies mehrmals angegeben werden konnte, wodurch die Notwendigkeit entfiel, ein Unterverzeichnis im Quell-Repo zu erstellen.) Da sich jemand zum Quell-Repo verpflichtet hatte, als ich bemerkte, dass ich das nicht übertragen hatte Verlauf, den ich gerade git reset commit-before-subdir-move --hardnach dem cloneBefehl verwendet und dem Befehl hinzugefügt --forcehabe filter-repo, damit er auf dem leicht modifizierten lokalen Klon ausgeführt werden kann.
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • Ich war bei der Installation ratlos, da ich das Erweiterungsmuster nicht kannte git, aber letztendlich habe ich Git-Filter-Repo geklont und es mit $(git --exec-path)folgendem Link verknüpft :
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)

1
Upvoted für die Empfehlung des neuen filter-repoTools (das ich letzten Monat in stackoverflow.com/a/58251653/6309 vorgestellt habe )
VonC

Die Verwendung git-filter-reposollte an dieser Stelle definitiv der bevorzugte Ansatz sein. Es ist viel, viel schneller und sicherer als git-filter-branchund schützt vor vielen Fallstricken, auf die man stoßen kann, wenn man seine Git-Geschichte neu schreibt. Hoffentlich bekommt diese Antwort etwas mehr Aufmerksamkeit, da sie angesprochen werden muss git-filter-repo.
Jeremy Caney

4

Ich hatte genau dieses Problem, aber alle Standardlösungen, die auf Git Filter-Branch basierten, waren extrem langsam. Wenn Sie ein kleines Repository haben, ist dies möglicherweise kein Problem, es war für mich. Ich habe ein anderes Git-Filterprogramm geschrieben, das auf libgit2 basiert und in einem ersten Schritt Zweige für jede Filterung des primären Repositorys erstellt und diese dann als nächsten Schritt an saubere Repositorys weiterleitet. In meinem Repository (500 MB 100000 Commits) dauerten die Standardmethoden für Git-Filter-Verzweigungen Tage. Mein Programm benötigt Minuten, um dieselbe Filterung durchzuführen.

Es hat den fabelhaften Namen git_filter und lebt hier:

https://github.com/slobobaby/git_filter

auf GitHub.

Ich hoffe es ist nützlich für jemanden.


4

Verwenden Sie diesen Filterbefehl, um ein Unterverzeichnis zu entfernen und dabei Ihre Tags und Zweige beizubehalten:

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all

Was ist Katze hier?
Rogerdpack

4

Hier erfahren Sie, wie Sie GitHub auf einem Windows-Computer verwenden. Angenommen, Sie haben ein geklontes Repo, in dem Sie wohnen C:\dir1. Die Verzeichnisstruktur sieht folgendermaßen aus : C:\dir1\dir2\dir3. Das dir3Verzeichnis ist das, für das ich ein neues separates Repo sein möchte.

Github:

  1. Erstellen Sie Ihr neues Repository: MyTeam/mynewrepo

Bash Prompt:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    Ref 'refs/heads/master' was rewrittenRückgabe : (fyi: dir2 / dir3 unterscheidet zwischen Groß- und Kleinschreibung.)

  3. $ git remote add some_name git@github.com:MyTeam/mynewrepo.git
    git remote add origin etc. hat nicht funktioniert, zurückgegeben " remote origin already exists"

  4. $ git push --progress some_name master


3

Wie oben erwähnt , musste ich die umgekehrte Lösung verwenden (Löschen aller Commits, die meine nicht berühren dir/subdir/targetdir), die ziemlich gut zu funktionieren schien, wobei etwa 95% der Commits entfernt wurden (wie gewünscht). Es gibt jedoch noch zwei kleine Probleme.

ZUERST , filter-branchhat es geschafft, Commits zu entfernen, die Code einführen oder ändern, aber anscheinend befinden sich Merge-Commits unter seiner Station im Gitiverse.

Dies ist ein kosmetisches Problem, mit dem ich wahrscheinlich leben kann (er sagt ... sich langsam mit abgewandten Augen zurückziehen) .

ZWEITENS sind die wenigen verbleibenden Commits so ziemlich ALLE dupliziert! Ich habe anscheinend eine zweite, redundante Zeitleiste erhalten, die fast die gesamte Geschichte des Projekts abdeckt. Das Interessante (was Sie auf dem Bild unten sehen können) ist, dass meine drei lokalen Niederlassungen nicht alle auf derselben Zeitachse liegen (weshalb es sicherlich existiert und nicht nur Müll gesammelt wird).

Das einzige, was ich mir vorstellen kann, ist, dass eines der gelöschten Commits möglicherweise das einzelne Merge-Commit war, das filter-branch tatsächlich gelöscht wurde , und das die parallele Zeitachse erstellt hat, als jeder jetzt nicht zusammengeführte Strang seine eigene Kopie der Commits erstellt hat. ( Achselzucken Wo sind meine TARDiS?) Ich bin mir ziemlich sicher, dass ich dieses Problem beheben kann, obwohl ich wirklich gerne verstehen würde, wie es passiert ist.

Im Fall von verrücktem Mergefest-O-RAMA werde ich dieses wahrscheinlich in Ruhe lassen, da es sich so fest in meiner Commit-Geschichte verankert hat - bedrohlich für mich, wenn ich in die Nähe komme -, dass es nicht wirklich zu verursachen scheint alle nicht-kosmetischen Probleme und weil es in Tower.app ziemlich hübsch ist.


3

Der einfachere Weg

  1. installieren git splits. Ich habe es als Git-Erweiterung erstellt, basierend auf der Lösung von jkeating .
  2. Teilen Sie die Verzeichnisse in einen lokalen Zweig auf #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ XY1 XY2

  3. Erstellen Sie irgendwo ein leeres Repo. Wir gehen davon aus, dass wir xyzauf GitHub ein leeres Repo mit dem folgenden Pfad erstellt haben:git@github.com:simpliwp/xyz.git

  4. Zum neuen Repo drücken. #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. Klonen Sie das neu erstellte Remote-Repo in ein neues lokales Verzeichnis
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git


Ein Vorteil dieser Methode gegenüber "The Easy Way" besteht darin, dass die Fernbedienung bereits für das neue Repo eingerichtet ist, sodass Sie sofort einen Teilbaum hinzufügen können. Tatsächlich scheint mir dieser Weg einfacher zu sein (auch ohne git splits)
MM

Props an AndrewD für die Veröffentlichung dieser Lösung. Ich habe sein Repo gegabelt, damit es unter OSX ( github.com/ricardoespsanto/git-splits ) funktioniert, wenn das für andere nützlich ist
Ricardoespsanto

2

Möglicherweise benötigen Sie vor der Garbage Collection etwas wie "git reflog expire --expire = now --all", um die Dateien tatsächlich zu bereinigen. git filter-branch entfernt nur Verweise im Verlauf, entfernt jedoch nicht die Reflog-Einträge, die die Daten enthalten. Testen Sie dies natürlich zuerst.

Meine Festplattennutzung ging dabei dramatisch zurück, obwohl meine Anfangsbedingungen etwas anders waren. Vielleicht negiert --subdirectory-filter dieses Bedürfnis, aber ich bezweifle es.


2

Schauen Sie sich das git_split-Projekt unter https://github.com/vangorra/git_split an

Verwandeln Sie Git-Verzeichnisse in eigene Repositorys an ihrem eigenen Standort. Kein Teilbaum lustiges Geschäft. Dieses Skript nimmt ein vorhandenes Verzeichnis in Ihrem Git-Repository und verwandelt dieses Verzeichnis in ein eigenständiges Repository. Unterwegs wird der gesamte Änderungsverlauf für das von Ihnen angegebene Verzeichnis kopiert.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.

1

Fügen Sie dies in Ihre gitconfig ein:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'

1

Ich bin mir sicher, dass der Git-Teilbaum in Ordnung und wunderbar ist, aber meine Unterverzeichnisse mit git-verwaltetem Code, den ich verschieben wollte, waren alle in Eclipse. Wenn Sie also egit verwenden, ist es schmerzlich einfach. Nehmen Sie das Projekt, das Sie verschieben möchten, und teilen Sie es mit einem Team-> Trennen und anschließend mit einem Team-> am neuen Standort. Standardmäßig wird versucht, den alten Repo-Speicherort zu verwenden. Sie können jedoch die Auswahl für die Verwendung deaktivieren und den neuen Speicherort auswählen. Alle begrüßen egit.


3
Der "schöne und wundervolle" Teil des Teilbaums ist, dass die Geschichte Ihres Unterverzeichnisses für die Fahrt mitkommt. Wenn Sie die Geschichte nicht brauchen, ist Ihre schmerzlich einfache Methode der richtige Weg.
Pglezen

0

Sie können ganz einfach das https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/ ausprobieren.

Das hat bei mir funktioniert. Die Probleme, mit denen ich in den oben angegebenen Schritten konfrontiert war, sind

  1. In diesem Befehl ist git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME The BRANCH-NAMEis master

  2. Wenn der letzte Schritt beim Festschreiben aufgrund eines Schutzproblems fehlschlägt, gehen Sie wie folgt vor: https://docs.gitlab.com/ee/user/project/protected_branches.html


0

Ich habe eine recht einfache Lösung gefunden. Die Idee ist, das Repository zu kopieren und dann einfach unnötige Teile zu entfernen. So funktioniert es:

1) Klonen Sie ein Repository, das Sie teilen möchten

git clone git@git.thehost.io:testrepo/test.git

2) Wechseln Sie in den Git-Ordner

cd test/

2) Entfernen Sie unnötige Ordner und schreiben Sie sie fest

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) Entfernen Sie mit BFG nicht benötigte Ordner aus dem Verlauf

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

Für mehrere Ordner können Sie Komma verwenden

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4) Stellen Sie sicher, dass der Verlauf nicht die gerade gelöschten Dateien / Ordner enthält

git log --diff-filter=D --summary | grep delete

5) Jetzt haben Sie ein sauberes Repository ohne ABC, also schieben Sie es einfach in einen neuen Ursprung

remote add origin git@github.com:username/new_repo
git push -u origin master

Das ist es. Sie können die Schritte wiederholen, um ein anderes Repository zu erhalten.

Entfernen Sie einfach XY1, XY2 und benennen Sie XYZ -> ABC in Schritt 3 um


Fast perfekt ... aber Sie haben "git filter-branch --prune-empty" vergessen, um alle alten Commits zu entfernen, die jetzt leer sind. Vor dem Push zum Origin Master!
ZettaCircl

Wenn Sie den Fehler gemacht haben und nach dem Entfernen des alten leeren Commits immer noch "repush" möchten, führen Sie Folgendes aus: "git push -u origin master --force-with-lease"
ZettaCircl
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.