Führen Sie zwei Git-Repositorys zusammen, ohne den Dateiversionsverlauf zu unterbrechen

226

Ich muss zwei Git-Repositorys zu einem brandneuen dritten Repository zusammenführen. Ich habe viele Beschreibungen dazu gefunden, wie dies mithilfe einer Teilbaumzusammenführung durchgeführt wird (zum Beispiel Jakub Narębskis Antwort auf Wie führt man zwei Git-Repositorys zusammen? ), Und das Befolgen dieser Anweisungen funktioniert meistens, außer dass beim Festschreiben des Teilbaums alle Dateien zusammengeführt werden aus den alten Repositorys werden als neu hinzugefügte Dateien aufgezeichnet. Ich kann den Commit-Verlauf aus den alten Repositorys sehen git log, wenn ich dies tue , aber wenn ich das tue git log <file>, wird nur ein Commit für diese Datei angezeigt - das Zusammenführen von Teilbäumen. Nach den Kommentaren zu der obigen Antwort zu urteilen, bin ich nicht allein, wenn ich dieses Problem sehe, aber ich habe keine veröffentlichten Lösungen dafür gefunden.

Gibt es eine Möglichkeit, Repositorys zusammenzuführen und den Verlauf einzelner Dateien intakt zu lassen?

Eric Lee
quelle
Ich verwende kein Git, aber in Mercurial würde ich bei Bedarf zuerst eine Konvertierung durchführen, um die Dateipfade der zusammenzuführenden Repos zu korrigieren, und dann ein Repo zwangsweise in das Ziel ziehen, um die Änderungssätze zu erhalten, und dann a Zusammenführung der verschiedenen Zweige. Dies ist getestet und funktioniert;) Vielleicht hilft dies auch, eine Lösung für Git zu finden ... im Vergleich zum Subtree-Merge-Ansatz ist der Konvertierungsschritt vermutlich anders, wenn der Verlauf neu geschrieben wird, anstatt nur einen Pfad zuzuordnen (wenn ich das verstehe korrekt). Dies gewährleistet dann eine reibungslose Zusammenführung ohne besondere Behandlung von Dateipfaden.
Lucero
Ich fand diese Frage auch hilfreich stackoverflow.com/questions/1683531/…
nacross
Ich habe eine Folgefrage erstellt. Könnte interessant sein: Führen Sie zwei Git-Repositorys zusammen und behalten Sie den Master-Verlauf bei: stackoverflow.com/questions/42161910/…
Dimitri Dewaele
Die automatisierte Lösung, die für mich funktionierte, war stackoverflow.com/a/30781527/239408
xverges

Antworten:

269

Es stellt sich heraus, dass die Antwort viel einfacher ist, wenn Sie einfach versuchen, zwei Repositorys zusammenzukleben und es so aussehen zu lassen, als wäre es die ganze Zeit so gewesen, anstatt eine externe Abhängigkeit zu verwalten. Sie müssen lediglich Ihren alten Repos Fernbedienungen hinzufügen, sie mit Ihrem neuen Master zusammenführen, die Dateien und Ordner in ein Unterverzeichnis verschieben, die Verschiebung festschreiben und für alle weiteren Repos wiederholen. Submodule, Teilbaumzusammenführungen und ausgefallene Rebases sollen ein etwas anderes Problem lösen und sind nicht für das geeignet, was ich versucht habe.

Hier ist ein Beispiel für ein Powershell-Skript zum Zusammenkleben von zwei Repositorys:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

Natürlich können Sie stattdessen old_b in old_a zusammenführen (was zum neuen kombinierten Repo wird), wenn Sie dies lieber tun möchten - ändern Sie das Skript entsprechend.

Verwenden Sie Folgendes, wenn Sie auch laufende Feature-Zweige übernehmen möchten:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

Dies ist der einzige nicht offensichtliche Teil des Prozesses - dies ist keine Zusammenführung von Teilbäumen, sondern ein Argument für die normale rekursive Zusammenführung, das Git mitteilt, dass wir das Ziel umbenannt haben und Git dabei hilft, alles richtig auszurichten.

