To rebase or not to rebase, that is the question

Wir benutzen git, und wir rebasen (wenn wir mehrere commits für ein Feature erhalten wollen, rebasen wir es und machen einen no-fast-forward-merge, so dass ein “Seitengleis” entsteht, aber ohne parallele commits). Ich habe nie verstanden, was Leute gegen einen Verschiebebahnhof in git haben (es ist eben die History, wie sie sich zugetragen hat), und bin jetzt über diesen Artikel gestolpert:

Wie macht ihr das? Findet ihr die Argumentation stichhaltig?

Rebase fuer Squashen etc. ja, am besten wenn alle mit dem PR einverstanden sind, oder wenn man selber aufrauemen will, um ueberfluessige Commits (zb. Experimente, trial&error um zu sehen wi CI sich verhaelt) etc. aufzuraeumen, um nur wirklich relevante Aenderungen zu behalten und logisch zu strukturieren.

Macht es IMHO viel schwieriger die Historie zu lesen, abgesehen von den vielen nutzlosen „merge commit“ messages.

Auf Master gibt es kein rebasen (verboten, nur mergen), Feature branches erlauben es, aber man sollte aufpassen wenn mehrere Leute gleichzeitig am Branch arbeiten, git push -f ist sonst nur anfangs witzig :wink:

The importance of keeping your history true should not be underestimated. By rebasing, you are lying to yourself and to your team. You pretend that the commits were written today, when they were in fact written yesterday, based on another commit. You’ve taken the commits out of their original context, disguising what actually happened.

Das Argument höre ich oft und ich kann es überspitzen.

Wenn ich eine Datei beim Commiten vergessen habe, darf ich kein --amend nutzen, weil ich damit “mein Team anlüge und die Geschichte verfälsche”?

Nein, mein Team will den Feature Branch mit 20 Commits nicht nacheinander durchgehen, um meinen Lösungsweg inkl. aller Fehler zwischendurch nachzuvollziehen. Wenn bei dem Lösungsweg eine wichtige Erkenntnis gemacht wurde, landet sie als Kommentar im Code und/oder in der (neuen) Commitnachricht.

Viel wichtiger ist für mich git blame. Wenn mich eine Codezeile interessiert, schaue ich mir in IntelliJ direkt an, was sich dabei jemand gedacht hat. Ein “Add missing semicolon” mag zwar die Geschichte haargenau wiederzuspiegeln, bringt mir aber absolut nichts und ich muss noch weiter in die Vergangenheit schauen.

Auch das “wann” interessiert mich eher wenig. Ich mag den Code letzte Woche geschrieben haben, aber mit meinem (aktiven) Rebase sorge ich dafür, dass die Änderungen genau jetzt auf master kommen. Oder anders ausgedrückt: ich sehe, wie sich die Code-Basis insgesamt entwickelt und nicht, ob jemand den Code in Juni oder Juli schrieb.

Can you be sure that the code builds?

Ja, das nennt sich CI.


Ich verstehe die Angst vor “Änderungen” an der Git-Historie, wenn man sich mit Git nicht jenseits von commit-merge-push-pull beschäftigt hat.

Lokal kann erstmal jeder tun, was er/sie will und kann. Aber auch ein git push -f kann völlig problemlos sein (selbst auf master), wenn das Team sehr klein ist und alle Teammitglieder verstehen, was passiert und damit auch umgehen können.

Meine privaten Projekte halte ich zwischen PC und Laptop synchron und pushe diese oft mit -f. Auf dem anderen Rechner kann ich völlig gefahrlos git pull --rebase aufrufen. Das tolle daran: wurden auf den beiden Rechnern zwei unterschiedliche Commits durchgeführt, werden meine Änderungen einfach nur rebased. Habe ich ein --amend durchgeführt, wird git intern einfach ein reset --hard auf origin durchführen. Ich muss mir darüber also keine Gedanken machen.

Die zu verwendete Merge-Strategie sollte imho eine bewusste Entscheidung sein, welche im Team kommuniziert wird. Für featuremaster wird bei uns im Projekt ohnehin ein Merge erzwungen. Für masterfeature hat jeder die freie Wahl. Gegen normale Merges sprechen vor allem die unnötigen Merge-Commits, und die kann man sich auch etwa mit --no-merges ausblenden lassen. Wenn ich 20 Commits mit vielen Mergekonflikten habe, bevorzuge ich meist auch einen Merge, damit ich die Konflikte alle auf einmal lösen kann.

