web-dev-qa-db-de.com

Zwei Zweige identisch machen

Ich habe zwei Zweige - A und B. B wurde aus A erstellt.

Die Arbeiten an beiden Niederlassungen wurden parallel fortgesetzt. Die Arbeit in Zweig A war schlecht (was zu einer nicht funktionierenden Version führte), während die Arbeit in Zweig B gut war. In dieser Zeit wurde Zweig B manchmal mit Zweig A zusammengeführt (aber nicht umgekehrt).

Jetzt möchte ich die Verzweigung A mit der Verzweigung B identisch machen. Ich kann git revert nicht verwenden, weil ich zu viele Commits zurücksetzen muss. Ich möchte nur die auf Verzweigung A durchgeführten Commits rückgängig machen .

Die Lösung, die ich fand, bestand darin, Zweig B in einen anderen Ordner zu klonen, alle Dateien aus dem Arbeitsordner von Zweig A zu löschen, die Dateien aus dem temporären Zweig B-Ordner zu kopieren und alle nicht aufgezeichneten Dateien hinzuzufügen.

Gibt es einen git-Befehl, der dasselbe tut? Ich habe einen git-Rückstellschalter verpasst?

16
zmbq

Es gibt viele Möglichkeiten, dies zu tun, und welche Sie verwenden sollten, hängt davon ab, welches Ergebnis Sie erzielen möchten und insbesondere davon, was Sie und alle mit Ihnen zusammenarbeitenden Personen (wenn es sich um ein freigegebenes Repository handelt) in Zukunft erwarten.

Die drei wichtigsten Möglichkeiten, dies zu tun:

  1. Mach dir keine Sorgen. Verzweigen Sie A, erstellen Sie einen neuen A2 Und verwenden Sie diesen.
  2. Verwenden Sie git reset Oder ein gleichwertiges Element, um A an eine andere Stelle zu verschieben.

    Die Methoden 1 und 2 sind auf lange Sicht praktisch gleich. Angenommen, Sie geben zunächst A auf. Jeder entwickelt stattdessen für eine Weile auf A2. Sobald alle Benutzer A2 Verwenden, löschen Sie den Namen A vollständig. Jetzt, da A weg ist, können Sie A2 Sogar in A umbenennen (alle anderen Benutzer müssen natürlich denselben Umbenennen vornehmen). Was unterscheidet sich an diesem Punkt, wenn überhaupt, zwischen dem Fall, in dem Sie Methode 1 verwendet haben, und dem Fall, in dem Sie Methode 2 verwendet haben? (Es gibt einen Ort, an dem Sie möglicherweise noch einen Unterschied feststellen können, je nachdem, wie lange Sie "auf lange Sicht" unterwegs sind und wann Sie alte Reflogs verfallen lassen.)

  3. Führen Sie eine Zusammenführung mit einer speziellen Strategie durch (siehe "Alternative Zusammenführungsstrategien" unten). Was Sie hier wollen, ist -s theirs, Aber es existiert nicht und Sie müssen es vortäuschen.

Randnotiz: Wo ein Zweig "erstellt von" ist, ist grundsätzlich irrelevant: Git verfolgt solche Dinge nicht und im Allgemeinen ist es nicht möglich, ihn später zu entdecken, daher sollte es Ihnen wahrscheinlich auch egal sein. (Wenn es Ihnen wirklich wichtig ist, können Sie einige Einschränkungen für die Manipulation des Zweigs festlegen oder seinen "Startpunkt" mit einem Tag markieren, damit Sie diese Informationen später mechanisch wiederherstellen können. Dies ist jedoch häufig ein Zeichen dafür.) Sie tun an erster Stelle etwas falsch - oder erwarten zumindest etwas von git, das git nicht bietet (siehe auch Fußnote 1 unten).

Definition von branch (siehe auch Was genau verstehen wir unter branch? )

Eine Verzweigung - oder genauer gesagt ein Verzweigungsname - in git ist einfach ein Zeiger auf ein bestimmtes Commit innerhalb des Commit-Diagramms. Dies gilt auch für andere Referenzen wie Tag-Namen. Das Besondere an einem Zweig im Vergleich zu einem Tag ist, dass Sie in einem Zweig sind und einen neuen Zweig erstellen. commit, git aktualisiert den Filialnamen automatisch, so dass er jetzt auf das neue commit verweist.