Ich schrieb eine etwas ausführlichere Erklärung nach oben hier .

Eric Lee
quelle
16
Diese Lösung git mvfunktioniert nicht so gut. Wenn Sie später eine git logfür eine der verschobenen Dateien verwenden, erhalten Sie nur das Commit aus der Verschiebung. Die gesamte Vorgeschichte geht verloren. Das liegt daran, dass git mves wirklich git rm; git addnur in einem Schritt geht .
mholm815
15
Es ist dasselbe wie bei jeder anderen Verschiebungs- / Umbenennungsoperation in Git: Über die Befehlszeile können Sie den gesamten Verlauf abrufen git log --follow, oder alle GUI-Tools erledigen dies automatisch für Sie. Bei einer Teilbaumzusammenführung können Sie meines Wissens nicht den Verlauf einzelner Dateien abrufen, daher ist diese Methode besser.
Eric Lee
3
@EricLee Wenn das old_b-Repo zusammengeführt wird, treten viele Zusammenführungskonflikte auf. Wird das erwartet? Ich bekomme CONFLICT (umbenennen / löschen)
Jon
9
Wenn ich versuche "dir -exclude old_a |% {git mv $ _. Name old_a}", erhalte ich sh.exe ": dir: Befehl nicht gefunden und sh.exe": git: Befehl nicht gefunden. Dies funktioniert: ls -I old_a | xargs -I '{}' git mv '{}' old_a /
George
5
Dies ist 1(die Nummer Eins) für lsund Kapital "Auge" für xargs. Danke für diesen Tipp!
Dominique Vial
149

Hier ist eine Methode, mit der kein Verlauf neu geschrieben wird, sodass alle Festschreibungs-IDs gültig bleiben. Das Endergebnis ist, dass die Dateien des zweiten Repos in einem Unterverzeichnis landen.

  1. Fügen Sie das zweite Repo als Fernbedienung hinzu:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. Stellen Sie sicher, dass Sie alle Commits von secondrepo heruntergeladen haben:

    git fetch secondrepo
    
  3. Erstellen Sie einen lokalen Zweig aus dem Zweig des zweiten Repos:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Verschieben Sie alle Dateien in ein Unterverzeichnis:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. Führen Sie den zweiten Zweig in den Hauptzweig des ersten Repos ein:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

Ihr Repository verfügt über mehr als ein Root-Commit, dies sollte jedoch kein Problem darstellen.

Flimm
quelle
1
Schritt 2 funktioniert bei mir nicht: fatal: Kein gültiger Objektname: 'secondrepo / master'.
Keith
@Keith: Stellen Sie sicher, dass Sie das zweite Repo als Remote mit dem Namen "secondrepo" hinzugefügt haben und dass dieses Repo einen Zweig mit dem Namen "master" hat (Sie können Zweige auf einem Remote-Repo mit dem Befehl anzeigen git remote show secondrepo)
Flimm
Ich musste einen Abruf machen, um es auch runter zu bringen. Zwischen 1 und 2 habe ich
Uhr
@monkjack: Ich habe meine Antwort so bearbeitet, dass sie einen Schritt zum Abrufen von Git enthält. Fühlen Sie sich frei, die Antwort in Zukunft selbst zu bearbeiten.
Flimm
4
@MartijnHeemels Für ältere Versionen von Git einfach weglassen --allow-unrelated-histories. Siehe den Verlauf dieses Antwortbeitrags.
Flimm
8

