Kombinieren mehrerer Git-Repositorys

207

Nehmen wir an, ich habe ein Setup, das ungefähr so ​​aussieht

phd/code/
phd/figures/
phd/thesis/

Aus historischen Gründen haben diese alle ihre eigenen Git-Repositories. Aber ich möchte sie zu einer einzigen kombinieren, um die Dinge ein wenig zu vereinfachen. Zum Beispiel könnte ich im Moment zwei Änderungssätze vornehmen und muss so etwas tun

cd phd/code
git commit 
cd ../figures
git commit

Es wäre (jetzt) ​​schön, nur aufzutreten

cd phd
git commit

Es scheint verschiedene Möglichkeiten zu geben, dies mit Submodulen zu tun oder aus meinen Sub-Repositorys zu ziehen, aber das ist etwas komplexer, als ich suche. Zumindest würde ich mich freuen

cd phd
git init
git add [[everything that's already in my other repositories]]

aber das scheint kein Einzeiler zu sein. Kann gitmir irgendetwas helfen?

Will Robertson
quelle
Denken Sie
Johan Sjöberg
Beachten
Sie
Das Skript join-git-repos.py leistet gute Arbeit, wenn Sie separate Repositorys mit jeweils Hauptzweigen haben, die Sie kombinieren möchten.
Mark

Antworten:

149

Hier ist eine Lösung, die ich hier gegeben habe :

  1. Machen Sie zuerst eine vollständige Sicherung Ihres Promotionsverzeichnisses: Ich möchte nicht dafür verantwortlich gemacht werden, dass Sie jahrelange harte Arbeit verloren haben! ;-);

    $ cp -r phd phd-backup
    
  2. Verschieben Sie den Inhalt von phd/codenach phd/code/codeund korrigieren Sie den Verlauf so, dass er immer dort war (dies verwendet den Filter-Branch- Befehl von git ):

    $ cd phd/code
    $ git filter-branch --index-filter \
        'git ls-files -s | sed "s#\t#&code/#" |
         GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
         git update-index --index-info &&
         mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
    
  3. Das Gleiche gilt für den Inhalt phd/figuresund phd/thesis(ersetzen Sie einfach codemit figuresund thesis).

    Jetzt sollte Ihre Verzeichnisstruktur folgendermaßen aussehen:

    phd
      |_code
      |    |_.git
      |    |_code
      |         |_(your code...)
      |_figures
      |    |_.git
      |    |_figures
      |         |_(your figures...)
      |_thesis
           |_.git
           |_thesis
                |_(your thesis...)
    
  4. Erstellen Sie dann ein Git-Repository im Stammverzeichnis, ziehen Sie alles hinein und entfernen Sie die alten Repositorys:

    $ cd phd
    $ git init
    
    $ git pull code
    $ rm -rf code/code
    $ rm -rf code/.git
    
    $ git pull figures --allow-unrelated-histories
    $ rm -rf figures/figures
    $ rm -rf figures/.git
    
    $ git pull thesis --allow-unrelated-histories
    $ rm -rf thesis/thesis
    $ rm -rf thesis/.git
    

    Schließlich sollten Sie jetzt haben, was Sie wollten:

    phd
      |_.git
      |_code
      |    |_(your code...)
      |_figures
      |    |_(your figures...)
      |_thesis
           |_(your thesis...)
    

Eine nette Seite dieses Verfahrens ist, dass nicht versionierte Dateien und Verzeichnisse an Ort und Stelle bleiben .

Hoffe das hilft.


Nur ein Wort der Warnung: Wenn Ihr codeVerzeichnis bereits ein codeUnterverzeichnis oder eine Datei enthält, können Probleme auftreten (dasselbe gilt für figuresundthesis natürlich). Wenn dies der Fall ist, benennen Sie das Verzeichnis oder die Datei einfach um, bevor Sie den gesamten Vorgang ausführen:

$ cd phd/code
$ git mv code code-repository-migration
$ git commit -m "preparing the code directory for migration"

Und wenn der Vorgang abgeschlossen ist, fügen Sie diesen letzten Schritt hinzu:

$ cd phd
$ git mv code/code-repository-migration code/code
$ git commit -m "final step for code directory migration"

Wenn das codeUnterverzeichnis oder die Datei nicht versioniert ist, verwenden Sie einfach mvanstelle von git mvund vergessen Sie das git commits.

MiniQuark
quelle
13
Vielen Dank für dieses Snippet - es hat genau das getan, was ich brauchte (nachdem ich festgestellt hatte, dass Mac OS X nicht "\ t" verarbeitet hat (ich musste stattdessen ^ V ^ I verwenden).
Craig Trader
6
Ich konnte dies zunächst nicht zum Laufen bringen und fand schließlich die Lösung für das Problem auf einem anderen alten Message Board. In der letzten Zeile musste ich die Dateinamen wie folgt in Anführungszeichen setzen: mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEADund dann hat es super funktioniert!
Jorin
3
Der Befehl funky filter-branch stammt aus den Manpages von git zum Filter-branch. Sie sollten Folgendes sagen: a) Es sollte korrekt zugeordnet werden. B) Ich werde einen solchen Befehl nicht ausführen, nur weil ihn jemand, selbst mit hohem Ansehen, auf StackOverflow gepostet hat. Ich weiß, dass es von Manpages stammt.
Tymtam
5
ACHTUNG! MacOS X verwendet nicht die GNU-Erweiterung von sed, daher kennt es die Sequenz \ t nicht. Das Ergebnis ist eine durcheinandergebrachte Geschichte! Meine Lösung bestand darin, den Code in eine Skriptdatei einzufügen und ein echtes <TAB> -Zeichen darin zu schreiben. Über das Terminal kann eine Registerkarte eingegeben werden, indem Sie Strg + V drücken und dann ein <TAB> schreiben. Ich habe Craig's Lösung nicht ausprobiert
Gil Vegliach
4
ACHTUNG (2)! Beachten Sie auch, dass der Befehl sed fehlschlägt, wenn einige Dateien oder Verzeichnisse Bindestriche ('-') enthalten. In diesem Fall können Sie es durch etwas wie 's ~ \ t ~ & code / ~' ersetzen.
Achten Sie
75

