Warum bevorzugen so viele Projekte "git rebase" gegenüber "git merge"?

68

Einer der Vorteile der Verwendung eines DVCS ist der Workflow zum Bearbeiten, Festschreiben und Zusammenführen (gegenüber dem Bearbeiten, Zusammenführen und Festschreiben , der häufig von einem CVCS erzwungen wird). Indem jede einzelne Änderung unabhängig von Zusammenführungen im Repository aufgezeichnet wird, wird sichergestellt, dass die DAG den tatsächlichen Stammbaum des Projekts genau wiedergibt.

Warum reden so viele Websites davon, "Merge Commits vermeiden" zu wollen? Erschwert das Zusammenführen von Pre-Commit- oder Re-Basing-Vorgängen nach dem Zusammenführen nicht das Isolieren von Regressionen, das Zurücksetzen früherer Änderungen usw.?

Klarstellungspunkt: Das Standardverhalten für ein DVCS ist das Erstellen von Merge-Commits. Warum sprechen so viele Orte von dem Wunsch, eine lineare Entwicklungsgeschichte zu sehen, in der diese Merge-Commits ausgeblendet sind?

Jace Browning
quelle
18
Werkzeuge machen es nicht immer richtig. Und wenn sie es falsch verstehen, Junge, verstehen sie es falsch.
Oded
2
@Oded beziehen Sie sich auf automatische Merge-Commits (wie die, die von erstellt wurden git pull)? Ich kann sehen, warum das problematisch sein könnte, aber meine Frage bezieht sich auf alle Merge-Commits.
Jace Browning
Mögliche Duplikate von Sind häufig auftretende komplizierte Zusammenführungskonflikte ein Anzeichen von Problemen? "Zusammenführungen und Neuzusammenlegungen sollten genau die gleichen Konflikte verursachen, für jene inhärenten Konflikte, die ein Mensch lösen muss (dh zwei Entwickler, die dieselbe Codezeile ändern) ..."
gnat
Ein Gedanke, den ich nach dem Posten meiner Antwort hatte, dass IMO nicht genau als Antwort passt: git pull --rebase == svn up(Gleichheit, nicht Gleichheit)
Izkata
In einem zentralisierten Speicher wie SourceSafe wurden die Änderungen weiterhin in regelmäßigen Abständen eingecheckt. Sobald jedoch ein stabiles Plateau erreicht wurde, wurden alle Dateien ab einem bestimmten Datum mit Labels versehen. Dann können Sie zumindest Fehler entweder vor oder nach einer solchen oder einer solchen Version oder einer Rebase-Zeile korrelieren .
Andyz Smith

Antworten:

64

Leute möchten Merge-Commits vermeiden, da dies das Protokoll schöner macht. Ernsthaft. Es sieht aus wie die zentralen Protokolle, mit denen sie aufgewachsen sind, und vor Ort können sie ihre gesamte Entwicklung in einer einzigen Filiale durchführen. Abgesehen von dieser Ästhetik gibt es keine Vorteile und zusätzlich zu den von Ihnen erwähnten auch einige Nachteile, z. B. dass es konfliktträchtig ist, direkt von einem Kollegen zu ziehen, ohne den "zentralen" Server zu durchlaufen.

