Eine aufgeräumte, lineare Git-Historie

Eines der Dinge, die in vielen Git-basierten Projekten übersehen werden, ist der Wert einer linearen Commit-Historie. Tatsächlich entmutigen viele Tools und Richtlinien Git-Workflows, die auf eine lineare Historie abzielen. Ich finde das traurig, da eine aufgeräumte Historie sehr wertvoll ist und es einen einfachen Workflow gibt, der eine lineare Historie sicherstellt.

Linearer vs. nichtlinearer Verlauf

Ein linearer Verlauf ist einfach ein Git-Verlauf, in dem alle Commits aufeinander folgen. I.e. sie werden keine Zusammenführungen von Zweigen mit unabhängigen Commit-Historien finden.

1 - nichtlinear-vs-linear

Warum willst du eine lineare Geschichte?

Eine lineare Historie ist nicht nur aufgeräumt und logisch, sondern auch nützlich, wenn:

  • Blick in die Geschichte. Eine nichtlineare Geschichte kann sehr schwer zu verfolgen sein – manchmal bis zu dem Punkt, dass die Geschichte einfach unverständlich ist.
  • Änderungen zurückverfolgen. Zum Beispiel: “Wurde Feature A vor oder nach Bugfix B eingeführt?”.
  • Fehler aufspüren. Git hat eine sehr nette Funktion namens Git bisect , die verwendet werden kann, um schnell zu finden, welches Commit einen Fehler oder eine Regression eingeführt hat. Bei einer nichtlinearen Historie wird die Verwendung dieser Halbierung jedoch schwierig oder sogar unmöglich.
  • Änderungen rückgängig machen. Angenommen, Sie haben ein Commit gefunden, das eine Regression verursacht hat, oder Sie möchten ein Feature entfernen, das in einer bestimmten Version nicht verfügbar sein sollte. Mit etwas Glück (wenn sich Ihr Code nicht zu sehr geändert hat) können Sie die unerwünschten Commits einfach mit Git revert . Wenn Sie jedoch eine nichtlineare Historie haben, möglicherweise mit vielen verzweigungsübergreifenden Zusammenführungen, wird dies erheblich schwieriger.

Es gibt wahrscheinlich noch eine Handvoll Situationen, in denen eine lineare Historie sehr wertvoll ist, je nachdem, wie Sie Git verwenden.

Punkt ist: Je weniger linear Ihre Geschichte ist, desto weniger wertvoll ist sie.

Ursachen einer nichtlinearen Historie

Kurz gesagt, jeder Merge-Commit ist eine potenzielle Quelle einer nichtlinearen Historie. Es gibt jedoch verschiedene Arten von Merge-Commits.

Von einem Themenzweig in den Master zusammenführen

Wenn Sie mit Ihrem Themenzweig fertig sind und ihn in den Master integrieren möchten, besteht eine gängige Methode darin, den Themenzweig in den Master zusammenzuführen. Vielleicht etwas entlang der Linien:

git checkout mastergit pullgit merge --no-ff my-topic-branch

Eine nette Eigenschaft bei dieser Methode ist, dass Sie die Informationen darüber beibehalten, welche Commits Teil Ihres Themenzweigs waren (eine Alternative wäre, “–no-ff” wegzulassen, was es Git ermöglichen würde, einen schnellen Vorlauf anstelle einer Zusammenführung durchzuführen.

Das Problem beim Zusammenführen zum Master tritt auf, wenn Ihr Themenzweig auf einem alten Master anstelle des neuesten Masters basiert. In diesem Fall erhalten Sie unweigerlich eine nichtlineare Geschichte.

2 - merging-old-branch

Ob dies ein häufiges Problem sein wird oder nicht, hängt weitgehend davon ab, wie aktiv das Git-Repository ist, wie viele Entwickler gleichzeitig arbeiten usw.

Merge from master into a topic branch

Manchmal möchten Sie Ihren Zweig auf den neuesten Master aktualisieren (z. es gibt einige neue Funktionen auf Master, die Sie in Ihren Themenzweig aufnehmen möchten, oder Sie stellen fest, dass Sie Ihren Themenzweig nicht in Master zusammenführen können, da Konflikte vorliegen).

Eine gängige Methode, die von einigen sogar empfohlen wird, besteht darin, die Spitze des Masters in Ihren Themenzweig einzufügen. Dies ist eine wichtige Quelle der nichtlinearen Geschichte!

3 - merging-master-into-topic

Die Lösung: Rebase!

Git rebase ist eine sehr nützliche Funktion, die Sie verwenden sollten, wenn Sie einen linearen Verlauf wünschen. Einige finden das Konzept des Rebasing umständlich, aber es ist wirklich ganz einfach: wiederholen Sie die Änderungen (Commits) in Ihrem Zweig über einem neuen Commit.

Zum Beispiel können Sie Git rebase verwenden, um die Wurzel Ihres Themenzweigs von einem alten Master in die Spitze des neuesten Masters zu ändern. Angenommen, Sie haben Ihren Themenzweig ausgecheckt, können Sie Folgendes tun:

git fetch origingit rebase origin/master

4 - rebasing

Beachten Sie, dass die entsprechende Zusammenführungsoperation darin besteht, die Spitze des Masters in Ihren Themenzweig einzufügen (wie in der vorherigen Abbildung dargestellt). Der resultierende Inhalt der Dateien in Ihrem Themenzweig wäre derselbe, unabhängig davon, ob Sie eine Rebase oder eine Zusammenführung durchführen. Die Geschichte ist jedoch anders (linear vs. nichtlinear!).

Das klingt alles gut und gut. Es gibt jedoch einige Einschränkungen bei Rebase, die Sie beachten sollten.

Vorbehalt 1: Rebase erstellt neue Commits

Durch das Rebasing eines Zweigs werden tatsächlich neue Commits erstellt. Die neuen Commits haben andere SHA: s als die alten Commits. Dies ist normalerweise kein Problem, aber Sie werden auf Probleme stoßen, wenn Sie einen Zweig neu erstellen, der außerhalb Ihres lokalen Repositorys vorhanden ist (z. B. wenn Ihr Zweig bereits im Ursprung vorhanden ist).

Wenn Sie einen Rebased-Zweig in ein Remote-Repository verschieben würden, das bereits denselben Zweig enthält (jedoch mit dem alten Verlauf), würden Sie:

  1. Muss den Zweig erzwingen (zB git push --force-with-lease ), da Git es Ihnen sonst nicht erlaubt, einen neuen Verlauf in einen vorhandenen Zweig zu verschieben. Dadurch wird der Verlauf für den angegebenen Remote-Zweig effektiv durch einen neuen Verlauf ersetzt.
  2. Kann eine andere Person sehr unglücklich machen, da ihre lokale Version des angegebenen Zweigs nicht mehr mit dem Zweig auf der Fernbedienung übereinstimmt, was zu allen möglichen Problemen führen kann.

Vermeiden Sie im Allgemeinen, den Verlauf eines Zweigs auf einem Remote-Computer zu überschreiben (die einzige Ausnahme besteht darin, den Verlauf eines Zweigs, der gerade überprüft wird, neu zu schreiben – je nachdem, wie Ihr Codeüberprüfungssystem funktioniert –, aber das ist eine andere Diskussion).

Wenn Sie einen Branch rebasen müssen, der für andere auf einem Remote-Computer freigegeben ist, besteht ein einfacher Workflow darin, einen neuen Branch zu erstellen, den Sie rebasen, anstatt den ursprünglichen Branch zu rebasen. Angenommen, Sie haben my-topic-branch ausgecheckt, können Sie Folgendes tun:

git checkout -b my-topic-branch-2git fetch origingit rebase origin/mastergit push -u origin my-topic-branch-2

… und dann sagen Sie den Leuten, die an my-topic-branch arbeiten, dass sie stattdessen an my-topic-branch-2 weiterarbeiten sollen. Der alte Zweig ist dann effektiv veraltet und sollte nicht wieder mit dem Master zusammengeführt werden.

Vorbehalt 2: Das Lösen von Konflikten in einer Rebase kann mehr Arbeit bedeuten als in einer Zusammenführung

Wenn Sie einen Konflikt in einer Zusammenführungsoperation erhalten, lösen Sie alle Konflikte als Teil dieses Zusammenführungs-Commits auf.

Bei einer Rebase-Operation können Sie jedoch möglicherweise einen Konflikt für jedes Commit in dem Zweig erhalten, den Sie rebasen.

Tatsächlich werden Sie oft feststellen, dass Sie, wenn Sie einen Konflikt in einem Commit erhalten, in nachfolgenden Commits in Ihrem Zweig auf verwandte (sehr ähnliche) Konflikte stoßen, einfach weil Commits in einem Themenzweig tendenziell verwandt sind (z. B. dieselben Teile des Codes ändern).

Der beste Weg, Konflikte zu minimieren, besteht darin, zu verfolgen, was im Master passiert, und zu vermeiden, dass ein Themenzweig zu lange ohne Rebasing ausgeführt wird. Der Umgang mit kleinen Konflikten im Vorfeld ist ab und zu einfacher, als sie alle am Ende in einem großen, glücklichen Konflikt-Chaos zu bewältigen.

Einige Warnungen für GitHub-Benutzer

GitHub ist in vielen Dingen großartig. Es ist fantastisch für Git-Hosting, und es hat eine wunderbare Web-Oberfläche mit Code-Browsing, schöne Markdown-Funktionalität, Gist, etc.

Pull Requests hingegen haben einige Funktionen, die den linearen Git-Verlauf aktiv durchkreuzen. Es wäre sehr willkommen, wenn GitHub diese Probleme tatsächlich beheben würde, aber bis dahin sollten Sie sich der Mängel bewusst sein:

“Merge Pull Request” ermöglicht nichtlineare Zusammenführungen zum Master

Es kann verlockend sein, den grünen, freundlich aussehenden “Merge Pull Request” -Button zu drücken, um einen Themenzweig zum Master zusammenzuführen. Vor allem, wenn es heißt “Dieser Zweig ist mit dem Basiszweig auf dem neuesten Stand. Das Zusammenführen kann automatisch durchgeführt werden”.

GitHub Merge Pull Request

Was GitHub hier wirklich sagt, ist, dass der Zweig ohne Konflikte zum Master zusammengeführt werden kann. Es wird nicht überprüft, ob der Pull-Request-Zweig auf dem neuesten Master basiert.

Mit anderen Worten, wenn Sie eine lineare Historie wünschen, müssen Sie sicherstellen, dass der Pull-Request-Zweig selbst auf dem neuesten Master basiert. Soweit ich das beurteilen kann, sind solche Informationen über die GitHub–Weboberfläche nicht verfügbar (es sei denn, Sie verwenden “geschützte Zweige” – siehe unten).

Selbst wenn die Pull-Anforderung ordnungsgemäß neu basiert, gibt es keine Garantie dafür, dass die Zusammenführungsoperation in der GitHub-Weboberfläche atomar ist (dh. jemand kann Änderungen an den Master senden, bevor Ihr Zusammenführungsvorgang abgeschlossen ist – und GitHub wird sich nicht beschweren).

Die einzige Möglichkeit, sicherzustellen, dass Ihre Zweige ordnungsgemäß auf dem neuesten Master basieren, besteht darin, den Zusammenführungsvorgang lokal durchzuführen und den resultierenden Master manuell zu verschieben. Etwas entlang der Linien:

git checkout mastergit pullgit checkout my-pullrequest-branchgit rebase mastergit checkout mastergit merge --no-ff my-pullrequest-branchgit push origin master

Wenn Sie Pech haben und es jemandem gelingt, Änderungen zwischen Ihren Pull- und Push-Vorgängen an den Master zu übertragen, wird Ihre Push-Operation abgelehnt. Dies ist jedoch eine gute Sache, da es garantiert, dass Ihre Operation atomar ist. Nur git reset --hard origin/master und wiederholen Sie die obigen Schritte, bis es durchgeht.

Hinweis: Respektieren Sie Ihre Projektrichtlinien, wenn Sie Code überprüfen und testen. Wenn Sie beispielsweise automatische Tests (Builds, statische Analysen, Komponententests usw.) als Teil einer Pull-Anforderung ausführen, sollten Sie Ihren neu basierten Zweig wahrscheinlich erneut senden (entweder mit git push -f oder durch Öffnen eines neuen) PR) anstatt nur den Master-Zweig manuell zu aktualisieren.