Ein paar Jahre sind vergangen und es gibt gut abgestimmte Lösungen, aber ich möchte meine teilen, weil es ein bisschen anders war, weil ich 2 Remote-Repositorys zu einem neuen zusammenführen wollte, ohne den Verlauf aus den vorherigen Repositorys zu löschen.

  1. Erstellen Sie ein neues Repository in Github.

    Geben Sie hier die Bildbeschreibung ein

  2. Laden Sie das neu erstellte Repo herunter und fügen Sie das alte Remote-Repository hinzu.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. Rufen Sie alle Dateien aus dem alten Repo ab, damit ein neuer Zweig erstellt wird.

    git fetch OldRepo
    git branch -a
    

    Geben Sie hier die Bildbeschreibung ein

  4. Führen Sie im Hauptzweig eine Zusammenführung durch, um das alte Repo mit dem neu erstellten zu kombinieren.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    Geben Sie hier die Bildbeschreibung ein

  5. Erstellen Sie einen neuen Ordner, um alle neu erstellten Inhalte zu speichern, die vom OldRepo hinzugefügt wurden, und verschieben Sie die Dateien in diesen neuen Ordner.

  6. Zuletzt können Sie die Dateien aus den kombinierten Repos hochladen und das OldRepo sicher von GitHub löschen.

Ich hoffe, dies kann für alle nützlich sein, die sich mit dem Zusammenführen von Remote-Repositorys befassen.

abautista
quelle
1
Dies ist die einzige Lösung, die für mich funktioniert hat, um die Git-Geschichte zu bewahren. Vergessen Sie nicht, die Remote-Verbindung zum alten Repo mit zu entfernen git remote rm OldRepo.
Harubiyori
7

Bitte schauen Sie sich die Verwendung an

git rebase --root --preserve-merges --onto

zwei Geschichten früh in ihrem Leben zu verbinden.

Wenn Sie Pfade haben, die sich überlappen, korrigieren Sie sie mit

git filter-branch --index-filter

Wenn Sie log verwenden, stellen Sie sicher, dass Sie "Kopien schwerer finden" mit

git log -CC

Auf diese Weise finden Sie alle Bewegungen von Dateien im Pfad.

Adam Dymitruk
quelle
Die Git-Dokumentation empfiehlt, nicht neu zu gründen ... git-scm.com/book/en/v2/Git-Branching-Rebasing#_rebase_peril
Stephen Turner
7

Ich habe die Lösung von @Flimm in eine solche umgewandelt git alias(zu meiner hinzugefügt ~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"
Fredrik Erlandsson
quelle
12
Nur neugierig: Tun Sie das wirklich oft genug, um einen Alias ​​zu benötigen?
Parker Coates
1
Nein, ich erinnere mich nicht, aber ich erinnere mich nie daran, wie es geht. Ein Alias ​​ist nur eine Möglichkeit für mich, mich daran zu erinnern.
Fredrik Erlandsson
1
Ja .. aber versuchen Sie, den Computer zu wechseln und zu vergessen, Ihre Aliase zu verschieben;)
quetzalcoatl
1
Was ist der Wert von $GIT_PREFIX?
Neowulf33
github.com/git/git/blob/… 'GIT_PREFIX' wird als zurückgegeben festgelegt, indem 'git rev-parse --show-prefix' aus dem ursprünglichen aktuellen Verzeichnis ausgeführt wird. Siehe linkgit: git-rev-parse [1].
Fredrik Erlandsson
3

Diese Funktion klont das Remote-Repo in das lokale Repo-Verzeichnis:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Wie benutzt man:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Beachten. Dieses Skript kann Commits neu schreiben, speichert jedoch alle Autoren und Daten. Dies bedeutet, dass neue Commits weitere Hashes enthalten. Wenn Sie versuchen, Änderungen auf den Remote-Server zu übertragen, kann es nur mit Force-Taste ausgeführt werden. Außerdem werden Commits auf dem Server neu geschrieben. Machen Sie also vor dem Start Backups.

Profitieren!

Andrey Izman
quelle
Ich benutze eher zsh als bash und v2.13.0 von git. Egal was ich versucht habe, ich konnte nicht git filter-branch --index-filterzur Arbeit kommen. Normalerweise erhalte ich die Fehlermeldung, dass die neue Indexdatei nicht vorhanden ist. Läutet das irgendwelche Glocken?
Patrick Beard
@PatrickBeard Ich weiß nicht, zsh, Sie können eine separate Datei git-add-repo.shmit der obigen Funktion erstellen , am Ende der Datei setzen Sie diese Zeile git-add-repo "$@". Danach können Sie es von zsh wie cd current/git/packageundbash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
Andrey Izman
Das Problem wurde hier besprochen: stackoverflow.com/questions/7798142/… mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" schlägt manchmal fehl, daher müssen Sie eine hinzufügen if test.
Patrick Beard
1
Ich würde diese Methode nicht verwenden! Ich habe das Skript naiv und wörtlich ausprobiert (ich kann mich nur für diesen Teil verantwortlich machen), und es hat mein lokales Git-Repo überlastet. Die Historie sah größtenteils richtig aus, aber ein Git-Push zurück zu Github führte zu dem gefürchteten Fehler "RPC fehlgeschlagen; Curl 55 SSL_write () gab SYSCALL zurück, errno = 32". Ich habe versucht, es zu reparieren, aber es war irreparabel kaputt. Am Ende musste ich die Dinge in einem neuen lokalen Repo rekonstruieren.
Mason
@MasonFreed Dieses Skript erstellt einen neuen Git-Verlauf mit einer Mischung aus beiden Repos, sodass es nicht in das alte Repo verschoben werden kann. Es muss ein neues erstellt oder mit der Force-Taste gedrückt werden.
Dies
2

Befolgen Sie die Schritte, um ein Repo in ein anderes Repo einzubetten, indem Sie einen einzelnen Git-Verlauf erstellen, indem Sie beide Git-Verlaufs zusammenführen.

  1. Klonen Sie beide Repos, die Sie zusammenführen möchten.

git clone [email protected]: user / parent-repo.git

git clone [email protected]: user / child-repo.git

  1. Gehe zum Kinder-Repo

cd child-repo /

  1. Führen Sie den folgenden Befehl aus und ersetzen Sie den Pfad my/new/subdir(3 Vorkommen) durch die Verzeichnisstruktur, in der Sie das untergeordnete Repo haben möchten.

git filter-branch --prune-empty --tree-filter 'if [! -e mein / neues / Unterverzeichnis]; dann mkdir -p my / new / subdir git ls-tree --name-only $ GIT_COMMIT | xargs -I Dateien mv Dateien my / new / subdir fi '

  1. Gehe zum Eltern-Repo

cd ../parent-repo/

  1. Fügen Sie dem übergeordneten Repo eine Fernbedienung hinzu und zeigen Sie den Pfad zum untergeordneten Repo

git remote add child-remote ../child-repo/

  1. Holen Sie sich das Kinder-Repo

git fetch child-remote

  1. Führen Sie die Geschichten zusammen

Git Merge - Allow-Un-Related-Histories Child-Remote / Master

Wenn Sie das Git-Protokoll jetzt im übergeordneten Repo überprüfen, sollten die untergeordneten Repo-Commits zusammengeführt werden. Sie können auch das Tag sehen, das von der Commit-Quelle angezeigt wird.

Der folgende Artikel hat mir geholfen, ein Repo in ein anderes Repo einzubetten und einen einzigen Git-Verlauf zu haben, indem beide Git-Verlaufs zusammengeführt wurden.

http://ericlathrop.com/2014/01/combining-git-repositories/

Hoffe das hilft. Viel Spaß beim Codieren!

AnoopGoudar
quelle
Schritt 3 ist bei mir mit einem Syntaxfehler fehlgeschlagen. Semikolons fehlen. Fixgit filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
Yuri L
1

Sagen Sie bitte Repository zusammenführen möchten ain b(Ich gehe davon aus sie nebeneinander angeordnet sind):

cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

Wenn Sie aein Unterverzeichnis erstellen möchten, gehen Sie vor den obigen Befehlen wie folgt vor:

cd a
git filter-repo --to-subdirectory-filter a
cd ..

Hierfür müssen Sie git-filter-repoinstalliert haben ( filter-branchwird davon abgeraten ).

Ein Beispiel für das Zusammenführen von zwei großen Repositorys, wobei eines davon in einem Unterverzeichnis abgelegt wird: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Mehr dazu hier .

x-yuri
quelle