Karl Bielefeldt
quelle
5
Sie sind der einzige, der meine Frage richtig verstanden hat. Wie kann ich das deutlicher machen?
Jace Browning
12
Vielleicht umformulieren zu "Was sind die Vorteile einer linearen Entwicklungsgeschichte?"
Karl Bielefeldt
11
"Oft tun Sie dies, um sicherzustellen, dass Ihre Commits sauber auf einem Remote-Zweig angewendet werden - möglicherweise in einem Projekt, zu dem Sie beitragen möchten, das Sie jedoch nicht verwalten. In diesem Fall erledigen Sie Ihre Arbeit Wenn Sie bereit waren, Ihre Patches an das Hauptprojekt zu senden, muss der Maintainer keine Integrationsarbeit leisten - nur einen schnellen Vorlauf oder eine saubere Anwendung. " git-scm.com/book/en/Git-Branching-Rebasing
Andyz Smith
12
Sie machen es so, als wäre es nur kosmetisch, aber es ist tatsächlich praktisch: Es ist viel einfacher zu verstehen, was in einer linearen Historie vor sich geht, als von einer komplizierten DAG mit sich kreuzenden und verschmelzenden Linien.
Daniel Hershcovich
20
"Abgesehen von dieser Ästhetik gibt es keine Vorteile" - darüber weiß ich nichts. Es ist wirklich schwer zu verstehen, was in Ihrem Verlauf vor sich geht, wenn Ihr Verlauf wie eine Leiterplatte aussieht. (Angenommen, mehrere Teammitglieder mit jeweils eigenen Zweigen und Workflows.)
Archagon,
46

In zwei Worten: git bisect

Mit einem linearen Verlauf können Sie die tatsächliche Fehlerquelle genau bestimmen.


Ein Beispiel. Dies ist unser Anfangscode:

def bar(arg=None):
    pass

def foo():
    bar()

Zweig 1 führt einige Umgestaltungen durch, argdie nicht mehr gültig sind:

def bar():
    pass

def foo():
    bar()

Branch 2 hat eine neue Funktion, die verwendet werden muss arg:

def bar(arg=None):
    pass

def foo():
    bar(arg=1)

Es wird keine Zusammenführungskonflikte geben, es wurde jedoch ein Fehler behoben. Glücklicherweise wird dieser eine beim Kompilieren erwischt, aber wir haben nicht immer so viel Glück. Wenn sich der Fehler als unerwartetes Verhalten und nicht als Kompilierungsfehler manifestiert, wird er möglicherweise erst nach ein oder zwei Wochen gefunden. An diesem Punkt git bisectzur Rettung!

Oh Mist. Das sieht es so:

(master: green)
|             \_________________________
|                \                      \
(master: green)  (branch 1: green)     (branch 2: green)
|                 |                     |
|                 |                     |
(master/merge commit: green)            |
|                         ______________/
|                        /
(master/merge commit: red)
|
...days pass...
|
(master: red)

Wenn wir also losschicken git bisect, um das Commit zu finden, das den Build unterbrochen hat, wird es ein Merge-Commit lokalisieren. Nun, das hilft ein wenig, aber es zeigt im Wesentlichen auf ein Paket von Commits, nicht auf ein einzelnes. Alle Vorfahren sind grün. Auf der anderen Seite erhalten Sie beim Neu-Basieren eine lineare Historie:

(master: green)
|
(master: green)
|
(all branch 1 commits: green)
|
(some branch 2 commits: green)
|
(branch 2 commit: red)
|
(remaining branch 2 commits: red)
|
...days pass...
|
(master: still red)

Nun git bisectwird auf das genaue Feature-Commit hingewiesen, das den Build unterbrochen hat. Im Idealfall wird in der Festschreibungsmeldung erläutert, was gut genug ist, um einen weiteren Refaktor durchzuführen und den Fehler sofort zu beheben.

Der Effekt wird nur in großen Projekten verstärkt, in denen die Betreuer nicht den gesamten Code geschrieben haben, sodass sie sich nicht unbedingt daran erinnern, warum ein bestimmtes Commit ausgeführt wurde bzw. wofür die einzelnen Zweige bestimmt waren. Das Festlegen des genauen Commits (und das anschließende Überprüfen der Commits in der Umgebung) ist daher eine große Hilfe.


Trotzdem bevorzuge ich (derzeit) das Zusammenführen. Durch erneutes Aufrufen eines Release-Zweigs können Sie Ihren linearen Verlauf für die Verwendung mit verwenden git bisect, während der wahre Verlauf für die tägliche Arbeit beibehalten wird.

