Wie kann ich wiederherstellen / neu synchronisieren, nachdem jemand eine Rebase oder ein Reset in einen veröffentlichten Zweig verschoben hat?

87

Wir haben alle gehört , dass man sollte nie rebase Arbeit veröffentlicht, dass es gefährlich ist , usw. Allerdings habe ich keine Rezepte gesehen geschrieben, wie mit der Situation im Falle eines Fütterungsmaterial beschäftigen wird veröffentlicht.

Beachten Sie nun, dass dies nur dann wirklich machbar ist, wenn das Repository nur von einer bekannten (und vorzugsweise kleinen) Gruppe von Personen geklont wird, sodass jeder, der die Rebase oder das Zurücksetzen vorantreibt, alle anderen benachrichtigen kann, dass sie beim nächsten Mal aufpassen müssen holen(!).

Eine offensichtliche Lösung, die ich gesehen habe, funktioniert, wenn Sie keine lokalen Commits haben foound diese neu basiert:

git fetch
git checkout foo
git reset --hard origin/foo

Dies wird einfach den lokalen Status foozugunsten seiner Geschichte gemäß dem Remote-Repository wegwerfen .

Aber wie geht man mit der Situation um, wenn man in diesem Zweig wesentliche lokale Veränderungen vorgenommen hat?

Aristoteles Pagaltzis
quelle
+1 für das einfache Fallrezept. Es ist ideal für die persönliche Synchronisierung zwischen Computern, insbesondere wenn diese unterschiedliche Betriebssysteme haben. Es ist etwas, das im Handbuch erwähnt werden sollte.
Philip Oakley
Das ideale Rezept für die persönliche Synchronisation ist git pull --rebase && git push. Wenn Sie masternur daran arbeiten, wird dies nahezu immer das Richtige für Sie tun, selbst wenn Sie am anderen Ende neu gegründet und gedrückt haben.
Aristoteles Pagaltzis
Da ich zwischen einem PC und einem Linux-Computer synchronisiere und entwickle, funktioniert die Verwendung eines neuen Zweigs für jedes Rebase / Update gut. Ich verwende die Variante git reset --hard @{upstream}jetzt auch, da ich weiß, dass die magische Refspec-Beschwörung für "Vergiss, was ich habe / hatte, benutze, was ich von der Fernbedienung abgerufen habe". Siehe meinen letzten Kommentar zu stackoverflow.com/a/15284176/717355
Philip Oakley
Mit Git2.0 können Sie den alten Ursprung Ihres Zweigs finden (bevor der Upstream-Zweig mit a umgeschrieben wurde push -f): siehe meine Antwort unten
VonC

Antworten:

75

Nach einer Push-Rebase wieder synchron zu sein, ist in den meisten Fällen nicht so kompliziert.

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

Dh. Zuerst richten Sie ein Lesezeichen für die ursprüngliche Position des Remote-Zweigs ein und verwenden dieses dann, um Ihre lokalen Commits ab diesem Zeitpunkt auf dem neu basierten Remote-Zweig wiederzugeben.

Rebasing ist wie Gewalt: Wenn es Ihr Problem nicht löst, brauchen Sie einfach mehr davon. ☺

Sie können dies natürlich auch ohne Lesezeichen tun, wenn Sie die origin/fooCommit-ID vor dem Rebase nachschlagen und diese verwenden.

So gehen Sie auch mit der Situation um, in der Sie vergessen haben, vor dem Abrufen ein Lesezeichen zu erstellen. Es geht nichts verloren - Sie müssen nur das Reflog für den Remote-Zweig überprüfen:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

Dadurch wird die Festschreibungs-ID gedruckt, origin/fooauf die vor dem letzten Abruf verwiesen wurde, der den Verlauf geändert hat.

Sie können dann einfach

git rebase --onto origin/foo $commit foo
Aristoteles Pagaltzis
quelle
11
Kurzer Hinweis: Ich denke, es ist ziemlich intuitiv, aber wenn Sie awk nicht gut kennen ... sucht dieser git reflog show origin/fooEinzeiler nur in der Ausgabe von nach der ersten Zeile mit der Aufschrift "fetch: Forced-Update"; Das ist es, was Git aufzeichnet, wenn ein Abruf den Remote-Zweig dazu veranlasst, alles andere als einen schnellen Vorlauf durchzuführen. (Sie können es auch einfach von Hand machen - das erzwungene Update ist wahrscheinlich das Neueste.)
Cascabel
2
Es ist nichts wie Gewalt. Gewalt macht gelegentlich Spaß
Iolo
5
@iolo Richtig, Rebasing macht immer Spaß.
Dan Bechard
1
Vermeiden Sie wie Gewalt fast immer eine Neugründung. Aber hab eine Ahnung wie.
Bob Stein
2
Vermeiden Sie es, eine Rebase dort zu platzieren, wo andere betroffen sind.
Aristoteles Pagaltzis
11

