Best Practice-Tipps für Git: Rebases nutzen anstatt nur zu Mergen

Ich erlebe es als Software-Entwickler leider sehr oft, dass Entwickler git nicht richtig verstehen und es gerade so hinkriegen in einer GUI (sei es Sourcetree oder eine IDE wie IntelliJ) die Branches ineinander zu mergen. Dabei entstehen sehr schwierig zu verstehende git-Historien. Das ist nicht optimal, doch mit meinen wenigen Tipps lernt ihr git hoffentlich besser zu verstehen und warum ihr nicht immer mergen solltet. Bei git geht es nicht nur darum stets den aktuellsten Stand zu haben, sondern es geht vor allem um Nachvollziehbarkeit! Hier ein kleines Beispiel: Die linke Seite ist „gut“, die rechte Seite ist „schlecht“/unübersichtlich:

Symbolbild: Gute Git-Historie (links) vs schlechte/unübersichtliche Historie (rechts)

Symbolbild: Gute git-Historie (links) vs schlechte/unübersichtliche Historie (rechts)

Die Sache ist, es funktioniert auch auf die hässliche Art und man hat irgendwie den aktuellsten Stand der Software. Aber wenn man dann mal sinnvoll zurückverfolgen will, wie und wo Änderungen in die Software gekommen sind, dann sollte man spätestens da überlegen, wie sinnvoll es ist, alle Branches kreuz und quer durcheinander zu mergen. Die Tipps beruhen auf langjähriger Erfahrung mit git auf der Kommandozeile.

Erklärung zu den obigen zwei git-Verläufen

Links seht ihr wie Änderungen von dev auf main gemerged werden und Änderungen vom Feature-Branch in den dev-Branch gemerged werden. Alle Änderungen lassen sich gut verfolgen und merges (neue Features) einfach rückgängig machen. Rechts hingegen ist ein größeres Hin und Her-Gemerge zu sehen, beispielsweise wurde dev in den Feature-Branch gemerged, und der Feature-Branch dann wieder in dev. Don’t do this (on the right)!

Main-Branch, Dev-Branch, Feature-Branches

Ich empfehle idealerweise einen main-Branch, auf dem lediglich stabile Versionen laufen. Dieser Branch ist von der laufenden Entwicklung, dem dev-Branch, ausgekoppelt. Auf main sollte force-pushen verboten sein und auf dev nur gemacht werden, wenn man genau weiß, was man tut. Auf main wird grundsätzlich nie direkt commited, sondern immer nur von dev reingemerged. Für Features deren Entwicklung länger dauert, gibt es eigene Feature-Branches. Kleinere Sachen können direkt auf dev commited werden. Man sollte Feature-Branches bei größeren Arbeiten verwenden, damit die git-Historie nicht eine abwechselnde Folge von Commits verschiedener Teile der Software ist. Eine Folge von Commits in der git-Historie sollte immer zusammengehörig sein bzw. anders gesagt: Eine Folge von Commits zu einem bestimmten Feature sollte nicht von anderen Commits unterbrochen werden.

Merging und Rebasing

Feature-Branches müssen regelmäßig auf origin/dev gerebased werden. Das bedeutet, dass wir den aktuellen Stand vom dev-Branch nehmen und als neue Basis des Feature-Branches nehmen. Nehmen wir als Beispiel eine Anwendung, die von Angular 4 auf Angular 5 geupdatet werden muss. Da muss man beispielweise alle <template>-Tags durch <ng-template> ersetzen. Macht man das auf seinem Feature-Branch und ein Entwickler hat in der Zeit aber eine Komponente auf dev hinzugefügt, so ist diese dem Feature-Branch nicht bekannt. Wir wollen Features im Idealfall als „ganze Einheiten“ in dev reinmergen, ohne den Feature-Branch mehrfach aufzumachen/zu nutzen. Daher rebasen wir (z.B.)[git checkout dev && git pull && git checkout my-feature-branch &&] git rebase dev. Dabei treten eventuell Konflikte auf, die gelöst werden müssen. Das geht aber einfach auf der Kommandozeile, alternativ aber auch insbesondere sehr einfach in IntelliJ IDE. Wurde unser Feature-Branch aber bereits einmal auf den Remote-Server gepusht, brauchen wir nach dem Rebase (den wir lokal vorgenommen haben) einen Force-Push, da sich die Commit-IDs geändert haben, da wir die Historie geändert haben. Das kann tricky sein, wenn mehrere Kolleg:innen auf einem Feature-Branch arbeiten. Dabei ist dann eine manuelle Abstimmung notwendig, die allerdings mit einer besseren Historie belohnt wird.

