Git Merge: Übernehmen Sie Änderungen an Code, der in eine andere Datei verschoben wurde

132

Ich versuche gerade ein ziemlich bulliges Git-Merge-Manöver. Ein Problem, auf das ich stoße, ist, dass ich einige Änderungen an einem Code in meiner Zweigstelle vorgenommen habe, mein Kollege diesen Code jedoch in eine neue Datei in seiner Zweigstelle verschoben hat. Als ich das tat git merge my_branch his_branch, bemerkte git nicht, dass der Code in der neuen Datei mit dem alten identisch war, und daher sind keine meiner Änderungen vorhanden.

Was ist der einfachste Weg, um meine Änderungen erneut auf den Code in den neuen Dateien anzuwenden? Ich werde nicht zu viele Probleme haben, herauszufinden, welche Commits erneut angewendet werden müssen (ich kann sie nur verwenden git log --stat). Aber soweit ich das beurteilen kann, gibt es keine Möglichkeit, git dazu zu bringen, die Änderungen erneut auf die neuen Dateien anzuwenden. Das Einfachste, was ich derzeit sehe, ist, die Änderungen manuell erneut anzuwenden, was nicht nach einer guten Idee aussieht.

Ich weiß, dass git Blobs erkennt, keine Dateien. Es muss also eine Möglichkeit geben, dies zu sagen: "Wenden Sie diese genaue Codeänderung aus diesem Commit an, außer nicht dort, wo sie war, sondern wo sie sich jetzt in dieser neuen Datei befindet."

asmeurer
quelle
2
Nicht genau das gleiche, aber hier ist eine ähnliche Frage mit guten Antworten, die zutreffen könnten: stackoverflow.com/questions/2701790/…
Mariano Desanze
4
Keine der Antworten beschreibt, warum git solche Zusammenführungen nicht automatisch durchführen kann. Ich dachte, es sollte klug genug sein, um Umbenennungen zu erkennen und die entsprechende Zusammenführung automatisch durchzuführen?
John
Vielleicht stimmte dies nicht, als die Frage gestellt wurde, aber moderne Versionen von git (ich verwende 1.9.5) können Änderungen in umbenannten und verschobenen Dateien zusammenführen. Es besteht sogar die --rename-thresholdMöglichkeit, die erforderliche Ähnlichkeit zu optimieren.
Todd Owen
1
@ToddOwen, das funktioniert, wenn Sie eine Vanille-Rebase von einem Upstream-Zweig aus durchführen, aber dennoch Probleme haben, wenn Sie eine Reihe von Änderungen auswählen oder zurückportieren, die möglicherweise nicht das Commit enthalten, mit dem die Dateien umbenannt werden .
GuyPaddock
Ist / war das überhaupt ein Problem, wenn Sie zuerst eine Rebase durchführen?
Hbogert

Antworten:

132

Ich hatte ein ähnliches Problem und habe es behoben, indem ich meine Arbeit neu ausgerichtet habe, um sie an die Organisation der Zieldatei anzupassen.

Angenommen, Sie haben Änderungen original.txtan Ihrem Zweig (dem localZweig) vorgenommen, der jedoch am Hauptzweig in original.txteinen anderen kopiert wurdecopy.txt . Diese Kopie wurde in einem Commit erstellt, das wir Commit nennen CP.

Sie möchten alle Ihre lokalen Änderungen, Commits Aund Bdarunter, die vorgenommen wurden original.txt, auf die neue Datei anwenden copy.txt.

 ---- X -----CP------ (master)
       \ 
        \--A---B--- (local)

Erstellen Sie am Wegpunkt moveIhrer Änderungen mit einen Wegwerfzweig git branch move X. Das heißt, setzen Sie den moveZweig auf Commit X, den vor den Commits, die Sie zusammenführen möchten. Dies ist höchstwahrscheinlich das Commit, von dem aus Sie zur Implementierung Ihrer Änderungen verzweigt haben. Wie Benutzer @digory doo unten schrieb, können Sie tun git merge-base master local, um zu finden X.

 ---- X (move)-----CP----- (master)
       \ 
        \--A---B--- (local)

Geben Sie in diesem Zweig den folgenden Umbenennungsbefehl aus:

git mv original.txt copy.txt

Dadurch wird die Datei umbenannt. Beachten Sie, dass copy.txtdies zu diesem Zeitpunkt noch nicht in Ihrem Baum vorhanden war.
Übernehmen Sie Ihre Änderung (wir nennen diese Festschreibung MV).

        /--MV (move)
       /
 ---- X -----CP----- (master)
       \ 
        \--A---B--- (local)

Sie können Ihre Arbeit jetzt neu aufbauen auf move:

git rebase move local

Dies sollte problemlos funktionieren und Ihre Änderungen werden copy.txtin Ihrer lokalen Niederlassung übernommen.

        /--MV (move)---A'---B'--- (local)
       /
 ---- X -----CP----- (master)

