1043 Stimmen

Git-Arbeitsablauf und Fragen zu rebase vs. merge

Ich verwende Git jetzt seit ein paar Monaten für ein Projekt mit einem anderen Entwickler. Ich habe mehrere Jahre Erfahrung mit SVN Also bringe ich wohl eine Menge Gepäck mit in die Beziehung.

Ich habe gehört, dass Git hervorragend zum Verzweigen und Zusammenführen geeignet ist, aber bisher sehe ich das nicht. Sicher, das Verzweigen ist ganz einfach, aber wenn ich versuche, zusammenzuführen, geht alles zum Teufel. Das bin ich von SVN gewohnt, aber mir scheint, dass ich gerade ein minderwertiges Versionierungssystem gegen ein anderes eingetauscht habe.

Mein Partner sagt mir, dass meine Probleme daher rühren, dass ich willkürlich zusammenführen will, und dass ich in vielen Situationen rebase statt merge verwenden sollte. Hier ist zum Beispiel der Arbeitsablauf, den er festgelegt hat:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

Im Wesentlichen erstellen Sie einen Feature-Zweig, führen IMMER einen Re-Base von Master auf den Zweig durch und führen den Zweig wieder auf Master zusammen. Wichtig dabei ist, dass der Zweig immer lokal bleibt.

Hier ist der Arbeitsablauf, mit dem ich begonnen habe

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

Es gibt zwei wesentliche Unterschiede (denke ich): Ich verwende "merge always" anstelle von "rebasing", und ich schiebe meinen Feature-Branch (und meine Feature-Branch-Commits) in das entfernte Repository.

Der Grund für die Remote-Zweigstelle ist, dass ich meine Arbeit während der Arbeit sichern möchte. Unser Repository wird automatisch gesichert und kann wiederhergestellt werden, wenn etwas schief geht. Bei meinem Laptop ist das nicht der Fall, oder nicht so gründlich. Deshalb hasse ich es, Code auf meinem Laptop zu haben, der nicht irgendwo anders gespiegelt ist.

Meine Begründung für die Zusammenführung anstelle von rebase ist, dass merge eine Standardfunktion und rebase eine erweiterte Funktion zu sein scheint. Mein Gefühl sagt mir, dass es sich bei dem, was ich vorhabe, nicht um ein fortgeschrittenes Setup handelt, so dass Rebase unnötig sein sollte. Ich habe sogar das neue Buch Pragmatic Programming über Git durchgelesen, und dort wird merge ausführlich behandelt und rebase kaum erwähnt.

Wie auch immer, ich folgte meinem Arbeitsablauf auf einem aktuellen Zweig, und als ich versuchte, es zurück zu Master zusammenzuführen, ging alles zum Teufel. Es gab Unmengen von Konflikten mit Dingen, die nicht von Bedeutung sein sollten. Die Konflikte ergaben für mich einfach keinen Sinn. Ich brauchte einen Tag, um alles zu sortieren, und gipfelte schließlich in einem erzwungenen Push auf den entfernten Master, da mein lokaler Master alle Konflikte gelöst hat, aber der entfernte Master immer noch nicht zufrieden war.

Was ist der "richtige" Arbeitsablauf für so etwas? Git soll das Verzweigen und Zusammenführen super-einfach machen, und ich sehe es einfach nicht.

Aktualisierung 2011-04-15

Dies scheint eine sehr beliebte Frage zu sein, also dachte ich, ich bringe meine zweijährige Erfahrung seit meiner ersten Frage auf den neuesten Stand.

Es stellt sich heraus, dass der ursprüngliche Arbeitsablauf richtig ist, zumindest in unserem Fall. Mit anderen Worten: Das ist es, was wir tun, und es funktioniert:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

Unser Arbeitsablauf ist ein wenig anders, denn wir neigen dazu, Folgendes zu tun Kürbisfusionen anstelle von rohen Zusammenführungen. ( Anmerkung: T ) Dies ermöglicht es uns, unseren gesamten Feature-Zweig in einen einzigen Commit auf Master zu verwandeln. Dann löschen wir unseren Feature-Zweig. Auf diese Weise können wir unsere Commits auf Master logisch strukturieren, auch wenn sie auf unseren Zweigen ein wenig unordentlich sind. Wir gehen also folgendermaßen vor:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