Ist ein Feature-Branch gerebased, können wir ihn auch ohne Merge-Konflikt in dev reinmergen. Branches bitte immer mit --no-ff (no-fast-forward) reinmergen! Dabei werden zwar grundsätzlich immer Merge-Commits erzeugt, statt die Commits „einfach mitzunehmen und auf den Branch zu setzen“ („fast forward“), man hat aber eine ganz klare Sicht wann welches Feature reingekommen ist und man kann es mit einem revert einfach wieder rausnehmen, ohne zu schauen ab welchem Commit genau die Entwicklung eines neuen Features begann.

Die git-Historie (git hist, alias für git log --graph --oneline --decorate [-all], so wie die git-Branches-Übersicht in IntelliJ unten) ist wie folgt aufgebaut: Links der main-Branch, rechts daneben der dev-Branch, rechts davon die Feature-Branches. Gemerged wird nur von „rechts nach links“. Wenn man „von links nach rechts“ merged, also zum Beispiel von dev auf einen Feature-Branch, dann ist das nicht schön. Das seht ihr oben im Bild in der rechten Hälfte.

Verhindern dev in dev zu mergen

Wenn mehrere Entwickler:innen auf dem dev-Branch arbeiten, dann wird es passieren, dass man nicht pushen kann, weil man Commits vom Remote-Branch noch nicht hat. In dem Fall bitte git pull --rebase (bzw. in der IDE auf „Rebase“ klicken) ausführen, denn sonst wird dev in dev gemerged und das ist nicht schön. Das wird euch schneller auf die Füße fallen als ihr denkt und macht die Historie komplexer. Oder als Standardeinstellung von git: $ git config --global pull.rebase true

Nutzt Ammend-Commits

Oft hat man dann den Fall, dass man etwas commited und danach merkt, es fehlte doch noch eine Stelle oder irgendwo ist ein Tippfehler. Nutzt in den Fällen doch bitte git commit --amend (bzw. Klickt den Schalter oben Rechts im IntelliJ-Commit-Fenster). So wird die Änderung noch an den letzten Commit angehängt. So kann man die Gesamtzahl der Commits kleiner halten, was zu mehr Übersichtlichkeit führt, da keine unnötigen Mini-Commits mehr entstehen. Hier gilt allerdings: Hat man vor einem Ammend-Commit bereits gepusht so muss ein Force-Push folgen, da die Commit-ID geändert wird.

Fazit

Es lohnt sich wirklich, wenn ihr euch mal ein paar Minuten Zeit nehmt und wenigstens ein paar Grundlagen von git lernt und dabei stets im Hinterkopf habt: „Ich muss ein Feature jederzeit einfach reverten können! Die Entwicklungshistorie muss nachvollziehbar sein!“ Rebases sind ein wichtiges Feature für saubere git-Historien. Rebases bereiten außerdem Merges von beispielsweise großen Feature-Branches vor, da das Mergen von großen Features dann ohne Merge-Konflikte funktioniert! Bitte hört auf Git auschließlich in Sourcetree und eurer IDE zu benutzen und übt git auf der Kommandozeile. Dadurch lernt ihr git sehr gut verstehen und das wird sich auszahlen – versprochen. 🙂

Weiterführende Links

Philipp Schuster

Ich bin Philipp, studiere Informatik an der TU Dresden und arbeite als Werkstudent im Bereich Software-Entwicklung bei T-Systems MMS. Ich bin 22 Jahre alt und beschäftige mich in meiner Freizeit gerne mit meinem Blog, Programierung, Technik, aber auch mit Joggen und vielen anderen Dingen. Get To Know Me oder schreibt mich an!

Das könnte dich auch interessieren …

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.