Nehmen wir zum Beispiel an, wir haben ein Commit-Diagramm, das ungefähr so ​​aussieht: o - Knoten (und der mit * Gekennzeichnete Knoten) stehen für Commits und A und B sind Branchennamen:

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o           <-- B

A und B zeigen jeweils auf das oberste Commit einer Verzweigung oder genauer gesagt auf die Datenstruktur der Verzweigung , die gebildet wird, indem bei einigen begonnen wird Festschreiben und Zurückarbeiten aller erreichbaren Festschreibungen, wobei jede Festschreibung auf eine oder mehrere übergeordnete Festschreibungen verweist.

Wenn Sie git checkout B Verwenden, sodass Sie sich in der Verzweigung B befinden und ein neues Commit ausführen, wird das neue Commit mit dem vorherigen Tipp von B als (einzeln) eingerichtet ) parent und git ändern die ID, die unter dem Zweignamen B gespeichert ist, so dass B auf das neue, am weitesten oben stehende Commit verweist:

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o <- o      <-- B

Das mit * Gekennzeichnete Commit ist die Merge-Basis der beiden Verzweigungsspitzen. Dieser Commit und alle früheren Commits befinden sich in beiden Zweigen.1 Die Zusammenführungsbasis ist wichtig für das Zusammenführen (duh :-)), aber auch für Dinge wie git cherry Und andere Release-Management-Operationen.

Sie erwähnen, dass Zweig B gelegentlich wieder in A zusammengeführt wurde:

git checkout A; git merge B

Dies führt zu einem neuen Merge-Commit , bei dem es sich um ein Commit mit zwei handelt2 Eltern. Das erste übergeordnete Element ist der vorherige Tipp des aktuellen Zweigs, dh der vorherige Tipp von A, und das zweite übergeordnete Element ist das genannte Festschreiben, dh der Tipp -most Festschreiben des Zweigs B. Wenn Sie das Obige etwas kompakter zeichnen (aber ein paar weitere - Hinzufügen, um die o - Linie zu verbessern), beginnen wir mit:

o--*--o--o--o--o      <-- A
    \
     o---o--o---o     <-- B

und am Ende mit:

o--*--o--o--o--o--o   <-- A
    \            /
     o---o--o---*     <-- B

Wir verschieben den * Auf die neue Merge-Basis von A und B (was eigentlich die Spitze von B ist). Vermutlich fügen wir dann noch ein paar Commits zu B hinzu und führen sie vielleicht noch ein paar Mal zusammen:

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

Was Git standardmäßig mit git merge <thing> Macht

Um eine Zusammenführung durchzuführen, checken Sie einen Zweig aus (A in diesen Fällen) und führen dann git merge Aus und geben ihm mindestens ein Argument, normalerweise einen anderen Zweignamen wie B. Der Zusammenführungsbefehl beginnt mit der Umwandlung des Namens in eine Festschreibungs-ID. Aus dem Namen eines Zweigs wird die ID des aktuellsten Commits für den Zweig.

Als nächstes findet git merge Die Zusammenführungsbasis . Dies sind die Commits, die wir die ganze Zeit über mit * Markiert haben. Die technische Definition der Zusammenführungsbasis ist der niedrigste gemeinsame Vorfahr im Festschreibungsdiagramm (und in einigen Fällen kann es mehr als einen geben). Der Einfachheit halber wird hier jedoch nur "das mit * Gekennzeichnete Festschreiben" verwendet.

Zuletzt führt git für gewöhnliche Zusammenführungen zwei git diff - Befehle aus.3 Das erste git diff Vergleicht das Festschreiben * Mit dem Festschreiben HEAD, d. H. Der Spitze des aktuellen Zweigs. Der zweite Diff vergleicht Commit * Mit dem Argument Commit, dh der Spitze des anderen Zweigs (Sie können ein bestimmtes Commit benennen und es muss nicht die Spitze eines Zweigs sein, aber in unserem Fall verschmelzen wir B in A, so dass wir diese beiden Branch-Tip-Commits erhalten).

Wenn git in beiden Zweigen eine Datei findet, die im Vergleich zur Merge-Base-Version geändert wurde, versucht git, diese Änderungen auf halb-intelligente (aber nicht wirklich intelligente) Weise zu kombinieren : Wenn beide Änderungen denselben Text zur selben Region hinzufügen, behält git eine Kopie des Zusatzes. Wenn beide Änderungen denselben Text in derselben Region löschen, löscht git diesen Text nur einmal. Wenn beide Änderungen Text in derselben Region ändern, tritt ein Konflikt auf, es sei denn, die Änderungen stimmen genau überein (dann erhalten Sie eine Kopie der Änderungen). Wenn eine Seite eine Änderung vornimmt und die andere Seite eine andere Änderung vornimmt, die Änderungen sich jedoch nicht zu überschneiden scheinen, übernimmt git beide Änderungen. Dies ist die Essenz einer Drei-Wege-Verschmelzung .