Geschützte Verzweigungsfunktionalität ermutigt Zusammenführungen vom Master

Wenn Sie in Ihrem GitHub-Projekt geschützte Verzweigungen und Statusprüfungen verwenden, erhalten Sie tatsächlich Schutz vor dem Zusammenführen eines Pull-Request-Zweigs in den Master, es sei denn, er basiert auf dem neuesten Master (Ich denke, der Grund dafür ist, dass Statusprüfungen, die im PR-Zweig durchgeführt werden, nach dem Zusammenführen zum Master weiterhin gültig sein sollten).

Jedoch … Wenn der Pull-Request-Zweig nicht auf dem neuesten Master basiert, wird eine benutzerfreundliche Schaltfläche mit dem Namen “Zweig aktualisieren” mit dem Text “Dieser Zweig ist mit dem Basiszweig veraltet. Führen Sie die neuesten Änderungen vom Master in diesen Zweig ein”.

github-update-branch

Zu diesem Zeitpunkt ist es am besten, den Zweig lokal neu zu erstellen und ihn auf die Pull-Anforderung zu erzwingen. Angenommen, Sie haben my-pullrequest-branch ausgecheckt, tun Sie:

git fetch origingit rebase origin/mastergit push -f

Leider spielen GitHub Pull Requests nicht gut mit Force Pushs, so dass einige der Code Review Informationen dabei verloren gehen können. Wenn dies nicht akzeptabel ist, sollten Sie eine neue Pull-Anforderung erstellen (dh Ihren rebasierten Zweig in einen neuen Remote-Zweig verschieben und eine Pull-Anforderung aus dem neuen Zweig erstellen).