Squash Merge Kontroverse - Wie mehrere Kommentatoren bemerkt haben, wird beim Squash Merge die gesamte Historie des Funktionszweigs gelöscht. Wie der Name schon sagt, werden alle Commits zu einem einzigen zusammengeführt. Für kleine Features ist das sinnvoll, da es sie in einem einzigen Paket zusammenfasst. Für größere Features ist es wahrscheinlich keine gute Idee, vor allem wenn die einzelnen Commits bereits atomar sind. Es kommt wirklich auf die persönlichen Vorlieben an.

Github und Bitbucket (andere?) Pull Requests - Falls du dich fragst, wie merge/rebase mit Pull Requests zusammenhängt, empfehle ich, alle oben genannten Schritte zu befolgen, bis du bereit bist, zurück zu Master zu mergen. Anstatt manuell mit Git zusammenzuführen, akzeptieren Sie einfach den PR. Beachten Sie, dass dies kein Squash Merge ist (zumindest nicht standardmäßig), aber Non-Squash, Non-Fast-Forward ist die akzeptierte Merge-Konvention in der Pull Request Community (soweit ich weiß). Genauer gesagt, funktioniert es so:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

Ich habe Git lieben gelernt und möchte nie wieder zu SVN zurückkehren. Wenn Sie Probleme haben, bleiben Sie einfach dabei und irgendwann werden Sie das Licht am Ende des Tunnels sehen.

439voto

Edward Anderson Punkte 12871

TL;DR

Ein Git-Rebase-Workflow schützt Sie nicht vor Leuten, die schlecht in der Konfliktlösung sind, oder vor Leuten, die an einen SVN-Workflow gewöhnt sind, wie es in Vermeiden von Git-Katastrophen: Eine blutige Geschichte . Das macht die Konfliktlösung für sie nur noch mühsamer und erschwert es, sich von einer schlechten Konfliktlösung zu erholen. Verwenden Sie stattdessen diff3, damit es gar nicht erst so schwierig wird.


Rebase Workflow ist nicht besser für die Konfliktlösung!

Ich bin sehr für die Aufarbeitung der Geschichte. Wenn jedoch Wenn ich jemals auf einen Konflikt stoße, breche ich die Neubasierung sofort ab und führe stattdessen eine Zusammenführung durch! Es macht mich wirklich fertig, dass Leute einen Rebase-Workflow als bessere Alternative zu einem Merge-Workflow zur Konfliktlösung empfehlen (und genau darum ging es in dieser Frage).

Wenn es bei einer Zusammenführung "zum Teufel geht", dann geht es auch bei einem Rebase "zum Teufel", und möglicherweise noch viel mehr! Hier ist der Grund dafür:

Grund Nr. 1: Konflikte einmal auflösen, statt einmal für jede Übertragung

Wenn Sie rebase statt merge durchführen, müssen Sie die Konfliktlösung so oft durchführen, wie Sie Commits zum rebase haben, für denselben Konflikt!

Reales Szenario

Ich verzweige von Master, um eine komplizierte Methode in einem Zweig zu überarbeiten. Meine Refactoring-Arbeit umfasst insgesamt 15 Commits, während ich daran arbeite, sie zu überarbeiten und Code-Reviews zu erhalten. Ein Teil meiner Überarbeitung besteht darin, die gemischten Tabulatoren und Leerzeichen zu korrigieren, die zuvor im Master vorhanden waren. Das ist notwendig, aber leider wird es zu Konflikten mit jeder Änderung kommen, die später an dieser Methode in Master vorgenommen wird. Während ich an dieser Methode arbeite, macht jemand eine einfache, legitime Änderung an der gleichen Methode im Master-Zweig, die mit meinen Änderungen zusammengeführt werden sollte.

Wenn es an der Zeit ist, meinen Zweig wieder mit Master zusammenzuführen, habe ich zwei Möglichkeiten:

git merge: Ich gerate in einen Konflikt. Ich sehe die Änderung, die sie an Master vorgenommen haben, und führe sie mit (dem Endprodukt) meines Zweigs zusammen. Erledigt.

git rebase: Ich bekomme einen Konflikt mit meinem erste verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner zweite verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner dritte verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner vierte verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner fünfte verpflichten. Ich löse den Konflikt und fahre mit dem Rebase fort. Ich erhalte einen Konflikt mit meiner sechste verpflichten. Ich löse den Konflikt und fahre mit dem Rebase fort. Ich erhalte einen Konflikt mit meiner siebte verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner achte verpflichten. Ich löse den Konflikt und fahre mit dem Rebase fort. Ich erhalte einen Konflikt mit meiner neunte verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner zehnte verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner elfte verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner zwölfte verpflichten. Ich löse den Konflikt und fahre mit dem Rebase fort. Ich erhalte einen Konflikt mit meiner dreizehnte verpflichten. Ich löse den Konflikt und fahre mit dem Rebase fort. Ich erhalte einen Konflikt mit meiner vierzehnte verpflichten. Ich löse den Konflikt und fahre mit der Neubasierung fort. Ich erhalte einen Konflikt mit meiner fünfzehnte verpflichten. Ich löse den Konflikt und fahre mit dem Rebase fort.

Sie wollen mich wohl verarschen, wenn este ist Ihr bevorzugter Arbeitsablauf. Alles was es braucht, ist eine Leerraumkorrektur, die mit einer Änderung auf Master kollidiert, und jeder Commit wird zum Konflikt und muss aufgelöst werden. Und dies ist ein einfach Szenario mit nur einem Leerzeichen-Konflikt. Der Himmel bewahre Sie davor, dass Sie einen echten Konflikt haben, der größere Code-Änderungen in verschiedenen Dateien mit sich bringt und den Sie lösen müssen dass mehrere Male.

Mit all den zusätzlichen Konfliktlösungen, die Sie durchführen müssen, erhöht sich nur die Wahrscheinlichkeit, dass du wirst einen Fehler machen . Aber Fehler sind in Git in Ordnung, da man sie rückgängig machen kann, richtig? Außer natürlich...

Grund Nr. 2: Bei rebase gibt es kein Rückgängigmachen!

Ich denke, wir sind uns alle einig, dass die Lösung von Konflikten schwierig sein kann, und dass manche Menschen darin sehr schlecht sind. Sie kann sehr fehleranfällig sein, und deshalb ist es großartig, dass Git das Rückgängigmachen so einfach macht!

Wenn Sie zusammenführen einen Zweig, erstellt Git einen Merge-Commit, der verworfen oder geändert werden kann, wenn die Konfliktlösung schlecht verläuft. Auch wenn Sie den schlechten Merge-Commit bereits in das öffentliche/autoritative Repository verschoben haben, können Sie mit git revert um die durch die Zusammenführung eingeführten Änderungen rückgängig zu machen und die Zusammenführung in einer neuen Zusammenführungsübergabe korrekt wiederherzustellen.

Wenn Sie die Basis neu setzen eine Zweigstelle, und wenn die Konfliktlösung falsch gemacht wird, ist man aufgeschmissen. Jeder Commit enthält nun die fehlerhafte Zusammenführung, und Sie können nicht einfach den Rebase* wiederholen. Bestenfalls müssen Sie zurückgehen und jeden der betroffenen Commits ändern. Das macht keinen Spaß.

Nach einem Rebase ist es unmöglich festzustellen, was ursprünglich zu den Commits gehörte und was durch eine schlechte Konfliktlösung hinzugefügt wurde.

*Es kann möglich sein, ein Rebase rückgängig zu machen, wenn man die alten Referenzen aus den internen Git-Protokollen ausgraben kann, oder wenn man einen dritten Zweig erstellt, der auf den letzten Commit vor dem Rebase verweist.

Konfliktlösung auf die leichte Schulter nehmen: diff3 verwenden