git-stitch-repoVerarbeitet die Ausgabe der git-fast-export --all --date-orderin der Befehlszeile angegebenen Git-Repositorys und erstellt einen dafür geeigneten Stream. Dadurch git-fast-importwird ein neues Repository erstellt, das alle Commits in einem neuen Commit-Baum enthält, der den Verlauf aller Quell-Repositorys berücksichtigt.

Aristoteles Pagaltzis
quelle
33
Äh, es ist ein Tool von Drittanbietern, das nicht Teil von git ist… :-)
Aristoteles Pagaltzis
1
In der Tat, jetzt sagst du es mir :) Na ja, ich musste wohl eines Tages lernen, wie man CPAN-Pakete installiert…
Will Robertson
1
Vielen Dank, dass Sie auf diesen Befehl hingewiesen haben. Ich habe es nur verwendet, um ein paar Repos von SVN auf Git zu verschieben.
Unterschreiben Sie
1
WARNUNG funktioniert möglicherweise nicht, wenn Sie Zweige / Zusammenführungen haben! Von der Seite git-stich-repo : "git-stich-repo funktioniert perfekt mit Repositorys mit linearem Verlauf (keine Zusammenführungen). Die Verbesserungen des in Version 0.06 hinzugefügten Stitching-Algorithmus sollten für Repositorys mit geeignet sein verzweigt und verschmilzt. "
Bryan P
6
Dies ist ein externes Skript, die Antwort ist zu kurz und nicht wirklich hilfreich. Dieses Skript hat Probleme mit Zusammenführungs-Commits. Nicht viele Leute würden mit Perl oder CPAN umgehen, und dies wird in der Antwort nicht gut erklärt. Also ... -1, sorry.
Haralan Dobrev
20