Wenn alles gut geht, macht git zuletzt ein neues Commit, das zwei (oder mehr, wie bereits in Fußnote 2 erwähnt) Eltern hat. Der Arbeitsbaum , der mit diesem neuen Commit verknüpft ist, ist derjenige Git, der beim Zusammenführen in drei Richtungen erstellt wurde.

Alternative Zusammenführungsstrategien

Während die Standardstrategie von git mergerecursive die Optionen -Xours und theirs enthält, tun sie hier nicht das, was wir wollen. Diese sagen einfach, dass git im Falle eines Konflikts diesen Konflikt automatisch lösen sollte, indem Sie "unsere Änderung" (-X ours) Oder "ihre Änderung" (-X theirs).

Der Befehl merge hat eine ganz andere Strategie, -s ours: In dieser Strategie wird angegeben, dass Sie statt der Zusammenführungsbasis zwischen den beiden Commits nur unseren Quelltextbaum verwenden müssen. Mit anderen Worten, wenn wir uns in der Verzweigung A befinden und git merge -s ours B Ausführen, führt git einen neuen Merge-Commit durch, wobei das zweite übergeordnete Element die Spitze der Verzweigung B ist Der Quelltextbaum entspricht der Version im vorherigen Zweig A. Das heißt, der Code für das neue Commit stimmt genau mit dem Code für das übergeordnete Commit überein.

Wie in diese andere Antwort beschrieben, gibt es eine Reihe von Möglichkeiten, git zu zwingen, -s theirs Effektiv zu implementieren. Ich denke, das Einfachste, um zu erklären , ist diese Sequenz:

git checkout A
git merge --no-commit -s ours B
git rm -rf .         # make sure you are at the top level!
git checkout B -- .
git commit

Der erste Schritt besteht darin, sicherzustellen, dass wir uns wie gewohnt in der Verzweigung A befinden. Die zweite Möglichkeit besteht darin, eine Zusammenführung auszulösen, aber das Ergebnis noch nicht festzuschreiben (--no-commit). Um die Zusammenführung für git zu vereinfachen - dies ist nicht erforderlich, macht die Dinge nur schneller und leiser - verwenden wir -s ours, Sodass git die diff-Schritte vollständig überspringen kann und wir alle Beschwerden über Zusammenführungskonflikte vermeiden.

An diesem Punkt kommen wir zum Fleisch des Tricks. Zuerst entfernen wir das gesamte Merge-Ergebnis, da es eigentlich wertlos ist: Wir wollen nicht den Work-Tree aus dem Tip-Commit von A, sondern den aus dem Tip von B. Dann checken wir jede Datei von der Spitze von B aus und machen sie festschreibungsbereit.

Zuletzt übergeben wir die neue Zusammenführung, die als erstes übergeordnetes Element den alten Tipp von A und als zweites übergeordnetes Element den Tipp von B hat, jedoch den Baum from commit B.

Wenn der Graph kurz vor dem Festschreiben war:

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

dann ist das neue Diagramm jetzt:

...---o--...--o--o--o   <-- A
     /       /     /
...-o--...--o--o--*     <-- B

Die neue Zusammenführungsbasis ist wie üblich die Spitze von B, und aus der Perspektive des Commit-Diagramms sieht diese Zusammenführung genauso aus wie jede andere Zusammenführung. Was ungewöhnlich ist, ist, dass der Quelltextbaum für die neue Zusammenführung an der Spitze von A genau mit dem Quelltextbaum für die Spitze von B übereinstimmt.


1In diesem speziellen Fall ist es auch wahrscheinlich der Punkt, an dem einer der beiden Zweige erstellt wurde (oder vielleicht sogar, an dem beide Zweige erstellt wurden), da die beiden Zweige unabhängig geblieben sind (nie zusammengeführt wurden) erstellt), obwohl Sie dies zum jetzigen Zeitpunkt noch nicht beweisen können (da jemand in der Vergangenheit möglicherweise git reset oder verschiedene andere Tricks verwendet hat, um die Zweigbezeichnungen zu verschieben). Sobald wir jedoch mit dem Zusammenführen beginnen, ist die Zusammenführungsbasis eindeutig nicht mehr der Ausgangspunkt, und es wird schwieriger, einen vernünftigen Ausgangspunkt zu finden.

2Technisch gesehen ist ein Merge-Commit ein Commit mit zwei oder mehr Elternteilen. Git-Aufrufe verschmelzen mit mehr als zwei Eltern "Octopus Merges". Nach meiner Erfahrung sind sie nur im git-Repository für git selbst üblich, und am Ende erreichen sie dasselbe wie mehrere gewöhnliche Zusammenführungen von zwei Elternteilen.

3Die Diffs werden normalerweise zu einem gewissen Grad intern ausgeführt, anstatt tatsächliche Befehle auszuführen. Dies ermöglicht viele kurze Optimierungen. Es bedeutet auch, dass, wenn Sie einen benutzerdefinierten Zusammenführungstreiber schreiben, dieser benutzerdefinierte Zusammenführungstreiber nur ausgeführt wird, wenn git feststellt, dass die Datei in beiden Diffs geändert wurde. Wenn es nur in einem der beiden Unterschiede geändert wird, wird für die Standardzusammenführung einfach das geänderte verwendet.

41
torek

Überprüfen Sie Ihre Zielniederlassung:

git checkout A;

So entfernen Sie alles in Zweig A und machen es mit B gleich:

git reset B --hard;

Wenn Sie alle Ihre lokalen Änderungen verwerfen müssen (Art der Zurücksetzung, aber git git revert nicht durcheinander bringen):

git reset HEAD --hard;

Vergessen Sie nicht, Ihren entfernten Zweig zu aktualisieren (verwenden Sie --force oder -f, falls Sie den Verlauf überschreiben möchten).

git Push Origin B -f;
4
Jack

Hier ist ein letzter Ausweg (es ist schmutzig und keine geile Art, Dinge zu tun) ...

  1. Gehen Sie zu Zweig B (Kasse B).
  2. Kopieren Sie den gesamten Inhalt dieses Zweigs (Strg + C)
  3. Gehe zu Zweig A (Git Checkout A)
  4. Alles aus Zweig A löschen (alles mit der Maus auswählen und löschen)
  5. Kopieren Sie den gesamten Inhalt von Zweig B in den Ordner, in dem sich der gesamte Inhalt von Zweig A befand. (Strg + V)
  6. Stellt alle neuen Änderungen bereit (git add.)
  7. Übernehmen Sie die bereitgestellten Änderungen (git commit -m "Zweig A ist jetzt dasselbe wie B")
2
Pero122

Sie müssen git reset Abzweig A bis Abzweig B . Siehe docs .

0
AlexZam

Das hat bei mir mit TortoiseGit funktioniert

SCHRITTE:

  1. [~ # ~] exportiere [~ # ~] das branch als Zip, mit dem du eine Identität mit deinem bevorzugten Ort erreichen willst: - enter image description here

  2. [~ # ~] entpacke [~ # ~] das Archiv/die Zip-Datei.

  3. Kopiere das .git Ordner von der Original-Code-Basis zu diesem unzipped-folder ( [~ # ~] sicherstellen, dass [~ # ~] der target branch ist in der Original-Code-Basis ausgewählt)

  4. git add .

  5. git commit -m "Achieving identicality with abc-branch"

  6. git Push, um Änderungen in das Repo zu verschieben. (--up-stream Falls erforderlich)

TortoiseGit: https://tortoisegit.org

Viel Glück...

0
Akash

hier ist die schöne Art und Weise, wie ich das für mich mache .. Die Idee ist, Unterschiede zwischen A und B zu bekommen, diesen Unterschied zu begehen, das Gegenteil umzusetzen und entgegengesetztes Commit zu erzeugen und dann das letzte Commit zu A zu machen

hier sind Befehle

git checkout B
git checkout -b B_tmp 
git merge --squash A
git commit -a -m 'diff'
git revert ⬆_commit_sha(paste here 'diff' commit sha, get from 'git log -1')
git checkout A                    
git cherry-pick revert_commit_sha (B_tmp branch last commit)
git branch -d B_tmp

und hier sind Sie, A ist dasselbe wie B, und Sie haben nur ein Commit in der A-Historie, das als B-Zweig zurückgesetzt wird.

0
Sergey Sahakyan