Jetzt möchten oder müssen Sie nicht unbedingt ein Commit haben MV im Verlauf Ihres Hauptzweigs haben, da der Verschiebevorgang zu einem Konflikt mit dem Kopiervorgang beim Festschreiben CPim Hauptzweig führen kann.

Sie müssen Ihre Arbeit nur erneut neu starten und den Verschiebevorgang wie folgt verwerfen:

git rebase move local --onto CP

... wo CPist das Commit wo copy.txtwurde in der anderen Niederlassung eingeführt. Dadurch werden alle Änderungen zusätzlich copy.txtzum CPCommit neu berechnet . Jetzt ist Ihr localZweig genau so, als hätten Sie ihn immer geändert copy.txtund nicht original.txt, und Sie können weiterhin mit anderen zusammenführen.

                /--A''---B''-- (local)
               /
 -----X-------CP----- (master)

Es ist wichtig, dass die Änderungen angewendet werden CPoder auf andere Weise copy.txtnicht vorhanden sind und die Änderungen wieder angewendet werdenoriginal.txt .

Hoffe das ist klar. Diese Antwort kommt zu spät, kann aber für andere nützlich sein.

Core-Dump
quelle
2
Das ist viel Arbeit, aber ich denke, es sollte im Prinzip funktionieren. Ich denke, Sie werden mit Merge mehr Glück haben als mit Rebase.
Asmeurer
7
Diese Lösung beinhaltet nur grundlegende Git-Befehle, im Gegensatz zum Bearbeiten von Patches oder Verwenden patch(was möglicherweise auch viel Arbeit erfordert), und deshalb hielt ich es für interessant, sie zu zeigen. Beachten Sie außerdem, dass ich in meinem Fall mehr Zeit gebraucht habe, um die Antwort zu schreiben, als die Änderungen tatsächlich anzuwenden. In welchem ​​Schritt würden Sie eine Zusammenführung empfehlen? und warum? Der einzige Unterschied, den ich sehe, besteht darin, dass ich durch erneutes Basieren ein temporäres Commit mache, das später verworfen wird (Commit MV), was nur bei Zusammenführungen nicht möglich ist.
Coredump
1
Mit Rebase haben Sie eine höhere Wahrscheinlichkeit, sich mit Zusammenführungskonflikten zu befassen, da Sie sich mit jedem Commit befassen, während Sie mit einem Zusammenführen alles auf einmal behandeln, was bedeutet, dass einige Änderungen, die bei einem Rebase aufgetreten sein könnten, bei einem Zusammenführen nicht vorhanden wären. Ich würde die zusammengeführte Datei zusammenführen und dann manuell verschieben. Vielleicht habe ich die Idee Ihrer Antwort jedoch falsch verstanden.
Asmeurer
4
Ich habe einige ASCII-Bäume hinzugefügt, um den Ansatz zu verdeutlichen. In diesem Fall wird Merge vs. Rebase erneut betrachtet: Ich möchte alle Änderungen an 'original.txt' in meinem Zweig übernehmen und sie auf 'copy.txt' im Hauptzweig anwenden, weil aus irgendeinem Grund 'original'. txt 'wurde irgendwann in' copy.txt 'kopiert (und nicht verschoben). Nach dieser Kopie hat sich 'original.txt' möglicherweise auch im Hauptzweig entwickelt. Wenn ich direkt zusammenführen würde, würden meine lokalen Änderungen an original.txt auf die geänderte original.txt im Hauptzweig angewendet, was schwierig zusammenzuführen wäre. Grüße.
Coredump
1
In beiden Fällen glaube ich jedoch, dass diese Lösung funktionieren wird (obwohl ich zum Glück momentan keine Situation habe, in der ich sie ausprobieren kann), daher werde ich sie vorerst als Antwort markieren.
Asmeurer
31

Sie können den Patch jederzeit mit git diff(oder git format-patch) generieren, dann die Dateinamen im Patch manuell bearbeiten und mit git apply(oder) anwendengit am ) .

Kurz gesagt, die einzige Möglichkeit, wie es automatisch funktioniert, besteht darin, dass die Umbenennungserkennung von git herausfinden kann, dass die alten und neuen Dateien dasselbe sind - was sich so anhört, als wären sie nicht wirklich in Ihrem Fall, sondern nur ein Teil davon. Es ist wahr, dass git Blobs verwendet, keine Dateien, aber ein Blob ist nur der Inhalt einer gesamten Datei, ohne den angehängten Dateinamen und Metadaten. Wenn Sie also einen Codeabschnitt zwischen zwei Dateien verschoben haben, sind diese nicht wirklich derselbe Blob - der Rest des Blob-Inhalts ist unterschiedlich, nur der gemeinsame Block.

