Git-Commits werden nach einer Rebase im selben Zweig dupliziert


130

Ich verstehe das in Pro Git vorgestellte Szenario über die Gefahren der Wiederherstellung . Der Autor erklärt Ihnen grundsätzlich, wie Sie doppelte Commits vermeiden können:

Setzen Sie Commits, die Sie in ein öffentliches Repository verschoben haben, nicht erneut um.

Ich werde Ihnen meine besondere Situation mitteilen, da ich denke, dass sie nicht genau zum Pro Git-Szenario passt und ich immer noch doppelte Commits habe.

Angenommen, ich habe zwei entfernte Niederlassungen mit ihren lokalen Gegenstücken:

origin/master    origin/dev
|                |
master           dev

Alle vier Zweige enthalten die gleichen Commits und ich werde mit der Entwicklung beginnen in dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

Nach ein paar Commits drücke ich die Änderungen auf origin/dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4 C5 C6  # (2) git push
dev           : C1 C2 C3 C4 C5 C6  # (1) git checkout dev, git commit

Ich muss zurückgehen master, um eine schnelle Lösung zu finden:

origin/master : C1 C2 C3 C4 C7  # (2) git push
master        : C1 C2 C3 C4 C7  # (1) git checkout master, git commit

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C5 C6

Und zurück zu devIch ändere die Änderungen neu, um die schnelle Lösung in meine eigentliche Entwicklung aufzunehmen:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C7 C5' C6'  # git checkout dev, git rebase master

Wenn ich die Geschichte der Commits mit GitX Anzeige / gitk merke ich , dass origin/devjetzt zwei identische Commits enthält C5'und C6'die mit Git unterschiedlich sind. Wenn ich nun die Änderungen dazu origin/devdränge, ist das Ergebnis:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6 C7 C5' C6'  # git push
dev           : C1 C2 C3 C4 C7 C5' C6'

Vielleicht verstehe ich die Erklärung in Pro Git nicht ganz, deshalb möchte ich zwei Dinge wissen:

  1. Warum dupliziert Git diese Commits beim erneuten Basieren? Gibt es einen bestimmten Grund, dies zu tun, anstatt sich nur zu bewerben C5und C6danach C7?
  2. Wie kann ich das vermeiden? Wäre es klug, es zu tun?

Antworten:


86

Sie sollten hier keine Rebase verwenden, eine einfache Zusammenführung reicht aus. Das von Ihnen verlinkte Pro Git-Buch erklärt im Grunde genau diese Situation. Das Innenleben mag etwas anders sein, aber so visualisiere ich es:

  • C5und C6werden vorübergehend herausgezogendev
  • C7 wird angewendet auf dev
  • C5und C6werden darüber wiedergegeben C7, wodurch neue Unterschiede und damit neue Commits entstehen

Also, in Ihrer devBranche C5und C6praktisch nicht mehr existieren: Sie sind jetzt C5'und C6'. Wenn Sie auf drücken origin/dev, sieht Git C5'und C6'als neue Commits und heftet sie bis zum Ende des Verlaufs an. Wenn Sie sich die Unterschiede zwischen C5und C5'in ansehen origin/dev, werden Sie feststellen, dass die Zeilennummern wahrscheinlich unterschiedlich sind, obwohl der Inhalt gleich ist - was den Hash des Commits unterschiedlich macht.

Ich werde die Pro Git-Regel wiederholen : Niemals Commits neu erstellen, die jemals irgendwo anders als in Ihrem lokalen Repository vorhanden waren . Verwenden Sie stattdessen Merge.


Ich habe das gleiche Problem, wie ich jetzt meinen Remote-Zweigverlauf reparieren kann. Gibt es eine andere Option als das Löschen des Zweigs und das Neuerstellen mit Cherry Picking?
Wazery

1
@xdsy: Schau dir das und das an .
Justin 27

2
Sie sagen "C5 und C6 werden vorübergehend aus dev herausgezogen ... C7 wird auf dev angewendet". Wenn dies der Fall ist, warum erscheinen dann C5 und C6 vor C7 in der Reihenfolge der Commits für origin / dev?
KJ50

@ KJ50: Weil C5 und C6 bereits angeschoben wurden origin/dev. Beim deverneuten Basieren wird der Verlauf geändert (C5 / C6 wird vorübergehend entfernt und nach C7 erneut angewendet). Das Ändern des Verlaufs von Push-Repos ist im Allgemeinen eine wirklich schlechte Idee ™, es sei denn, Sie wissen, was Sie tun. In diesem einfachen Fall könnte das Problem gelöst werden, indem nach dem Rebase ein Force-Push von devbis origin/devausgeführt wird und alle anderen Mitarbeiter darüber informiert werden, origin/devdass sie wahrscheinlich einen schlechten Tag haben werden. Die bessere Antwort ist wiederum "
Tu

3
Eines ist zu beachten: Der Hash von C5 und C5 'ist sicherlich unterschiedlich, aber nicht, weil die Zeilennummern unterschiedlich sind, sondern für die folgenden zwei Tatsachen, von denen eine für den Unterschied ausreicht: 1) der Hash, über den wir sprechen ist der Hash des gesamten Quellbaums nach dem Festschreiben, nicht der Hash der Delta-Differenz, und daher enthält C5 'alles, was vom C7 kommt, während C5 dies nicht tut, und 2) das übergeordnete Element von C5' unterscheidet sich von C5 und diesen Informationen ist auch im Stammknoten eines Festschreibungsbaums enthalten, der sich auf das Hash-Ergebnis auswirkt.
Ozgur Murat

113

Kurze Antwort

Sie haben die Tatsache, dass Sie ausgeführt wurden git push, weggelassen , den folgenden Fehler erhalten und dann ausgeführt git pull:

To git@bitbucket.org:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Obwohl Git versucht, hilfreich zu sein, ist der Ratschlag "Git Pull" höchstwahrscheinlich nicht das, was Sie tun möchten .

Wenn du bist:

  • Die Arbeit an einem „Feature Zweig“ oder „Entwicklerzweig“ allein , dann können Sie laufen git push --forcedie Fernbedienung mit Ihrer Post-rebase Commits zu aktualisieren ( wie pro user4405677 Antwort ).
  • Wenn Sie mit mehreren Entwicklern gleichzeitig an einem Zweig arbeiten, sollten Sie ihn wahrscheinlich gar nicht erst verwendengit rebase . Um devmit Änderungen von zu aktualisieren master, sollten Sie, anstatt zu laufen git rebase master dev, laufen, git merge masterwährend Sie eingeschaltet sind dev( gemäß Justins Antwort ).

Eine etwas längere Erklärung

Jeder Commit-Hash in Git basiert auf einer Reihe von Faktoren, von denen einer der Hash des Commits ist, der davor steht.

Wenn Sie Commits neu anordnen, ändern Sie die Commit-Hashes. Durch erneutes Basieren (wenn es etwas tut) werden Commit-Hashes geändert. Das Ergebnis des Ausführens git rebase master dev, bei dem devdie Synchronisierung nicht stimmt master, erzeugt neue Commits (und damit Hashes) mit demselben Inhalt wie die aktivierten, devjedoch mit den mastervor ihnen eingefügten Commits .

Sie können auf verschiedene Weise in eine solche Situation geraten. Zwei Möglichkeiten, die ich mir vorstellen kann:

  • Sie könnten Verpflichtungen eingehen, auf masterdie Sie Ihre devArbeit stützen möchten
  • Sie hätten Commits auf , devdie bereits an einem entfernten geschoben worden sind, die Sie dann zu ändern gehen (reword Commit - Nachrichten, Neuordnungs Commits, Squash Commits, etc.)

Lassen Sie uns besser verstehen, was passiert ist - hier ein Beispiel:

Sie haben ein Repository:

2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0

Anfänglicher Satz linearer Commits in einem Repository

Anschließend ändern Sie die Commits.

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(Hier müssen Sie mein Wort dafür nehmen: Es gibt eine Reihe von Möglichkeiten, Commits in Git zu ändern. In diesem Beispiel habe ich die Zeit von geändert C3, aber Sie fügen neue Commits ein, ändern Commit-Nachrichten, ordnen Commits neu an. Squashing Commits zusammen usw.)

ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0

Das gleiche gilt für neue Hashes

Hier ist es wichtig zu beachten, dass die Commit-Hashes unterschiedlich sind. Dies ist das erwartete Verhalten, da Sie etwas (irgendetwas) an ihnen geändert haben. Das ist okay, ABER:

Ein Diagrammprotokoll, das zeigt, dass der Master nicht mit der Fernbedienung synchron ist

Wenn Sie versuchen zu pushen, wird ein Fehler angezeigt (und ein Hinweis darauf, dass Sie ausgeführt werden sollten git pull).

$ git push origin master
To git@bitbucket.org:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Wenn wir laufen git pull, sehen wir dieses Protokoll:

7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Oder anders gezeigt:

Ein Diagrammprotokoll mit einem Zusammenführungs-Commit

Und jetzt haben wir doppelte Commits vor Ort. Wenn wir laufen git pushwürden, würden wir sie an den Server senden.

Um dieses Stadium nicht zu erreichen, hätten wir laufen können git push --force(wo wir stattdessen gelaufen sind git pull). Dies hätte unsere Commits mit den neuen Hashes ohne Probleme an den Server gesendet. Um das Problem zu diesem Zeitpunkt zu beheben, können wir vor dem Ausführen auf Folgendes zurücksetzen git pull:

Schauen Sie sich das reflog ( git reflog) an, um zu sehen, was der Commit-Hash war, bevor wir ausgeführt wurden git pull.

070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0

Oben sehen wir, dass dies ba7688adas Commit war, an dem wir vor dem Laufen teilgenommen haben git pull. Mit diesem Commit-Hash in der Hand können wir auf that ( git reset --hard ba7688a) zurücksetzen und dann ausführen git push --force.

Und wir sind fertig.

Aber warte, ich habe die Arbeit weiterhin auf die doppelten Commits gestützt

Wenn Sie irgendwie nicht bemerkt haben, dass die Commits dupliziert wurden, und weiterhin auf doppelten Commits arbeiten, haben Sie sich wirklich selbst durcheinander gebracht. Die Größe des Chaos ist proportional zur Anzahl der Commits, die Sie auf den Duplikaten haben.

Wie das aussieht:

3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Git-Protokoll mit linearen Commits auf doppelten Commits

Oder anders gezeigt:

Ein Protokolldiagramm, das lineare Commits auf doppelten Commits zeigt

In diesem Szenario möchten wir die doppelten Commits entfernen, aber die darauf basierenden Commits beibehalten - wir möchten C6 bis C10 beibehalten. Wie bei den meisten Dingen gibt es eine Reihe von Möglichkeiten, dies zu tun:

Entweder:

  • Erstellen Sie einen neuen Zweig beim letzten duplizierten Commit 1 , cherry-pickjedes Commit (C6 bis einschließlich C10) für diesen neuen Zweig, und behandeln Sie diesen neuen Zweig als kanonisch.
  • Führen Sie aus git rebase --interactive $commit, wo $commitsich das Commit vor den beiden duplizierten Commits befindet. 2 . Hier können wir die Zeilen für die Duplikate sofort löschen.

1 Es spielt keine Rolle, welche der beiden Sie wählen ba7688aoder ob sie gut 2a2e220funktionieren.

2 Im Beispiel wäre es 85f59ab.

TL; DR

Stellen Sie ein advice.pushNonFastForwardauf false:

git config --global advice.pushNonFastForward false

1
Es ist in Ordnung, den Ratschlägen "git pull ..." zu folgen, solange man erkennt, dass die Auslassungspunkte die Option "--rebase" (auch bekannt als "-r") verbergen. ;-)
G. Sylvie Davies

4
Ich würde empfehlen, git push's --force-with-leaseheutzutage zu verwenden, da es eine bessere Standardeinstellung ist
Whymarrh

4
Es ist entweder diese Antwort oder eine Zeitmaschine. Vielen Dank!
ZeMoon

Sehr nette Erklärung ... Ich bin auf ein ähnliches Problem gestoßen, bei dem mein Code 5-6 Mal dupliziert wurde, nachdem ich wiederholt versucht hatte, die Basis neu zu erstellen ... nur um sicherzugehen, dass der Code mit dem Master auf dem neuesten Stand ist ... aber jedes Mal, wenn er gepusht wurde Neue Commits in meiner Filiale, die auch meinen Code duplizieren. Können Sie mir bitte sagen, ob Force Push (mit Lease-Option) hier sicher ist, wenn ich der einzige Entwickler bin, der in meiner Branche arbeitet? Oder ist es besser, den Master in meinen zu verschmelzen, anstatt ihn neu zu gründen?
Dhruv Singhal

12

Ich denke, Sie haben bei der Beschreibung Ihrer Schritte ein wichtiges Detail übersprungen. Genauer gesagt, Ihr letzter Schritt git pushauf dev hätte Ihnen tatsächlich einen Fehler gegeben, da Sie normalerweise keine nicht schnell vorlaufenden Änderungen vornehmen können.

Sie haben dies also git pullvor dem letzten Push getan , was zu einem Zusammenführungs-Commit mit C6 und C6 'als Eltern führte, weshalb beide im Protokoll aufgeführt bleiben. Ein hübscheres Protokollformat hat möglicherweise deutlicher gemacht, dass es sich um zusammengeführte Zweige doppelter Commits handelt.

Oder Sie haben stattdessen eine git pull --rebase(oder ohne explizite, --rebasewenn dies durch Ihre Konfiguration impliziert wird) erstellt, die die ursprünglichen C5 und C6 in Ihrem lokalen Entwickler zurückzog (und die folgenden auf neue Hashes umstellte, C7 'C5' 'C6'). ').

Ein Ausweg hätte git push -fdarin bestehen können, den Push zu erzwingen, wenn der Fehler aufgetreten ist, und C5 C6 vom Ursprung zu löschen. Wenn jedoch jemand anderes sie auch ziehen ließ, bevor Sie sie abwischten, würden Sie viel mehr Probleme haben. Grundsätzlich müsste jeder, der C5 C6 hat, spezielle Schritte unternehmen, um sie loszuwerden. Genau aus diesem Grund heißt es, Sie sollten niemals etwas neu veröffentlichen, was bereits veröffentlicht wurde. Es ist jedoch immer noch machbar, wenn das "Veröffentlichen" in einem kleinen Team erfolgt.


1
Das Weglassen von git pullist entscheidend. Ihre Empfehlung git push -f, obwohl gefährlich, ist wahrscheinlich das, wonach Leser suchen.
Whymarrh

Tatsächlich. Damals, als ich die Frage schrieb, die ich tatsächlich gestellt hatte git push --force, nur um zu sehen, was Git tun würde. Ich habe seitdem eine Menge über Git gelernt und heutzutage rebasegehört es zu meinem normalen Workflow. Ich git push --force-with-leasevermeide es jedoch, die Arbeit eines anderen zu überschreiben.
Elitalon

Verwenden --force-with-leaseist eine gute Standardeinstellung, ich werde auch einen Kommentar unter meiner Antwort
hinterlassen

2

Ich fand heraus, dass dieses Problem in meinem Fall die Folge eines Git-Konfigurationsproblems ist. (Mit Pull and Merge)

Beschreibung des Problems:

Sympthome: Commits, die nach dem Rebase auf dem untergeordneten Zweig dupliziert wurden, was zahlreiche Zusammenführungen während und nach dem Rebase impliziert.

Workflow: Hier sind die Schritte des Workflows, den ich ausgeführt habe:

  • Arbeit am "Features-Zweig" (Kind von "Develop-Zweig")
  • Änderungen auf "Features-Branch" festschreiben und pushen
  • Kasse "Entwicklungszweig" (Hauptzweig der Funktionen) und arbeite damit.
  • Übernehmen und pushen Sie Änderungen an "Develop-Branch"
  • Kasse "Features-Zweig" und ziehe Änderungen aus dem Repository (falls jemand anderes Arbeit festgeschrieben hat)
  • Setzen Sie "Features-Branch" auf "Develop-Branch" um
  • Drücken Sie die Kraft der Änderungen auf "Feature-Branch"

Als Konsequenz dieses Workflows Duplizieren aller Commits von "Feature-Branch" seit vorheriger Rebase ... :-(

Das Problem war auf das Ziehen von Änderungen des untergeordneten Zweigs vor der erneuten Basis zurückzuführen. Die Standard-Pull-Konfiguration von Git ist "Merge". Dies ändert die Indizes der Commits, die für den untergeordneten Zweig ausgeführt werden.

Die Lösung: Konfigurieren Sie in der Git-Konfigurationsdatei Pull so, dass es im Rebase-Modus funktioniert:

...
[pull]
    rebase = preserve
...

Hoffe es kann JN Grx helfen


1

Möglicherweise haben Sie aus einem anderen Zweig als Ihrem aktuellen Zweig gezogen. Zum Beispiel haben Sie möglicherweise vom Master abgerufen, wenn Ihre Niederlassung entwickelt wird. Git zieht pflichtbewusst doppelte Commits ein, wenn es aus einem nicht verfolgten Zweig gezogen wird.

In diesem Fall können Sie Folgendes tun:

git reset --hard HEAD~n

wo n == <number of duplicate commits that shouldn't be there.>

Stellen Sie dann sicher, dass Sie aus dem richtigen Zweig ziehen, und führen Sie dann Folgendes aus:

git pull upstream <correct remote branch> --rebase

Durch Ziehen mit --rebasewird sichergestellt, dass Sie keine überflüssigen Commits hinzufügen, die den Commit-Verlauf beeinträchtigen könnten.

Hier ist ein bisschen Hand halten für Git Rebase.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.