Fazit

Wenn Sie sich für eine lineare Geschichte interessieren:

  • Erstellen Sie eine neue Basis für Ihren Themenzweig über dem neuesten Master, bevor Sie ihn mit dem Master zusammenführen.
  • Führen Sie master nicht in Ihren Themenzweig ein. Rebase statt.
  • Wenn Sie Ihren Themenzweig für andere freigeben, erstellen Sie einen neuen Zweig, wann immer Sie ihn neu erstellen müssen (my-topic-branch-2, my-topic-branch-3, …).
  • Wenn Sie GitHub Pull Requests verwenden, beachten Sie:
    • Die Schaltfläche “Merge” garantiert nicht, dass der PR-Zweig auf dem neuesten Master basiert. Rebase manuell, wenn nötig.
    • Wenn Sie geschützte Zweige mit Statusprüfungen verwenden, drücken Sie niemals die Schaltfläche “Zweig aktualisieren”. Rebase manuell statt.

Wenn Sie sich nicht zu sehr für eine lineare Git-Historie interessieren – happy merging!

Wunschliste für GitHub

An Sie, gute Leute, die bei GitHub arbeiten: Bitte fügen Sie Unterstützung für Rebase-Workflows in Pull-Requests hinzu (dies können Opt-In-Repository-Optionen sein).

  • Fügen Sie die Möglichkeit hinzu, die Merge-Schaltfläche / Operation in Pull-Requests zu deaktivieren, wenn der Zweig nicht auf dem neuesten Master basiert (dies sollte keine Statusprüfungen erfordern).
  • Fügen Sie eine Schaltfläche “Rebase auf den neuesten Master” hinzu. In vielen Fällen sollte dies ein konfliktfreier Vorgang sein, der einfach über die Weboberfläche ausgeführt werden kann.
  • Bewahren Sie den Pull-Request-Verlauf (Commits, Kommentare, …) nach einem Rebase / Force-Push auf.

Leave a Reply