Cascabel
quelle
1
Nun, es ist die bisher beste Antwort. Ich konnte nicht herausfinden, wie ich git format-patchfür ein Commit arbeiten kann. In git format-patch SHA1diesem Fall werden eine ganze Reihe von Patch-Dateien für den gesamten Verlauf generiert. Aber ich denke, es git show SHA1 > diff.patchwird genauso gut funktionieren.
Asmeurer
1
@asmeurer: Verwenden Sie die -1Option. Die normale Betriebsart für Format-Patch ist beispielsweise ein Revisionsbereich origin/master..master, sodass Sie problemlos eine Patch-Serie vorbereiten können.
Cascabel
1
Eigentlich noch eine Anmerkung. git applyund git amsind zu wählerisch, weil sie die gleichen Zeilennummern wollen. Aber ich bin derzeit mit dem UNIX- patchBefehl erfolgreich.
Asmeurer
23

Hier ist eine Zusammenführungslösung , bei der ein Zusammenführungskonflikt beim Umbenennen und Bearbeiten auftritt und mit mergetool gelöst wird, wobei die richtigen 3 Zusammenführungsquelldateien erkannt werden.

  • Nachdem eine Zusammenführung aufgrund einer gelöschten Datei fehlgeschlagen ist, von der Sie feststellen, dass sie umbenannt und bearbeitet wurde:

    1. Sie brechen die Zusammenführung ab.
    2. Übertragen Sie umbenannte Dateien in Ihrem Zweig.
    3. Und wieder zusammenführen.

Durchgang:

Erstellen Sie eine Datei.txt:

$ git init
Initialized empty Git repository in /tmp/git-rename-and-modify-test/.git/

$ echo "A file." > file.txt
$ git add file.txt
$ git commit -am "file.txt added."
[master (root-commit) 401b10d] file.txt added.
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

Erstellen Sie einen Zweig, den Sie später bearbeiten werden:

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch master.

Erstellen Sie die Umbenennung und bearbeiten Sie sie auf dem Master:

$ git mv file.txt renamed-and-edited.txt
$ echo "edits on master" >> renamed-and-edited.txt 
$ git commit -am "file.txt + edits -> renamed-and-edited.txt."
[master def790f] file.txt + edits -> renamed-and-edited.txt.
 2 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 file.txt
 create mode 100644 renamed-and-edited.txt

Zum Zweig wechseln und dort auch bearbeiten:

$ git checkout branch-with-edits 
Switched to branch 'branch-with-edits'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ 
$ echo "edits on branch" >> file.txt 
$ git commit -am "file.txt edited on branch."
[branch-with-edits 2c4760e] file.txt edited on branch.
 1 file changed, 1 insertion(+)

Versuch, Master zusammenzuführen:

$ git merge master
CONFLICT (modify/delete): file.txt deleted in master and modified in HEAD. Version HEAD of file.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

Beachten Sie, dass der Konflikt schwer zu lösen ist - und dass Dateien umbenannt wurden. Abbrechen, die Umbenennung nachahmen:

$ git merge --abort
$ git mv file.txt renamed-and-edited.txt
$ git commit -am "Preparing for merge; Human noticed renames files were edited."
[branch-with-edits ca506da] Preparing for merge; Human noticed renames files were edited.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file.txt => renamed-and-edited.txt (100%)

Versuchen Sie es erneut:

$ git merge master
Auto-merging renamed-and-edited.txt
CONFLICT (add/add): Merge conflict in renamed-and-edited.txt
Recorded preimage for 'renamed-and-edited.txt'
Automatic merge failed; fix conflicts and then commit the result.

Toll! Das Zusammenführen führt zu einem "normalen" Konflikt, der mit mergetool gelöst werden kann:

$ git mergetool
Merging:
renamed-and-edited.txt

Normal merge conflict for 'renamed-and-edited.txt':
  {local}: created file
  {remote}: created file
$ git commit 
Recorded resolution for 'renamed-and-edited.txt'.
[branch-with-edits 2264483] Merge branch 'master' into branch-with-edits
Vincent Scheib
quelle
Interessant. Ich muss es versuchen, wenn ich das nächste Mal auf dieses Problem stoße.
Asmeurer
1
In dieser Lösung scheint mir der erste Schritt, die abgebrochene Zusammenführung, nur nützlich zu sein, um herauszufinden, welche Dateien remote umbenannt und bearbeitet wurden. Wenn Sie sie im Voraus kennen. Sie können diesen Schritt überspringen. Grundsätzlich besteht die Lösung darin, die Dateien manuell lokal umzubenennen und die Konflikte dann wie gewohnt zusammenzuführen und zu lösen.
1
Danke dir. Meine Situation war, dass ich mit git mv umgezogen bin B.txt -> C.txtund A.txt -> B.txtgit die Zusammenführungskonflikte nicht automatisch korrekt zuordnen konnte (es wurden Zusammenführungskonflikte zwischen alt B.txtund neu angezeigt B.txt). Mit dieser Methode befinden sich die Zusammenführungskonflikte jetzt zwischen den richtigen Dateien.
Cib
Dies funktioniert nur, wenn die gesamte Datei verschoben wird, aber git sollte diese Situation im Allgemeinen automatisch erkennen. Die schwierige Situation ist, wenn nur ein Teil einer Datei verschoben wird.
Robin Green