Git-Repository im Unterverzeichnis zusammenführen

83

Ich möchte ein Remote-Git-Repository in meinem Arbeits-Git-Repository als Unterverzeichnis davon zusammenführen. Ich möchte, dass das resultierende Repository den zusammengeführten Verlauf der beiden Repositorys enthält und dass jede Datei des zusammengeführten Repositorys ihren Verlauf wie im Remote-Repository beibehält. Ich habe versucht, die Teilbaumstrategie zu verwenden, wie unter Verwenden der Teilbaum-Zusammenführungsstrategie beschrieben , aber nachdem ich dieses Verfahren befolgt habe, haben einzelne Dateien, die von der entfernten stammen, ihren Verlauf nicht beibehalten, obwohl das resultierende Repository tatsächlich den zusammengeführten Verlauf der beiden Repositorys enthält (`git log 'auf einem von ihnen zeigt nur die Meldung" Zusammengeführter Zweig ... ").

Außerdem möchte ich keine Submodule verwenden, da ich nicht mehr möchte, dass die beiden kombinierten Git-Repositorys getrennt sind.

Ist es möglich, ein Remote-Git-Repository in einem anderen als Unterverzeichnis zusammenzuführen, wobei einzelne Dateien aus dem Remote-Repository ihren Verlauf beibehalten?

Vielen Dank für jede Hilfe.

BEARBEITEN: Ich probiere derzeit eine Lösung aus, die Git-Filter-Zweig verwendet, um den zusammengeführten Repository-Verlauf neu zu schreiben. Es scheint zu funktionieren, aber ich muss es noch etwas testen. Ich werde zurückkehren, um über meine Ergebnisse zu berichten.

EDIT 2: In der Hoffnung, ich mache mich klarer, gebe ich die genauen Befehle an, die ich mit der Teilbaumstrategie von git verwendet habe, was zu einem offensichtlichen Verlust des Verlaufs der Dateien des Remote-Repositorys führt. Sei A das Git-Repo, in dem ich gerade arbeite, und B das Git-Repo, das ich als Unterverzeichnis in A aufnehmen möchte. Es hat folgendes getan:

git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."

Nach diesen Befehlen und dem Aufrufen des Verzeichnisses subdir / Iwant / to / put / B / in werden alle Dateien von B angezeigt, aber git logauf jedem von ihnen wird nur die Festschreibungsmeldung "B als Unterverzeichnis in subdir / Iwant / to / put zusammenführen" angezeigt /Behälter." Ihr Dateiverlauf wie in B geht verloren.

Was zu funktionieren scheint (da ich ein Anfänger auf Git bin, kann ich mich irren) ist das Folgende:

git remote add -f B <url-of-B>
git checkout -b B_branch B/master  # make a local branch following B's master
git filter-branch --index-filter \ 
   'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD 
git checkout master
git merge B_branch

Der obige Befehl für filter-branch stammt von git help filter-branch, in dem ich nur den Subdir-Pfad geändert habe.

christosc
quelle
Was gitksagt über die Geschichte aus? Ich habe in der Vergangenheit die Zusammenführung von Git-Teilbäumen erfolgreich verwendet. Vielleicht können Sie Ihre genauen Befehle offenlegen? Ich bin mir nicht sicher, ob Git-Filter-Branch der richtige Ansatz ist. Ich könnte empfehlen, git-fast-export und git-fast-import zu versuchen, um eine neue Geschichte zu synthetisieren.
Seth Robertson
Nach dem Teilbaumverfahren werden gitkdie beiden Repos auf ihren Tipps zusammengeführt und in ihren anfänglichen Commits nicht miteinander verbunden. (Würde es helfen, wenn ich Screenshots der Verlaufsansicht von gitk veröffentliche? Kann ich?) Leider haben einzelne Dateien des Remote-Repositorys ihren Verlauf nicht beibehalten, wenn ich dies im Terminal tue git log <file-from-remote-repo>. Ich schaue in git-fast-exportund git-fast-import; Ich bin sehr neu in Git. Ich werde meine Frage bearbeiten, um genau zu zeigen, welche Befehle ich mit dem Git-Teilbaum verwendet habe. Vielen Dank für Ihre Antwort.
Christosc
@christosc: Ihre zweite Methode hat wunderbar und sehr einfach funktioniert. Vielen Dank! Ich musste nur das Unterverzeichnis / Iwant / in / put / B / in / ändern und es zu einem Oneliner machen (weil msysgit unter Windows Zeilenumbrüche in Befehlen mit nicht zu unterstützen scheint): git filter-branch --index-filter 'git ls-files -s | sed "s- \ t \" * - & subdir / Iwant / to / put / B / in / - "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv" $ GIT_INDEX_FILE.new "" $ GIT_INDEX_FILE "'HEAD
gaborous
@ user1121352 Ich bin froh, Ihnen geholfen zu haben.
Christosc
Normalerweise folge ich dieser Antwort: stackoverflow.com/a/1684694/207791
Victor Sergienko

Antworten:

37

Nachdem ich die ausführlichere Erklärung erhalten habe, denke ich, dass ich es verstehe, und auf jeden Fall habe ich unten eine Problemumgehung. Insbesondere glaube ich, dass die Erkennung des Umbenennens durch die Zusammenführung des Teilbaums mit --prefix getäuscht wird. Hier ist mein Testfall:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB

Wir erstellen Git-Verzeichnisse a und b mit jeweils mehreren Commits. Wir führen eine Teilbaumzusammenführung durch und führen dann eine endgültige Festschreibung im neuen Teilbaum durch.

Das Ausführen gitk(in z / a) zeigt, dass der Verlauf angezeigt wird, wir können ihn sehen. Laufen git logzeigt, dass der Verlauf angezeigt wird. Das Betrachten einer bestimmten Datei hat jedoch ein Problem: git log bdir/B

Nun, es gibt einen Streich, den wir spielen können. Mit --follow können wir den Vorbenennungsverlauf einer bestimmten Datei anzeigen. git log --follow -- B. Dies ist gut, aber nicht großartig, da der Verlauf der Vorzusammenführung nicht mit der Nachzusammenführung verknüpft werden kann.

Ich habe versucht, mit -M und -C zu spielen, aber ich konnte es nicht dazu bringen, einer bestimmten Datei zu folgen.

Ich denke, die Lösung besteht darin, git über die Umbenennung zu informieren, die im Rahmen der Teilbaumzusammenführung stattfinden wird. Leider ist git-read-tree ziemlich pingelig in Bezug auf das Zusammenführen von Teilbäumen, sodass wir ein temporäres Verzeichnis durcharbeiten müssen, aber das kann verschwinden, bevor wir uns verpflichten. Danach können wir die vollständige Geschichte sehen.

Erstellen Sie zunächst ein "A" -Repository und machen Sie einige Commits:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA

Zweitens erstellen Sie ein "B" -Repository und machen einige Commits:

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB

Und der Trick, damit dies funktioniert : Erzwingen Sie, dass Git die Umbenennung erkennt, indem Sie ein Unterverzeichnis erstellen und den Inhalt in dieses verschieben.

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename

Kehren Sie zum Repository "A" zurück und rufen Sie den Inhalt von "B" ab und führen Sie ihn zusammen:

cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"

Um zu zeigen, dass sie jetzt zusammengeführt sind:

cd bdir
echo BBB>>B
git commit -a -m BBB

Um zu beweisen, dass die gesamte Geschichte in einer verbundenen Kette erhalten bleibt:

git log --follow B

Wir erhalten die Historie danach, aber das Problem ist, dass Sie seit diesem Dritten in Schwierigkeiten sind, wenn Sie das alte "b" -Repo tatsächlich beibehalten und gelegentlich daraus verschmelzen (sagen wir, es handelt sich tatsächlich um ein separat verwaltetes Repo eines Drittanbieters) wird die Umbenennung nicht durchgeführt haben. Sie müssen versuchen, neue Änderungen in Ihrer Version von b mit dem Umbenennen zusammenzuführen, und ich befürchte, dass dies nicht reibungslos verläuft. Aber wenn b weggeht, gewinnt man.

Seth Robertson
quelle
In der Tat funktioniert das @Seth! Und ich musste nicht wie beim Filterzweig auf das Umschreiben der Geschichte zurückgreifen, was zu einer etwas trügerischen Geschichte führt (z git log --stat. B. beim Betrachten ). Außerdem hatte ich den --followWechsel in der Dokumentation von Git Log nicht bemerkt . scheint sehr praktisch bei Umbenennungen. Vielen Dank für Ihre ausführliche und informative Antwort!
Christosc
2
Diese Antwort wäre viel hilfreicher, wenn der Beispielcode in lesbare Zeilen anstatt in einen einzelnen, durch Doppelpunkte getrennten Einzeiler unterteilt würde. ;)
Jwadsack
Ich möchte "b" in "a" zusammenführen, um die gesamte Geschichte beizubehalten. Wie könnte ich das machen?
Smaragdhieu
3
Siehe stackoverflow.com/questions/37937984/… für Bugfix
Alex Brown
1
Wie @AlexBrown erwähnt, wird bei neuen Versionen gitdavon produziert fatal: refusing to merge unrelated historiesund Sie müssen git merge -s ours --allow-unrelated-histories --no-commit B/masterstattdessen ausführen .
pjvandehaar
61

git-subtreeist ein Skript, das genau für diesen Anwendungsfall entwickelt wurde, bei dem mehrere Repositorys unter Beibehaltung des Verlaufs zu einem zusammengeführt werden (und / oder der Verlauf von Teilbäumen aufgeteilt wird, obwohl dies für diese Frage irrelevant zu sein scheint). Es wird seit Release 1.7.11 als Teil des Git-Baums verteilt .

Verwenden Sie Folgendes, um ein Repository <repo>bei der Revision <rev>als Unterverzeichnis zusammenzuführen :<prefix>git subtree add

git subtree add -P <prefix> <repo> <rev>

git-subtree implementiert die Strategie zum Zusammenführen von Teilbäumen benutzerfreundlicher.

Der Nachteil ist, dass im zusammengeführten Verlauf die Dateien nicht fixiert sind (nicht in einem Unterverzeichnis). Angenommen, Sie führen das Repository ain ein b. Als Ergebnis git log a/f1werden Ihnen alle Änderungen (falls vorhanden) mit Ausnahme derjenigen im zusammengeführten Verlauf angezeigt. Du kannst tun:

git log --follow -- f1

Dadurch werden jedoch keine anderen Änderungen als in der zusammengeführten Historie angezeigt.

Mit anderen Worten, wenn Sie die aDateien im Repository nicht ändern , bmüssen Sie --followeinen nicht festgelegten Pfad angeben . Wenn Sie sie in beiden Repositorys ändern, haben Sie zwei Befehle, von denen keiner alle Änderungen anzeigt.

Mehr dazu hier .

kynan
quelle
Nett! Genau das brauchte ich in einer Zeile. Danke, die Zukunft!
Iameli
Dies ist die perfekte Lösung, um ein anderes Repository in einer Unterrichtung in meinem Repository zusammenzuführen.
Eitch
1
Beachten Sie, dass dies mit vorhandenen Unterverzeichnissen unter nicht funktioniert <prefix>. Zum Beispiel, um ein Unterverzeichnis zusammenzuführen, das irgendwann manuell in ein eigenes Repository verschoben wurde, und um es wieder zusammenzuführen.
Richard Kiefer
6

ich wollte

  1. eine lineare Historie ohne explizite Zusammenführung beibehalten und
  2. Lassen Sie es so aussehen, als ob die Dateien des zusammengeführten Repositorys immer im Unterverzeichnis vorhanden waren, und machen Sie als Nebeneffekt die git log -- fileArbeit ohne --follow.

Schritt 1 : Schreiben Sie den Verlauf im Quell-Repository neu, damit er so aussieht, als ob alle Dateien immer unterhalb des Unterverzeichnisses vorhanden wären.

Erstellen Sie einen temporären Zweig für den neu geschriebenen Verlauf.

git checkout -b tmp_subdir

Verwenden Sie dann git filter-branchwie unter Wie kann ich den Verlauf neu schreiben, damit sich alle Dateien mit Ausnahme der Dateien, die ich bereits verschoben habe, in einem Unterverzeichnis befinden? ::

git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
    mkdir -p foo/bar
    git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'

Schritt 2 : Wechseln Sie zum Ziel-Repository. Fügen Sie das Quell-Repository als Remote im Ziel-Repository hinzu und rufen Sie dessen Inhalt ab.

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo

Schritt 3 : Verwenden Sie merge --ontodiese Option , um die Commits des neu geschriebenen Quell-Repositorys über dem Ziel-Repository hinzuzufügen.

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir

Sie können das Protokoll überprüfen, um festzustellen, ob Sie wirklich das bekommen haben, was Sie wollten.

git log --stat

Schritt 4 : Nach dem Rebase befinden Sie sich im Status "Getrennter KOPF". Sie können den Master schnell auf den neuen Kopf vorspulen.

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged

Schritt 5 : Zum Schluss noch eine Bereinigung: Entfernen Sie die temporäre Fernbedienung.

git remote rm sourcerepo
hfs
quelle
git rebasescheint die angegebenen Optionen nicht zusammen zuzulassen: "Fehler: Interaktive Optionen können nicht kombiniert werden (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + - -onto) mit am Optionen (--committer-Datum-ist-Autor-Datum) "
Sam
Interessant! Versuche zu fallen --committer-date-is-author-date. Die Prüfung auf inkompatible Optionen wurde kürzlich in git v2.19.0 ( github.com/git/git/commit/… ) hinzugefügt . Aus der Beschreibung geht hervor, dass --committer-date-is-author-datees vorher sowieso still ignoriert wurde.
HFS
Anstatt den alten filter-branchBefehl zu verwenden git filter-repo --to-subdirectory-filter <dir>, ist er viel schneller und einfacher.
Willem
5

Wenn Sie wirklich Dinge zusammennähen möchten, schauen Sie nach. Sie sollten auch verwenden git rebase --preserve-merges --onto. Es besteht auch die Möglichkeit, das Autorendatum für die Committer-Informationen beizubehalten.

Adam Dymitruk
quelle
@adymitruk Danke für deine Antwort. Ich bin wirklich neu in Git, also werde ich mir die von Ihnen vorgeschlagene Lösung ansehen. Ich habe es versucht git filter-branchund es scheint zu funktionieren, aber vielleicht ist deins besser. Ich werde es ausprobieren.
Christosc
@adymitruk Kann ich Rebase mit zwei Repositorys verwenden, die nicht als Zweige miteinander verbunden sind? Ich meine, die beiden Repositorys, die ich zusammenführen möchte, haben keine gemeinsamen anfänglichen Commits ...
christosc
Danke @adymitruk. Ich war mir nicht sicher, ob eine Neugründung mit zwei unabhängigen Repositorys möglich ist. Es wird sicherlich nützlich sein ...
Christosc
Aber keine Angst vor Filterzweigen. Es hat uns viele Male gerettet. Machen Sie einfach vorher einen anderen Zweig und Sie können jederzeit zurückgehen. Das oder benutze das Reflog.
Adam Dymitruk
Ich verstehe ... Auf jeden Fall lese ich besser die Dokumente zu diesen Git-Konzepten und -Befehlen. Ich habe nur wenig Erfahrung mit VCSs, nämlich svn, und bin irgendwie überwältigt von Git. Seine Macht scheint es jedoch wert zu sein.
christosc
4

Ich fand die folgende Lösung für mich praktikabel. Zuerst gehe ich in Projekt B und erstelle einen neuen Zweig, in dem bereits alle Dateien in das neue Unterverzeichnis verschoben werden. Ich schiebe dann diesen neuen Zweig zum Ursprung. Als nächstes gehe ich zu Projekt A, füge die Fernbedienung von B hinzu und hole sie, dann checke ich den verschobenen Zweig aus, gehe zurück zum Master und füge zusammen:

# in local copy of project B
git checkout -b prepare_move
mkdir subdir
git mv <files_to_move> subdir/
git commit -m 'move files to subdir'
git push origin prepare_move

# in local copy of project A
git remote add -f B_origin <remote-url>
git checkout -b from_B B_origin/prepare_move
git checkout master
git merge from_B

Wenn ich in ein Unterverzeichnis gehe subdir, kann ich den Verlauf verwenden git log --followund habe ihn immer noch.

Ich bin kein Git-Experte, daher kann ich nicht sagen, ob dies eine besonders gute Lösung ist oder ob es Vorbehalte gibt, aber bisher scheint alles in Ordnung zu sein.

0__
quelle
Die Leute scheinen diesen Ansatz hier zu unterstützen: stackoverflow.com/questions/1683531/…
nacross
3

Haben Sie versucht, das zusätzliche Repository als Git-Submodul hinzuzufügen? Der Verlauf wird nicht mit dem enthaltenen Repository zusammengeführt, sondern es handelt sich um ein unabhängiges Repository.

Ich erwähne es, weil du es nicht hast.

Abizern
quelle
1
Danke für die Antwort Abizern. Eigentlich möchte ich, dass die beiden Repository-Historien zu einer zusammengeführt werden. Ich möchte nicht, dass sie getrennt sind, deshalb habe ich keine Submodule erwähnt.
Christosc
0

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

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

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