Izkata
quelle
2
Danke für diese Erklärung. Können Sie erläutern, warum Sie immer noch die Fusion bevorzugen? Ich weiß, dass viele Leute in den meisten Situationen lieber fusionieren als neu zu gründen, aber ich habe nie verstanden, warum.
Philip
@Philip Ich zögere ein wenig, da ich git nur für ein paar Monate bei kleinen persönlichen Projekten verwendet habe, niemals in einem Team. Wenn ich jedoch nachverfolge, was ich in letzter Zeit getan habe, gitk --allist es äußerst nützlich, den Fluss von Commits über Filialen hinweg zu sehen (zumal ich in der Regel zu einem bestimmten Zeitpunkt dreistufige Filialen habe und abhängig von meiner Stimmung bei jeden Tag zwischen diesen umschalte) die Zeit).
Izkata
16
Die Zusammenführung ist jedoch das Problem . Daher möchten Sie ein Zusammenführungs-Commit als bisectErgebnis. Es ist einfach genug, die einzelnen Commits mit einem blameoder einem anderen zu finden, bisectwenn Sie tiefer graben möchten. Bei einem linearen Verlauf erfolgt die Zusammenführung außerhalb des Buchs, sodass Sie nicht mehr feststellen können, dass eine fehlerhafte Zusammenführung das Problem war. Es sieht aus wie ein Idiot, der ein Argument verwendet, das nicht existiert, insbesondere wenn das Argument zuvor um mehrere Commits entfernt wurde. Der Versuch, herauszufinden, warum er das tun würde, ist viel schwieriger, wenn Sie die Geschichte zerstören.
Karl Bielefeldt
1
Stimme dieser Antwort wirklich nicht zu. Das erneute Zerlegen zerstört gelegentlich die Brauchbarkeit von Bisect. Mit Ausnahme der Head-Commits einer Rebase können die Commits einer Rebase möglicherweise nicht ausgeführt werden. Beispiel: Sie haben sich bei A verzweigt und B, C, D, jemanden hat E gedrückt. Sie sind B und C und arbeiten, basieren aber wieder auf E, Sie sind B und C, sind nicht lauffähig oder können ausgeführt werden und funktionieren nicht. Der beste Fall ist, Sie geben auf, der schlimmste Fall, Sie denken, B oder C enthält das Problem, wenn es in Wirklichkeit D ist.
Lan
4
@Izkata Warum benutzt du Bisect? Weil Bugs / Fehler passieren. Es ist eine demütigende Realität. Vielleicht war "Ziehen der Maus nach links" vorher nicht Teil der automatisierten / manuellen Testliste und jetzt stellen wir fest, dass diese Funktion defekt ist, aber wir wissen, dass sie früher funktionierte.
Lan
17

Kurz gesagt, weil das Verschmelzen oft ein anderer Ort ist, an dem etwas schief gehen kann, und es nur einmal schief gehen muss, damit die Menschen Angst davor haben, es erneut zu tun (wenn Sie so wollen, einmal zweimal schüchtern gebissen).

Nehmen wir also an, wir arbeiten an einem neuen Bildschirm für die Kontoverwaltung, und es stellt sich heraus, dass im Workflow für neue Konten ein Fehler entdeckt wurde. OK, wir gehen zwei getrennte Wege - Sie beenden die Kontoverwaltung und ich behebe den Fehler mit "Neue Konten". Da wir uns beide mit Konten befassen, haben wir mit sehr ähnlichem Code gearbeitet - vielleicht mussten wir sogar dieselben Codeteile anpassen.

Momentan haben wir zwei verschiedene, aber voll funktionsfähige Versionen von Software. Wir haben beide einen Commit für unsere Änderungen ausgeführt, wir haben unseren Code beide pflichtbewusst getestet und sind unabhängig davon sehr zuversichtlich, dass wir eine großartige Arbeit geleistet haben. Was jetzt?

Nun, es ist Zeit zu fusionieren, aber ... Mist, was passiert jetzt? Wir könnten sehr gut von zwei funktionierenden Software-Sätzen zu einem vereinheitlichten, schrecklich kaputten Teil einer neu fehlerhaften Software übergehen, bei der Ihre Kontoverwaltung nicht funktioniert und neue Konten kaputt sind und ich nicht einmal weiß, ob der alte Fehler noch vorhanden ist .