2 Likes

If you do get conflicts during rebasing however, Git will pause on the conflicting commit, allowing you to fix the conflict before proceeding. Solving conflicts in the middle of rebasing a long chain of commits is often confusing, hard to get right, and another source of potential errors.

Also dass ist ja wohl der absolute Quatsch.
Tatsächlich ist dies der Hauptgrund, warum ich rebase (und viele kleine Commits) benutze!

Bei mir sind die Regeln so:
Master muss immer kompilieren.
Wenn ich ein Feature entwickle wird nach jeder TDD-Mikroiteration committet, also ein Zustand, in dem das Projekt compiliert und alle Tests grün sind.

Wenn jetzt bei einem rebase Konflikte auftreten ist es entgegen der Behauptung des Autors überhaupt nicht schwierig herauszufinden, wie der gewünschte Zustand ist. Erstens können bei kleinen Änderungen auch nur wenige überschaubare Konflikte auftreten die sich mit der Message des aktuellen Kommits abgleichen lassen (wenn man vernünftige Messages macht) Und weil die Vorbedinung für den Commit war, dass das Projekt kompiliert muss das nach dem Rebase jedes einzelnen Commits wieder so sein. Wenn dann die Kompilierfehler beseitigt sind sagen mir anschließend die Unittests, ob die Funktionalität noch stimmt.

Ich finde es im Gegenteil viel schwieriger die Konflikte eines Merge aufzulösen.

Consider the case where a dependency that is still in use on feature has been removed on master

Ich kenne natürlich seine Umgebung nicht, aber in den Projekten, in denen ich bisher so gearbeitet habe wurde niemals je ein Feature aus master entfernt. Scheint mir, als würde hier ein extremer Sonderfall zum NoGo-Kriterium aufgeblasen.

Because it is our most important tool for tracking down the source of bugs in our code. Git is our safety net.

Da hat er wohl die Rolle von git falsch verstanden: Das “safey net” sind die UnitTests, nicht git.

A while back, I had to bisect through several hundred commits to track down a bug in our system. The faulty commit was located in the middle of a long chain of commits that didn’t compile, due to a faulty rebase a colleague had performed.

Ja, der Kollege hat einen Fehler gemacht. Offenbar hat er weder während der Konfliktauflösung noch nach dem Rebase versucht das Projekt zu kompilieren. Ergo wäre der Fehler auch bei einem normalen Merge aufgetreten. Denn wenn er den Kompiler nach dem Rebase nicht angeworfen hat, warum sollte er das dann nach einem Merge machen?

So how can we avoid these chains of broken commits during rebasing?

Beide vorgeschlagenen Methoden sind unbrauchbar. Die erste ist im Prinzip genau der Merge, die zweite einfach overkill und unnötig. Rebase selbst erzeugt keine Fehler (wenn keine Konflikte auftreten). Und wenn beim rebase konflikte auftreten hat mans einfacher, wenn es prüfbare Zielbedingungen gibt: Projekt kompiliert, Unittests sind grün.

Alles andere ist Unsinn.

There is; Git merge. It’s a simple, one-step process, where all conflicts are resolved in a single commit.

Was ich (wie beschrieben) ehr als Nachteil empfinde.

The resulting merge commit clearly marks the integration point between our branches,

Den erhalte ich auch wenn ich das Feature nach dem Rebase mittels --no-ff nach master merge.

What motivates people to rebase branches?

I’ve come to the conclusion that it’s about vanity. Rebasing is a purely aesthetic operation.

Für mich gibt es handfeste Vorteile gerade bei der Auflösung von Konflikten wenn ich währen der Entwicklung des Features die Aktualisierungen im master integrieren will.

The apparently clean history appeals to us as developers, but it can’t be justified, from a technical nor functional standpoint.

Das ist der einzige Punkt, in dem ich dem Autor zustimme. Der trifft aber nicht auf das rebase zu, sondern auf das weit verbreitete squash. In einigen Projekten der Community darf ein Feature, dass man beisteuern möchte, nur einen einzigen Commit haben. Das ist dann “a purely aesthetic operation” und eine Verfälschung der Historie.

bye
TT