Squash Merge als Standard – warum ich mich für eine saubere Git-Historie entschieden habe

In vielen Teams, die ich erlebe, wird beim Abschließen eines Pull Requests einfach der Standard genommen: Merge Commit (No Fast-Forward). Das war auch bei uns so – jahrelang. Niemand hat es hinterfragt.

Bis ich mir unsere Git-Historie wirklich angeschaut habe.

Vorab: Dieser Beitrag ist kein fertiges Rezept. Er beschreibt meinen aktuellen Denkstand – Work in Progress. Ich habe mich in den letzten Wochen intensiv mit dem Thema beschäftigt, viel gelesen und im Team diskutiert. Was hier steht, ist das Ergebnis dieser Auseinandersetzung – keine finale Handlungsanweisung. Wenn du andere Erfahrungen gemacht hast oder Dinge anders siehst, freue ich mich über den Austausch.

Die Beispiele beziehen sich auf Azure DevOps, aber die Merge-Strategien selbst sind plattformunabhängig – GitHub, GitLab, Bitbucket und andere bieten dieselben Optionen. Die Überlegungen lassen sich 1:1 übertragen.

Was ich gesehen habe

$ git log --oneline --graph develop

*   053dabf Merged PR 35: Erläuterungen auch auf Ebene ZE
|\
| * 1b5a995 Refacttored
| * b89e74f Abfrage korrigiert
|/
*   ee16ff8 Merged PR 28: Treeview Benutzerverwaltung
|\
| * ad5a17f Event-Routine verändert
| *   a592d2d Merge branch 'develop' into Task3801
| |\
| * | 4b31fa9 Test
| * | eaa33db Backup
| * | cf64ea9 Backup
| * | 06fb7b7 Backup
| * | 1068d31 Löschen implementiert

"Backup", "Backup", "Backup", "Test", "Refacttored" (mit Typo). Zwischen-Merges, die verschachtelte Rauten erzeugen. Tausende Commits, bei denen man keine Chance hat, die eigentliche Entwicklung nachzuvollziehen.

Und das ist kein Vorwurf an die Entwickler. Häufiges Committen – auch als Zwischensicherung – ist genau richtig. Microsoft sagt selbst:

„Each commit doesn't have to be perfect, and it might take several commits to accomplish an intended change."

Azure DevOps Docs: Save your work with commits

Das Problem ist nicht das Committen. Das Problem ist, dass diese Zwischen-Commits auf dem Hauptbranch landen.

Was ich stattdessen will

$ git log --oneline develop

053dabf Merged PR 35: Erläuterungen auch auf Ebene ZE
ee16ff8 Merged PR 28: Treeview Benutzerverwaltung
fe00c72 Merged PR 32: Deploymentanpassungen
9a77a7f Merged PR 29: Korrekturlisten erzeugen
018561b Merged PR 21: BerechtigungEKT: Laden optimiert

Eine gerade Linie. Jeder Eintrag ein PR. Kein Rauschen.

Das ist Squash Merge – und es ist kein Nischenansatz. Es ist Microsofts offizielle Empfehlung.

Was Microsoft dazu sagt

Das Cloud Adoption Framework – Microsofts offizieller Leitfaden für Enterprise-Infrastruktur – listet unter den Design Recommendations für Branch-Strategien:

„Set squash as merge strategy, which allows you to condense the Git history of topic branches when you complete pull requests. Instead of adding each commit on a topic branch to the history of the default branch, a squash merge adds all file changes to a single new commit on the default branch."

Das ist keine Blog-Meinung. Das steht im CAF als Best Practice für Unternehmen.

Daneben empfiehlt Microsoft dort auch:

  • „You can delete origin branches after pull requests are complete, which gives you more control and better branch management."
  • „Pull requests should have clear titles and descriptions so reviewers know what to expect."
  • „Pull requests should be simple, with the number of files kept to a minimum."

„Aber gehen die Commit-Messages nicht verloren?"

Das war meine erste Sorge – und die meines Teams. Die Antwort hat mich überrascht: Nein.

Beim Squash Merge in Azure DevOps werden alle Einzel-Commit-Messages automatisch in den Body des Squash-Commits übernommen. Das heißt:

$ git show --stat HEAD

commit abc1234
Author: Entwickler X
Date:   Tue Apr 8 2026

    Merged PR 28: Treeview Benutzerverwaltung

    - Löschen implementiert
    - Layout angepasst
    - Event-Routine verändert
    - Backup
    - Backup

 src/TreeView.cs  | 45 ++++++++++++++++++
 src/Layout.razor | 12 ++---

Mit git log --oneline sieht man die saubere Übersicht. Mit git log oder git show sieht man die Details. Direkt in Git. Ohne Azure DevOps, offline, auf jedem Klon.

Was bleibt wo?

Information Git Azure DevOps (PR)
Squash-Commit (vollständiger Diff)
PR-Nummer → Zuordnung ✅ (Commit-Message)
Einzel-Commit-Messages ✅ (im Commit-Body)
Einzelne Diffs pro Zwischen-Commit
Review-Kommentare
Work-Item-Verlinkungen

Der Squash-Commit enthält den vollständigen Diff und die Commit-Messages aller Einzelschritte. Was nur über Azure DevOps verfügbar bleibt, sind die Diffs der Zwischen-Commits und die Review-Diskussionen – aber die sind bei einem normalen Merge Commit auch nur dort.

„Was wenn Azure DevOps mal nicht mehr da ist?"