Nehmen wir zum Beispiel diesen Konflikt:

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Wenn man sich den Konflikt ansieht, ist es unmöglich zu sagen, was die einzelnen Zweige geändert haben oder was ihre Absicht war. Das ist meiner Meinung nach der Hauptgrund, warum Konfliktlösung verwirrend und schwierig ist.

diff3 ist die Rettung!

git config --global merge.conflictstyle diff3

Wenn Sie diff3 verwenden, hat jeder neue Konflikt einen dritten Abschnitt, den zusammengeführten gemeinsamen Vorgänger.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Untersuchen Sie zunächst den fusionierten gemeinsamen Vorfahren. Vergleichen Sie dann beide Seiten, um die Absicht der einzelnen Zweige zu ermitteln. Sie können sehen, dass HEAD EmailMessage in TextMessage geändert hat. Seine Absicht ist es, die verwendete Klasse in TextMessage zu ändern und dabei dieselben Parameter zu übergeben. Sie können auch sehen, dass die Absicht von feature-branch darin besteht, false statt true für die Option :include_timestamp zu übergeben. Um diese Änderungen zusammenzuführen, kombinieren Sie die Absicht beider:

TextMessage.send(:include_timestamp => false)

Generell:

  1. Vergleichen Sie den gemeinsamen Vorfahren mit jedem Zweig und bestimmen Sie, welcher Zweig die einfachste Veränderung aufweist
  2. Wenden Sie diese einfache Änderung auf die Version des Codes im anderen Zweig an, so dass sie sowohl die einfachere als auch die komplexere Änderung enthält
  3. Entfernen Sie alle Abschnitte des Konfliktcodes mit Ausnahme des Abschnitts, in den Sie die Änderungen gerade zusammengeführt haben

Stellvertreter: Auflösen durch manuelles Anwenden der Änderungen des Zweigs

Schließlich sind einige Konflikte auch mit diff3 schwer zu verstehen. Das passiert vor allem dann, wenn diff gemeinsame Zeilen findet, die semantisch nicht gemeinsam sind (z.B. haben beide Zweige zufällig eine Leerzeile an der gleichen Stelle!). Zum Beispiel ändert ein Zweig die Einrückung des Körpers einer Klasse oder ordnet ähnliche Methoden neu an. In diesen Fällen kann eine bessere Lösungsstrategie darin bestehen, die Änderung auf beiden Seiten der Zusammenführung zu untersuchen und den Diff manuell auf die andere Datei anzuwenden.

Schauen wir uns an, wie wir einen Konflikt in einem Szenario lösen könnten, in dem das Zusammenführen origin/feature1 donde lib/message.rb Konflikte.

  1. Entscheiden Sie, ob unser aktuell ausgecheckter Zweig ( HEAD o --ours ) oder den Zweig, den wir zusammenführen ( origin/feature1 o --theirs ) ist eine einfacher anzuwendende Änderung. Die Verwendung von diff mit dreifachem Punkt ( git diff a...b ) zeigt die Änderungen, die am b seit seiner letzten Abweichung von a oder mit anderen Worten: Vergleichen Sie den gemeinsamen Vorfahren von a und b mit b.

    git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
    git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
  2. Sehen Sie sich die kompliziertere Version der Datei an. Damit werden alle Konfliktmarkierungen entfernt und die von Ihnen gewählte Seite verwendet.

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
  3. Wenn Sie die komplizierte Änderung ausgecheckt haben, rufen Sie das Diff der einfacheren Änderung auf (siehe Schritt 1). Wenden Sie jede Änderung aus diesem Diff auf die konfliktbehaftete Datei an.

387voto

VonC Punkte 1117238

"Konflikte" bedeuten "parallele Entwicklungen desselben Inhalts". Wenn also während einer Zusammenführung "alles zum Teufel geht", bedeutet dies, dass Sie massive Entwicklungen an derselben Gruppe von Dateien haben.