Vielleicht war die Software schlau und es gab einen Konflikt und bestand darauf, dass wir sie anleiten. Scheiße, ich setze mich hin und sehe, dass Sie einen komplexen Code hinzugefügt haben, den ich nicht sofort verstehe. Ich denke, es widerspricht den Änderungen, die ich vorgenommen habe ... Ich frage Sie, und wenn Sie eine Minute Zeit haben, überprüfen Sie, und Sie sehen meinen Code, den Sie nicht verstehen. Einer oder beide von uns müssen sich die Zeit nehmen, sich hinzusetzen, eine ordnungsgemäße Zusammenführung vorzunehmen und möglicherweise die ganze Sache noch einmal zu testen, um sicherzustellen, dass wir sie nicht kaputt gemacht haben.

In der Zwischenzeit schreiben 8 andere Typen Code wie die Sadisten, die sie sind. Ich habe ein paar kleine Fehlerbehebungen vorgenommen und sie eingereicht, bevor ich wusste, dass wir einen Zusammenführungskonflikt hatten sind für den Nachmittag weg oder stecken in einer Besprechung oder was auch immer. Vielleicht sollte ich einfach mal Urlaub machen. Oder Karrieren wechseln.

Um diesem Albtraum zu entkommen, haben einige Menschen große Angst vor Engagement (was gibt es sonst noch Neues?). In solchen Szenarien sind wir natürlich risikoscheu - es sei denn, wir glauben, dass wir scheißen und es trotzdem vermasseln werden. In diesem Fall handeln die Leute mit rücksichtsloser Hingabe. Seufzer

Hier bitteschön. Ja, moderne Systeme wurden entwickelt, um diesen Schmerz zu lindern, und es sollte möglich sein, einfach zurückzutreten und zu rebasieren und zu debasieren und freebase und hanglide und so weiter.

Aber es ist viel mehr Arbeit, und wir möchten nur den Knopf auf der Mikrowelle drücken und ein 4-Gänge-Menü zubereiten, bevor wir Zeit haben, eine Gabel zu finden, und alles fühlt sich so unerfüllt an - Code ist Arbeit, es ist produktiv, es ist Sinnvolles, aber anmutiges Zusammenführen zählt einfach nicht.

Programmierer müssen in der Regel ein großartiges Arbeitsgedächtnis entwickeln und dann die Tendenz haben, alle Junk - und Variablennamen und das Scoping sofort zu vergessen, sobald sie das Problem gelöst haben, und einen Zusammenführungskonflikt (oder schlimmer noch: a falsch gehandhabte Zusammenführung) ist eine Aufforderung, an Ihre Sterblichkeit erinnert zu werden.

BrianH
quelle
5
Erstaunlich formuliert, Sir!
Southpaw Hare
10
Dies ist die Antwort darauf, warum Menschen Angst vor dem Zusammenführen haben und nicht, warum Zusammenführungs-Commits von so vielen als schlecht angesehen werden.
Jay
23
Das Problem mit dieser Antwort ist, dass dies für eine Rebase immer noch zutrifft . Immerhin führt eine Rebase immer noch eine Zusammenführung durch, bei der Sie eine Codezeile geändert haben, die bereits geändert wurde. Ich denke, das ist ein Problem mit der Art und Weise, wie die Frage gestellt wurde.
Deworde
1
Ich habe diese Antwort abgelehnt, weil sie, da sie immer noch beredt ist, über den Punkt hinausgeht, imho.
Eugene
5
Dies scheint bei Rebase vs. Merge-Merge überhaupt nicht zu scheitern.
Andyz Smith
5

Durch das erneute Reduzieren wird ein verschiebbarer Verzweigungspunkt bereitgestellt, der das Zurückschieben von Änderungen an die Basislinie vereinfacht. Auf diese Weise können Sie einen Zweig mit langer Laufzeit so behandeln, als wäre es eine lokale Änderung. Bei Verzweigungen werden Änderungen von der Basislinie akkumuliert, die in den Änderungen enthalten sind, die wieder zur Basislinie zusammengeführt werden.

Beim Zusammenführen verbleibt Ihre Basislinie am ursprünglichen Verzweigungspunkt. Wenn Sie Änderungen aus der Linie, von der Sie abgezweigt sind, für ein paar Wochen zusammenführen, haben Sie jetzt viele Änderungen von Ihrem Verzweigungspunkt aus, von denen sich viele in Ihrer Grundlinie befinden. Dies macht es schwierig, Ihre Änderungen in Ihrer Branche zu identifizieren. Das Zurückschieben von Änderungen an die Grundlinie kann zu Konflikten führen, die nicht mit Ihren Änderungen zusammenhängen. Im Falle von Konflikten ist es möglich, inkonsistente Änderungen zu pushen. Laufende Zusammenführungen erfordern viel Aufwand und es ist relativ einfach, Änderungen zu verlieren.

Durch erneutes Starten wird der Verzweigungspunkt auf die neueste Version auf Ihrer Basislinie verschoben. Alle Konflikte, auf die Sie stoßen, betreffen nur Ihre Änderungen. Das Verschieben von Änderungen ist viel einfacher. Konflikte werden in der lokalen Niederlassung durch eine zusätzliche Rebase behandelt. Im Falle widersprüchlicher Pushs muss das Problem mit der Änderung behoben werden, wenn der letzte Push die Änderung vornimmt.

BillThor
quelle
1
Alles, was es tut, ist, neue Änderungen auf eine Ebene herabzustufen, die als "das Falsche" betrachtet wird, weil sie die einzige Änderung sind . Wenn alle alten Änderungen in die Zusammenführung einbezogen werden, sind alle Änderungen auf einer Ebene, in Bezug darauf, ob sie heute "das Richtige" sind.
Andyz Smith
@AndyzSmith Ich würde die neuen Änderungen nicht für falsch halten . Wenn Sie herausfinden möchten, was geändert wird und welche Maßnahmen ergriffen werden sollten, sind Sie hier richtig . Nach dem Neustart können Sie die alten Änderungen ignorieren, da sie Teil Ihrer Baseline sind.
BillThor
Richtig. Wenn Sie sich nicht sicher sind, wie hoch Ihre Baseline ist, dh ob die Änderungen, die Sie bereits zur Zusammenführung bereit gemacht haben, "richtig" sind, führen Sie keine Neueinstufung durch.
Andyz Smith
Und wenn Sie einen Änderungsschub von jemandem erhalten, der den Build bricht, weil sein Funktionsprototyp nicht mit einem vorhandenen Prototyp übereinstimmt, wissen Sie sofort, dass der neue Typ falsch liegt. Sie müssen nicht vermuten, dass der neue Typ möglicherweise den richtigen Prototyp hat und die zuvor nicht herangezogenen, nicht angewendeten Änderungen an den Änderungssätzen diejenigen mit dem falschen Prototyp sind.
Andyz Smith
4

Die automatisierten Tools können immer besser sicherstellen, dass der Zusammenführungscode kompiliert und ausgeführt wird, wodurch syntaktische Konflikte vermieden werden. Sie können jedoch nicht die Abwesenheit logischer Konflikte garantieren, die durch Zusammenführungen entstehen können. Eine „erfolgreiche“ Zusammenführung gibt Ihnen das Gefühl eines falschen Vertrauens, wenn sie in Wirklichkeit nichts garantiert und Sie alle Ihre Tests wiederholen müssen.

Das eigentliche Problem beim Verzweigen und Zusammenführen ist meines Erachtens, dass es die sprichwörtliche Dose die Straße hinunterwirft. Sie können eine Woche lang "Ich arbeite nur in meiner eigenen kleinen Welt" sagen und alle Probleme behandeln, die später auftauchen. Aber das Beheben von Fehlern ist immer schneller / billiger, wenn sie frisch sind. Wenn alle Code-Zweige zusammengeführt werden, vergessen Sie möglicherweise bereits einige Nuancen der durchgeführten Aktionen.

Nehmen Sie die beiden oben genannten Probleme zusammen, und Sie könnten sich in einer Situation befinden, in der es einfacher und einfacher ist, dass alle am selben Stamm arbeiten und Konflikte fortlaufend lösen, selbst wenn sie die aktive Entwicklung etwas verlangsamen.

MrFox
quelle
4
Dies ist die Antwort darauf, warum Menschen Angst vor dem Zusammenführen haben und nicht, warum Zusammenführungs-Commits von so vielen als schlecht angesehen werden.
Jay
Also, dieser Trunk, auf den Sie sich beziehen, beinhaltet das Rebase? Bitte erläutern.
Andyz Smith
@AndyzSmith "trunk" ist die Bezeichnung von svn für das Äquivalent zum "master" -Zweig von git
Izkata,
@Izkata also diese Antwort befürwortet, weder Zusammenführen noch Wiederherstellen dann zu verwenden?
Andyz Smith
@AndyzSmith Ja, so habe ich es auch gelesen. Und ich bin damit nicht einverstanden; Nachdem ich in der vorgeschlagenen Weise mit 7 anderen Entwicklern in einem Team gearbeitet habe, schlage ich dies nicht vor. Wir hätten Feature-Zweige häufiger verwenden sollen als wir, aber die meisten haben Angst vor dem Zusammenführen (wie BrianDHall in seiner Antwort ausführt).
Izkata
0

Ein weiterer relevanter Punkt ist folgender: Mit dem Rebasing kann ich problemlos ein Feature in meinem Release-Zweig auswählen oder zurücksetzen.

Bruno Schäpper
quelle
1
Ist dies näher ausgeführt?
Akavall
Klar :-) Im Git Flow erstellen wir regelmäßig neue Release Zweige. Wenn nach der Erstellung ein Fehler im Entwicklungszweig behoben wird git cherry-pick -x <commit>, wenden wir ihn auf den Veröffentlichungszweig an. Oder wir möchten vielleicht etwas für die Veröffentlichung rückgängig machen, mit git revert <commit>. Wenn <commit>es sich um eine Verschmelzung handelt, wird es haarig. Es ist möglich, soweit ich weiß, aber nicht einfach zu tun. Bei der Verwendung von Rebase ist jede "Zusammenführung" ein riesiger, aber regelmäßiger Commit, der leicht auswählbar und rückgängig zu machen ist.
Bruno Schäpper
0

Drei Dinge wurden in keiner der Antworten gesagt:

  • Innerhalb eines Zweiges unterscheiden:

    • Das Unterscheiden zwischen einem beliebigen Commit-Paar in einem Zweig wird extrem schwierig, wenn es Merge-Commits gibt.
  • Jeweils ein Element zusammenführen:

    • Wenn Sie den Unterschied zwischen zwei Zweigen auflösen, geschieht die Zusammenführung in der Regel auf einmal und alle Zusammenführungskonflikte, die Sie ausführen müssen, ohne dass der Kontext berücksichtigt wird, der für den Konflikt verantwortlich war. Wenn Sie einen Neuaufbau durchführen, stoppt der Neuaufbau an dem Punkt, an dem der Konflikt aufgetreten ist, und ermöglicht Ihnen, ihn in diesem Kontext zu lösen.
  • Aufräumen vor dem Push:

    • Wenn Sie ein Fehler-Commit gemacht haben, das Sie später beheben müssen, können Sie Commits kombinieren, teilen, ändern und verschieben, wenn Sie die interaktive Rebase nicht aktiviert haben. Während Sie dies nach dem Zusammenführen immer noch tun können, wird es sehr schwierig, wenn Sie eine Zusammenführungsgrenze kombinieren / teilen / ändern / überqueren möchten.
Catskul
quelle