Ich würde sagen, dass der Abschnitt zum Wiederherstellen von Upstream-Rebase auf der Manpage zu git-rebase so ziemlich alles abdeckt.

Es ist wirklich nicht anders, als sich von Ihrer eigenen Rebase zu erholen - Sie verschieben einen Zweig und stützen alle Zweige, die ihn in ihrer Geschichte hatten, auf seine neue Position.

Cascabel
quelle
4
Ah, das tut es auch. Aber obwohl ich jetzt verstehe, was es sagt, hätte ich es vorher nicht getan, bevor ich es selbst herausgefunden hätte. Und es gibt kein Kochbuchrezept (vielleicht zu Recht in einer solchen Dokumentation). Ich werde auch darauf hinweisen, dass es FUD ist, den „harten Fall“ als schwer zu bezeichnen. Ich behaupte, dass umgeschriebene Geschichte auf der Skala der meisten internen Entwicklungen trivial handhabbar ist. Die abergläubische Art und Weise, wie dieses Thema immer behandelt wird, ärgert mich.
Aristoteles Pagaltzis
4
@Aristotle: Sie haben Recht, dass es sehr überschaubar ist, da alle Entwickler wissen, wie man Git verwendet, und dass Sie effektiv mit allen Entwicklern kommunizieren können. In einer perfekten Welt wäre das das Ende der Geschichte. Aber viele Projekte da draußen sind groß genug, dass eine vorgelagerte Rebase wirklich beängstigend ist. (Und dann gibt es Orte wie meinen Arbeitsplatz, wo die meisten Entwickler haben noch nie gehört , von einem Fütterungsmaterial.) Ich denke , die „superstition“ ist nur ein Weg, um die sichersten, allgemeinen Ratschläge möglich. Niemand möchte derjenige sein, der eine Katastrophe im Repo eines anderen verursacht.
Cascabel
2
Ja, ich verstehe das Motiv. Und ich stimme dem voll und ganz zu. Aber es gibt einen großen Unterschied zwischen "Versuchen Sie das nicht, wenn Sie die Konsequenzen nicht verstehen" und "Sie sollten das niemals tun, weil es böse ist", und dies allein stelle ich in Frage. Es ist immer besser zu unterweisen, als Angst zu wecken.
Aristoteles Pagaltzis
@ Aristoteles: Einverstanden. Ich versuche, mich dem Ende "Stellen Sie sicher, dass Sie wissen, was Sie tun" zu widmen, aber insbesondere online versuche ich, ihm genügend Gewicht zu geben, damit ein gelegentlicher Besucher von Google dies zur Kenntnis nimmt. Sie haben Recht, vieles sollte wahrscheinlich abgeschwächt werden.
Cascabel
11

Beginnend mit git 1.9 / 2.0 Q1 2014 müssen Sie nicht Ihre vorherigen Zweig Ursprung markieren , bevor sie auf dem neu geschrieben Upstream - Zweig Rebasing, wie sie in der Aristoteles Pagaltzis ‚s Antwort :
Siehe begehen 07d406b und begehen d96855f :

Nach der Arbeit an dem mit erstellten topicZweig git checkout -b topic origin/masterwurde der Verlauf des Fernverfolgungszweigs origin/mastermöglicherweise zurückgespult und neu erstellt, was zu einem Verlauf dieser Form führte:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

wo origin/masterfrüher auf Commits gezeigt B3wurde B2, B1und jetzt zeigt es auf B, und Ihr topicZweig wurde darüber gestartet, als es noch origin/masterwar B3.

In diesem Modus wird das Reflog von origin/masterto B3als Gabelpunkt verwendet, sodass das topicüber dem aktualisierten Wert neu basiert werden kannorigin/master durch:

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

Deshalb hat der git merge-baseBefehl eine neue Option:

--fork-point::

Suchen Sie den Punkt, an dem sich ein Zweig (oder ein Verlauf, zu dem er führt <commit>) von einem anderen Zweig (oder einer Referenz) gegabelt hat <ref>.
Dies sucht nicht nur nach dem gemeinsamen Vorfahren der beiden Commits, sondern berücksichtigt auch das Reflog von, um <ref>zu sehen, ob die Geschichte, die dazu geführt <commit>hat, aus einer früheren Inkarnation des Zweigs hervorgegangen ist<ref> .


Der git pull --rebaseBefehl " " berechnet den Gabelpunkt des Zweigs, der neu basiert, unter Verwendung der Reflog-Einträge des baseZweigs "(normalerweise ein Zweig mit Fernverfolgung ), auf dem die Arbeit des Zweigs basiert, um den Fall zu bewältigen, in dem die" Basis " Zweig wurde zurückgespult und wieder aufgebaut.

Zum Beispiel, wenn die Geschichte wie folgt aussah:

  • Die aktuelle Spitze des " base" -Zweigs befindet sich bei B, aber beim früheren Abrufen wurde festgestellt, dass die Spitze früher war B3und dann B2und dann, B1 bevor zum aktuellen Commit gelangt wurde, und
  • Der Zweig, der auf der neuesten "Basis" neu basiert, basiert auf Commit B3.

es versucht , zu finden , B3indem Sie durch die Ausgabe von „ git rev-list --reflog base“ (dh B, B1, B2, B3) , bis sie eine Festschreibung feststellt , dass ein Vorfahre der aktuellen Spitze ist „ Derived (topic)“.

Intern haben wir das get_merge_bases_many(), was dies mit einem Schlag berechnen kann.
Wir möchten eine Zusammenführungsbasis zwischen Derivedund ein fiktives Zusammenführungs-Commit, das sich aus der Zusammenführung aller historischen Tipps von " base (origin/master)" ergibt .
Wenn ein solches Commit vorhanden ist, sollten wir ein einzelnes Ergebnis erhalten, das genau mit einem der Reflog-Einträge von " base" übereinstimmt .


Git 2.1 (Q3 2014) wird diese Funktion noch robuster machen: siehe Commit 1e0dacd von John Keeping ( johnkeeping)

Behandeln Sie das Szenario mit der folgenden Topologie korrekt:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

wo:

  • B'ist eine feste Version davon B, die nicht mit dem Patch identisch ist B;
  • C*und D*sind pflaster identisch Cund Djeweils und Konflikt textuell , wenn in der falschen Reihenfolge angewandt wird ;
  • Ehängt textlich ab von D.

Das korrekte Ergebnis git rebase master devist , dass Bwie die gabelPunkt identifiziert wird devund master, so dass C, D, Esind die Festschreibungen , dass Bedarf an wiedergegeben werden master; aber Cund Dsind patch-identisch mit C*und D*und können so gelöscht werden, so dass das Endergebnis ist:

o --- B' --- C* --- D* --- E  <- dev

Wenn der Gabelpunkt nicht identifiziert wird, führt die Auswahl Beines Zweigs, der enthält, B'zu einem Konflikt. Wenn die patchidentischen Commits nicht korrekt identifiziert werden, führt die Auswahl Ceines Zweigs, der D(oder gleichwertig D*) enthält, zu einem Konflikt.


Der " --fork-point" Modus von " git rebase" ging zurück, als der Befehl in der Ära 2.20 in C neu geschrieben wurde, was mit Git 2.27 (Q2 2020) korrigiert wurde.

Siehe Commit f08132f (09. Dezember 2019) von Junio ​​C Hamano ( gitster) .
(Zusammengeführt von Junio ​​C Hamano - gitster- in Commit fb4175b , 27. März 2020)

rebase: --fork-pointRegressionsfix


Abgemeldet von: Alex Torok [jc: Überarbeitete das Update und verwendete Alex 'Tests] Abgemeldet
von: Junio ​​C Hamano

" git rebase --fork-point master" funktionierte früher in Ordnung, wie es intern " git merge-base --fork-point" genannt wurde. Das wusste, wie man mit einem kurzen Referenznamen umgeht und ihn auf den vollständigen Referenznamen dwim, bevor die zugrunde liegende get_fork_point()Funktion aufgerufen wird .

Dies gilt nicht mehr, nachdem der Befehl in C neu geschrieben wurde, da sein interner Aufruf, der direkt an get_fork_point()gesendet wird, keine kurze Referenz enthält.

Verschieben Sie das Argument "dwim the refname" in die vollständige "refname" -Logik, die in "git merge-base" verwendet wird, in die zugrunde liegende get_fork_point()Funktion, sodass sich der andere Aufrufer der Funktion bei der Implementierung von "git rebase" auf die gleiche Weise verhält diese Regression.

VonC
quelle
1
Beachten Sie, dass eine Git-Push-Force jetzt (Git 1.8.5) vorsichtiger ausgeführt werden kann: stackoverflow.com/a/18505634/6309
VonC