Der Grund, warum ein Rebase dann besser ist als ein Merge, ist folgender:

  • Sie überschreiben Ihre lokale Commit-Historie mit der des Masters (und wenden dann Ihre Arbeit erneut an, um etwaige Konflikte zu lösen)
  • der endgültige Merge wird sicherlich ein "Fast Forward" sein, da er die gesamte Commit-Historie des Masters hat und nur Ihre Änderungen wieder übernehmen wird.

Ich bestätige, dass der korrekte Arbeitsablauf in diesem Fall (Entwicklungen an einem gemeinsamen Satz von Dateien) folgender ist erst umbasieren, dann zusammenführen .

Das bedeutet jedoch, dass, wenn Sie Ihren lokalen Zweig pushen (aus Gründen der Datensicherung), dieser Zweig nicht von jemand anderem gezogen (oder zumindest verwendet) werden sollte (da die Commit-Historie durch das nachfolgende Rebase neu geschrieben wird).


Zu diesem Thema (Arbeitsablauf "rebase then merge"), barraponto erwähnt in den Kommentaren zwei interessante Beiträge, beide von randyfay.com :

Mit dieser Technik kommt Ihre Arbeit immer auf den öffentlichen Zweig, wie ein Patch, der auf dem neuesten Stand ist HEAD .

(eine ähnliche Technik existiert für Basar )

35voto

Alex Gontmakher Punkte 1337

In meinem Arbeitsablauf rebase ich so oft wie möglich (und ich versuche, es oft zu tun). Dadurch, dass ich die Diskrepanzen nicht anhäufen lasse, werden die Anzahl und die Schwere der Kollisionen zwischen den Zweigen drastisch reduziert).

Aber auch in einem größtenteils rebase-basierten Workflow gibt es einen Platz für Zusammenführungen.

Erinnern Sie sich daran, dass bei der Zusammenführung ein Knoten entsteht, der zwei Elternteile hat. Betrachten Sie nun die folgende Situation: Ich habe zwei unabhängige Funktionszweige A und B und möchte nun Dinge im Funktionszweig C entwickeln, die sowohl von A als auch von B abhängen, während A und B überprüft werden.

Was ich dann tue, ist Folgendes:

  1. Erstellen (und Auschecken) Sie den Zweig C über A.
  2. Zusammenführen mit B

Jetzt enthält Zweig C die Änderungen von A und B, und ich kann mit der Entwicklung fortfahren. Wenn ich eine Änderung an A vornehme, rekonstruiere ich den Graphen der Zweige auf folgende Weise:

  1. einen Zweig T auf der neuen Spitze von A erzeugen
  2. T mit B verschmelzen
  3. C auf T umbasieren
  4. Zweig T löschen

Auf diese Weise kann ich tatsächlich beliebige Graphen von Verzweigungen pflegen, aber etwas Komplexeres als die oben beschriebene Situation zu tun, ist bereits zu komplex, da es kein automatisches Werkzeug gibt, um das Rebasing durchzuführen, wenn sich das Elternteil ändert.

27voto

Scott Brown Punkte 301

Verwenden Sie git push origin --mirror unter KEINER UMSTÄNDE.

Sie werden nicht gefragt, ob Sie sich sicher sind, dass Sie dies tun wollen, und Sie sollten sich besser sicher sein, denn es werden alle entfernten Zweige gelöscht, die sich nicht auf Ihrem lokalen Rechner befinden.

http://twitter.com/dysinger/status/1273652486

15voto

knweiss Punkte 465

Ich habe eine Frage, nachdem ich Ihre Erklärung gelesen habe: Könnte es sein, dass Sie nie eine

git checkout master
git pull origin
git checkout my_new_feature

vor der Durchführung von "git rebase/merge master" in Ihrem Funktionszweig?

Denn Ihr Der Master-Zweig wird nicht automatisch aus dem Repository Ihres Freundes aktualisiert. Sie müssen dies mit dem git pull origin . D.h. vielleicht würden Sie immer von einem sich nie ändernden lokalen Master-Zweig rebasen? Und dann, wenn Sie pushen, pushen Sie in ein Repository, das (lokale) Commits enthält, die Sie nie gesehen haben, und daher schlägt der Push fehl.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X