Vielleicht einfach (ähnlich wie in der vorherigen Antwort, aber mit einfacheren Befehlen) in jedem der separaten alten Repositorys ein Commit erstellen, das den Inhalt in ein entsprechend benanntes Unterverzeichnis verschiebt, z.

$ cd phd/code
$ mkdir code
# This won't work literally, because * would also match the new code/ subdir, but you understand what I mean:
$ git mv * code/
$ git commit -m "preparing the code directory for migration"

und dann die drei separaten Repos zu einem neuen zusammenführen, indem Sie Folgendes tun:

$ cd ../..
$ mkdir phd.all
$ cd phd.all
$ git init
$ git pull ../phd/code
...

Dann speichern Sie Ihre Historien, fahren aber mit einem einzigen Repo fort.

imz - Ivan Zakharyaschev
quelle
Dies ist in Ordnung, aber wenn Sie ein Repo in ein anderes zusammenführen (dh phd war ein nicht leeres, bereits vorhandenes Repo), werden Sie Probleme haben, wenn phd Ordner mit Namen hatte, die mit den Unterordnern im Codeverzeichnis identisch sind, wie 'git pull'. / phd / code 'zieht alle Commits mit den ursprünglichen Pfaden und wendet erst am Ende das mv-Commit an.
Tymtam
1
@Tymek: aber das wird in dieser Situation immer noch ohne Probleme funktionieren. Die Sache, die nicht schön sein wird, ist, dass die Pfade in der Geschichte nicht "korrekt" sind (entsprechen den neuen Pfaden).
imz - Ivan Zakharyaschev
19

Sie können die Strategie zum Zusammenführen von Teilbäumen ausprobieren . Damit können Sie Repo B mit Repo A zusammenführen. Der Vorteil gegenüber git-filter-branchist, dass Sie Ihre Repo A-Historie nicht neu schreiben müssen (SHA1-Summen brechen).

Leif Gruenwoldt
quelle
Der Link funktioniert nicht und dies würde die Geschichte nicht bewahren, oder?
Tymtam
3
@Tymek (Sorry, Teile von kernel.org sind nach der Sicherheitsverletzung immer noch nicht verfügbar). Es bricht SHA1s des eingehenden Repos B. Aber A bleibt intakt.
Leif Gruenwoldt
2
Hier ist ein Spiegel dieses Dokuments für jetzt ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/howto/…
Leif Gruenwoldt
1
@LeifGruenwoldt Der 1. Link funktioniert jetzt. Und die Spiegelverbindung ist weg, du solltest sie entfernen, nehme ich an.
Vadim Kotov
9

Die Git-Filter-Branch-Lösung funktioniert gut, aber beachten Sie, dass Ihr Git-Repo, wenn es aus einem SVN-Import stammt, möglicherweise mit einer Meldung wie der folgenden fehlschlägt:

Rewrite 422a38a0e9d2c61098b98e6c56213ac83b7bacc2 (1/42)mv: cannot stat `/home/.../wikis/nodows/.git-rewrite/t/../index.new': No such file or directory

In diesem Fall müssen Sie die Erstrevision aus dem Filterzweig ausschließen - dh HEADam Ende ändern in [SHA of 2nd revision]..HEAD- siehe:

http://www.git.code-experiments.com/blog/2010/03/merging-git-repositories.html

Gareth
quelle
2
Danke dir! Ich habe mir am Kopf gekratzt, warum das nicht funktioniert hat! Das Repo kam tatsächlich von SVN.
Arthur Maltson
1
Gleicher Fehler, wenn ich das mache. Habe meine Hoffnungen geweckt. Außerdem ist die Verbindung jetzt unterbrochen.
Ryan
Könnten Sie näher erläutern, was Sie mit "Kopfwechsel an ..." gemeint haben? Mein Repo stammt aus einem SVN-Import und ich stehe genau vor diesem Problem. Ich würde mich sehr über Hilfe freuen!
5

Die @ MiniQuark-Lösung hat mir sehr geholfen, berücksichtigt jedoch leider keine Tags, die sich in Quell-Repositorys befinden (zumindest in meinem Fall). Unten ist meine Verbesserung auf @MiniQuark Antwort.

  1. Erstellen Sie zuerst ein Verzeichnis, das zusammengesetztes Repo und zusammengeführte Repos enthält, und erstellen Sie ein Verzeichnis für jedes zusammengeführte.

    $ mkdir new_phd
    $ mkdir new_phd / code
    $ mkdir new_phd / Zahlen
    $ mkdir new_phd / These

  2. Ziehen Sie jedes Repository ab und rufen Sie alle Tags ab. (Anweisungen nur für codeUnterverzeichnis anzeigen)

    $ cd new_phd / code
    $ git init
    $ git pull ../../original_phd/code master
    $ git fetch ../../original_phd/code refs / tags / *: refs / tags / *

  3. (Dies ist eine Verbesserung gegenüber Punkt 2 in der MiniQuark-Antwort.) Verschieben Sie den Inhalt von new_phd/codenach new_phd/code/codeund fügen code_Sie vor jedem Tag ein Präfix hinzu

    $ git filter-branch --index-filter 'git ls-files -s | sed "s- \ t \" * - & code / - "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv $ GIT_INDEX_FILE.new $ GIT_INDEX_FILE '--tag-name-filter' sed" s -. * - Code _ & - "'HEAD

  4. Danach gibt es doppelt so viele Tags wie vor der Filterverzweigung. Alte Tags bleiben im Repo und neue Tags mit code_Präfix werden hinzugefügt.

    $ git tag
    mytag1
    code_mytag1

    Alte Tags manuell entfernen:

    $ ls .git / refs / tags / * | grep -v "/ code_" | xargs rm

    Wiederholen Sie Punkt 2,3,4 für andere Unterverzeichnisse

  5. Jetzt haben wir die Struktur der Verzeichnisse wie in @MiniQuark Antwort Punkt 3.

  6. Gehen Sie wie in Punkt 4 der MiniQuark-Antwort vor, rufen Sie jedoch nach dem Ziehen und vor dem Entfernen des Verzeichnisses .gitTags ab:

    $ git fetch catalog refs / tags / *: refs / tags / *

    Fortsetzen..

Dies ist nur eine andere Lösung. Hoffe es hilft jemandem, es hat mir geholfen :)

MichK
quelle
5

Git-Stitch-Repo aus der Antwort von Aristoteles Pagaltzis funktioniert nur für Repositories mit einfacher, linearer Historie.

Antwort von MiniQuark funktioniert für alle Repositorys, verarbeitet jedoch keine Tags und Zweige.

Ich habe ein Programm erstellt, das genauso funktioniert wie von MiniQuark beschrieben, aber ein Merge-Commit (mit N Eltern) verwendet und auch alle Tags und Zweige neu erstellt, um auf diese Merge-Commits zu verweisen.

Beispiele zur Verwendung finden Sie im git-merge-repos-Repository .

robinst
quelle
3

Ich habe ein Tool erstellt, das diese Aufgabe erledigt. Die verwendete Methode ist ähnlich (intern machen einige Dinge wie --filter-branch), ist aber freundlicher. Ist GPL 2.0

http://github.com/geppo12/GitCombineRepo

Giuseppe Monteleone
quelle
3

Tatsächlich unterstützt Git-Stitch-Repo jetzt Zweige und Tags, einschließlich kommentierter Tags (ich habe festgestellt, dass es einen Fehler gab, den ich gemeldet habe, und er wurde behoben). Was ich nützlich fand, ist mit Tags. Da Tags an Commits angehängt sind und einige der Lösungen (wie Eric Lees Ansatz) sich nicht mit Tags befassen. Sie versuchen, einen Zweig aus einem importierten Tag zu erstellen. Dadurch werden alle Zusammenführungen / Verschiebungen von Git rückgängig gemacht und Sie werden zurückgeschickt, als ob das konsolidierte Repository nahezu identisch mit dem Repository ist, aus dem das Tag stammt. Es gibt auch Probleme, wenn Sie dasselbe Tag in mehreren Repositorys verwenden, die Sie zusammengeführt / konsolidiert haben. Wenn Sie beispielsweise Repos A und B haben, haben beide das Tag rel_1.0. Sie führen Repo A und Repo B zu Repo AB zusammen. Da sich rel_1.0-Tags auf zwei verschiedenen Commits befinden (eines für A und eines für B), Welches Tag wird in AB sichtbar sein? Entweder das Tag aus dem importierten Repo A oder aus dem importierten Repo B, aber nicht beide.

Mit Git-Stitch-Repo können Sie dieses Problem beheben, indem Sie die Tags rel_1.0-A und rel_1.0-B erstellen. Möglicherweise können Sie das rel_1.0-Tag nicht auschecken und beide erwarten, aber zumindest können Sie beide sehen, und theoretisch können Sie sie zu einem gemeinsamen lokalen Zweig zusammenführen und dann ein rel_1.0-Tag für diesen zusammengeführten Zweig erstellen (vorausgesetzt, Sie haben nur Quellcode zusammenführen und nicht ändern). Es ist besser, mit Zweigen zu arbeiten, da Sie wie Zweige aus jedem Repo in lokalen Zweigen zusammenführen können. (dev-a und dev-b können zu einem lokalen dev-Zweig zusammengeführt werden, der dann zum Ursprung verschoben werden kann).

user3622356
quelle
2

Die von Ihnen vorgeschlagene Reihenfolge

git init
git add *
git commit -a -m "import everything"

wird funktionieren, aber Sie werden Ihren Commit-Verlauf verlieren.

Patrick_O
quelle
Den Verlauf zu verlieren ist nicht so schlimm, aber da das Repository für meine eigene Arbeit bestimmt ist (dh privat ist), gibt es eine Menge Dinge, die ich nicht versionieren möchte oder die noch nicht versioniert sind.
Will Robertson
1

So führen Sie ein zweites Projekt in einem Hauptprojekt zusammen:

A) Im zweiten Projekt

git fast-export --all --date-order > /tmp/secondProjectExport

B) Im Hauptprojekt:

git checkout -b secondProject
git fast-import --force < /tmp/secondProjectExport

Führen Sie in diesem Zweig alle wichtigen Transformationen durch, die Sie durchführen müssen, und legen Sie sie fest.

C) Dann zurück zum Meister und eine klassische Verschmelzung zwischen den beiden Zweigen:

git checkout master
git merge secondProject
user123568943685
quelle
Dies würde alle Dateien und Ordner im Stammverzeichnis beider Git-Projekte zu einem Projekt zusammenführen. Ich bezweifle, dass jemand dies möchte.
Clintm
0

Ich werde meine Lösung auch hier reinwerfen. Es ist im Grunde ein ziemlich einfacher Bash-Script-Wrapper git filter-branch. Wie bei anderen Lösungen werden nur Hauptzweige und keine Tags migriert. Die vollständigen Master-Commit-Historien werden jedoch migriert und es handelt sich um ein kurzes Bash-Skript, sodass es für Benutzer relativ einfach sein sollte, sie zu überprüfen oder zu optimieren.

https://github.com/Oakleon/git-join-repos

chrishiestand
quelle
0

Dieses Bash-Skript umgeht das Problem mit den Zeichen der Registerkarte "sed" (z. B. unter MacOS) und das Problem fehlender Dateien.

export SUBREPO="subrepo"; # <= your subrepository name here
export TABULATOR=`printf '\t'`;
FILTER='git ls-files -s | sed "s#${TABULATOR}#&${SUBREPO}/#" |
  GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
  git update-index --index-info &&
  if [ -f "$GIT_INDEX_FILE.new" ]; then mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE; else echo "git filter skipped missing file: $GIT_INXEX_FILE.new"; fi'

git filter-branch --index-filter "$FILTER" HEAD

Dies ist eine Kombination von miniquark , marius-Butuc und ryan ‚s Beiträge. Prost auf sie!

bue
quelle