Berechtigte Frage. Meine Antwort: Selbst ohne Azure DevOps bleiben in Git:

  • ✅ Der vollständige Code-Stand zu jedem Zeitpunkt
  • ✅ Welche Dateien geändert wurden (Squash-Diff)
  • ✅ PR-Nummer und Beschreibung (Commit-Message)
  • ✅ Alle Einzel-Commit-Messages (im Body)

Was man verliert: Die Diffs der Zwischen-Commits. Aber seien wir ehrlich – was ist wertvoller für die Nachwelt? Der vollständige Feature-Diff oder die einzelnen Diffs von „Backup → Backup → Test"?

Dazu kommt: Reviews, Work-Item-Links und Diskussionen sind schon heute nur in Azure DevOps gespeichert. Die Abhängigkeit besteht längst.

Der rote Faden: Nicht gegen den aktuellen Ansatz – für einen besseren

Merge Commit ist nicht falsch. Es war die Standardeinstellung, und es hat funktioniert. Aber wenn ich mir ehrlich die Frage stelle, was uns wirklich hilft – dann ist es nicht die Aufbewahrung jedes „Backup"-Commits auf dem Hauptbranch.

Was hilft, ist:

  • Eine Historie, die man lesen kann
  • Commits, die man einer Änderung zuordnen kann
  • Werkzeuge wie git bisect und git blame, die auf einer geraden Linie funktionieren

Die vier Merge-Strategien kurz erklärt

Für den Überblick – Azure DevOps bietet vier Optionen beim PR-Abschluss:

Merge Commit (No Fast-Forward)

Erzeugt einen Merge-Commit mit zwei Parents. Die gesamte Branch-Historie landet auf dem Zielbranch – inkl. WIP-Commits und Zwischen-Merges.

       o---o---o  (Feature)
      /         \
 ----o-----o-----M  (develop)

Pro: Vollständige Historie, stabile Hashes.
Contra: Unübersichtlich, WIP-Commits sichtbar, verschachtelte Graphen.

Squash Merge

Alle Commits werden zu einem einzigen zusammengefasst. Einzel-Messages landen im Body.

 ----o-----o-----S  (develop, ein Commit)

Pro: Saubere lineare Historie, WIP bereinigt, Messages bleiben im Body.
Contra: Keine separaten Zwischen-Commits auf dem Zielbranch.

Rebase and Fast-Forward

Commits werden auf den Zielbranch rebased, kein Merge-Commit.

 ----o-----D---A'---B'---C'  (develop)

Pro: Lineare Historie, Einzel-Commits erhalten.
Contra: Hashes ändern sich, WIP-Commits bleiben sichtbar, technische Einschränkungen (Branch-Policies, Conflict-Extension).

Semi-Linear Merge

Rebase + Merge-Commit als Klammer.

         A'---B'---C'
        /            \
 ----o-----D----------M  (develop)

Pro: Lineare Basis + Feature-Klammer.
Contra: Gleiche Einschränkungen wie Rebase, WIP-Commits bleiben sichtbar.

Wann welche Strategie?

Szenario Empfehlung
Feature oder Bugfix (Normalfall) Squash Merge
Große Architekturänderung mit sauberen Commits Semi-Linear Merge
Einzelner, sauberer Commit Rebase (oder Squash – beides okay)
Alles andere Squash Merge

Im Alltag ist die Antwort fast immer: Squash Merge.

Was sich am Workflow ändert

Für Entwickler: Nichts. Weiter wie bisher – Branch erstellen, committen, PR erstellen. Der einzige Unterschied ist die Auswahl beim Abschließen des PRs.

Was sich ändert, ist das Ergebnis. Und ein Tipp: Der PR-Titel wird zur Commit-Message auf dem Hauptbranch. Es lohnt sich also, den Titel aussagekräftig zu formulieren.

Flankierende Maßnahmen

Die Umstellung auf Squash Merge funktioniert am besten mit ein paar Begleitmaßnahmen – alle aus dem Cloud Adoption Framework:

  • Merge-Typ per Branch Policy einschränken – damit Squash Merge Standard wird
  • Source-Branch nach Merge automatisch löschen – saubere Branch-Liste
  • PR-Template anlegen – erinnert an aussagekräftige Titel und Beschreibungen
  • Work Items verlinken – Traceability sicherstellen
  • Kommentar-Resolution erzwingen – offene Punkte müssen geklärt werden

Mein Fazit – vorläufig

Die Entscheidung für Squash Merge war keine Entscheidung gegen unsere bisherige Arbeitsweise. Es war eine Entscheidung für eine Historie, die uns tatsächlich hilft.

Die Commit-Messages bleiben erhalten – im Body des Squash-Commits. Die Details bleiben erhalten – im Pull Request. Und der Hauptbranch wird endlich zu dem, was er sein sollte: eine lesbare Geschichte der Entwicklung.

Kein „Backup“. Kein „Test“. Keine verschachtelten Rauten. Nur: Was wurde gemacht, und warum.

Ist das die perfekte Lösung für jedes Team? Wahrscheinlich nicht. Aber für meinen Kontext – ein Projekt mit vielen WIP-Commits, einem PR-basierten Workflow und Azure DevOps – fühlt es sich stimmig an.

Vielleicht passt es auch in deinen Kontext. Vielleicht nicht. Vielleicht findest du einen besseren Kompromiss. Ich bin gespannt, wie andere Teams das handhaben.

Was ist deine Erfahrung mit Merge-Strategien? Nutzt du Squash Merge, und wenn ja – warum? Wenn nein – warum nicht?


Quellen: