vom Commit zur automatisierten Release-Pipeline
Wer Commit-Messages nach Lust und Laune formuliert, verliert die Möglichkeit, Änderungen maschinell auszuwerten und Releases automatisch zu planen. Conventional Commits standardisiert das Format auf type(scope): description und macht die Commit-Historie damit zur verlässlichen Grundlage für automatisierte Changelogs, semantische Versionsnummern und vollautomatische Releases, ganz ohne manuelle Zwischenschritte in der CI/CD-Pipeline.
Inhaltsverzeichnis
- 1. Die Spezifikation im Überblick: type(scope): description
- 2. Erlaubte Commit-Types im Detail
- 3. Breaking Changes kennzeichnen: ! und BREAKING CHANGE Footer
- 4. Mehr als Formatierung: maschinenlesbare Commit-Historie
- 5. Automatisches Semantic Versioning aus Commit-Historie
- 6. Automatisierte Changelog-Generierung
- 7. Commitlint als Enforcement-Tool
- 8. CI/CD-Integration für automatisierte Releases
- 9. Typische Fehler, Monorepos und der Vergleich zu Freeform-Commits
- 10. Zusammenfassung
- 11. FAQ
1. Die Spezifikation im Überblick: type(scope): description
Conventional Commits ist eine leichtgewichtige Spezifikation für die Struktur von Commit-Messages, die 2018 aus der Angular-Commit-Convention hervorgegangen ist und mittlerweile als De-facto-Standard in Open-Source- und Enterprise-Projekten gilt. Das Grundformat lautet type(scope): description, gefolgt von einem optionalen Body für ausführlichere Erklärungen und einem optionalen Footer für Metadaten wie Breaking Changes oder Issue-Referenzen. Der type klassifiziert die Art der Änderung, der scope in Klammern grenzt den betroffenen Bereich ein, etwa ein Modul oder eine Komponente, und die description fasst die Änderung in einem kurzen, im Imperativ formulierten Satz zusammen.
Der entscheidende Unterschied zu freien Commit-Messages ist nicht Stilistik, sondern Struktur: Ein Parser kann type, scope und description zuverlässig extrahieren, ohne den Freitext interpretieren zu müssen. Tools wie semantic-release, standard-version oder Angular verlassen sich exakt auf dieses Format, um Commits automatisch Kategorien zuzuordnen. Wer die Spezifikation korrekt anwendet, bekommt maschinenlesbare Historie quasi geschenkt, ohne zusätzliche Metadaten in separaten Dateien pflegen zu müssen.
# Conventional Commits: real-world examples
git commit -m "feat(checkout): add express payment button"
git commit -m "fix(cart): prevent duplicate items on quick add"
git commit -m "docs(readme): document environment variables"
git commit -m "chore(deps): bump magento/module-catalog to 104.0.5"
git commit -m "refactor(api): extract price calculation into service"
git commit -m "test(checkout): add coverage for tax calculation"
git commit -m "perf(catalog): cache category tree for 5 minutes"
git commit -m "build(webpack): enable tree shaking for vendor bundle"
git commit -m "ci(github-actions): run phpstan on every pull request"
# With scope and multi-line body
git commit -m "feat(auth): add two-factor authentication" -m "Adds TOTP-based 2FA for admin accounts. Users can enroll via account settings and recovery codes are generated on activation."
2. Erlaubte Commit-Types im Detail
Die Angular-Konvention, auf der Conventional Commits aufbaut, definiert ein festes Set an Types: feat für neue Features, fix für Bugfixes, docs für reine Dokumentationsänderungen, style für Formatierung ohne Codeänderung, refactor für Umstrukturierungen ohne Verhaltensänderung, perf für Performance-Verbesserungen, test für Testergänzungen, build für Änderungen am Build-System oder an Abhängigkeiten, ci für CI/CD-Konfiguration und chore für Wartungsarbeiten ohne Produktionscode-Bezug. Ein zusätzlicher Type revert kennzeichnet das Zurücknehmen eines früheren Commits und referenziert im Body dessen Hash.
Nicht jeder Type ist für die spätere Versionierung gleich relevant: feat und fix sind die einzigen Types, die semantic-release standardmäßig zur automatischen Versionsermittlung heranzieht, alle anderen gelten als versionsneutral und tauchen im Changelog meist in eigenen, weniger prominenten Abschnitten auf. Der scope in Klammern sollte projektweit aus einer begrenzten, dokumentierten Liste stammen, etwa Modulnamen wie checkout, catalog oder api, statt bei jedem Commit neu erfunden zu werden. Ohne diese Disziplin verwässert der scope schnell zu Rauschen, das weder beim Lesen der Historie noch bei automatisierten Auswertungen einen Mehrwert liefert.
3. Breaking Changes kennzeichnen: ! und BREAKING CHANGE Footer
Breaking Changes sind der einzige Fall, in dem Conventional Commits eine Änderung erzwingt, die über die reine Kategorisierung hinausgeht: Sie signalisieren, dass eine Änderung die öffentliche API inkompatibel verändert und Konsumenten des Pakets ihren Code anpassen müssen. Es gibt zwei gleichwertige Notationen: ein Ausrufezeichen direkt nach type oder scope, etwa feat(api)!: remove legacy price endpoint, oder ein Footer BREAKING CHANGE: gefolgt von einer Erklärung, was sich ändert und wie migriert wird. Beide Notationen können auch kombiniert auftreten, wobei der Footer zusätzlichen Raum für eine ausführliche Migrationsanleitung bietet.
Für Paketkonsumenten, die per Composer oder npm mit einem Caret-Constraint wie ^2.4.0 auf ein Paket zeigen, ist diese Kennzeichnung kein Detail, sondern die Grundlage dafür, ob ein Update automatisch eingespielt werden darf. semantic-release erkennt BREAKING CHANGE unabhängig vom zugrunde liegenden Type, ein breaking fix ist also ebenso eine Major-Version wie ein breaking feat. In der Praxis lohnt es sich, Breaking Changes im Footer immer mit einer konkreten Migrationsanleitung zu versehen, weil das Changelog später als einzige Informationsquelle für Konsumenten dient, die kein Diff der Codebasis lesen.
4. Mehr als Formatierung: maschinenlesbare Commit-Historie
Der eigentliche Wert von Conventional Commits liegt nicht in hübscheren Commit-Messages, sondern darin, dass die Git-Historie zu einer strukturierten Datenquelle wird. git log --grep="^feat" isoliert sämtliche neuen Features seit einem beliebigen Tag, ohne Freitext interpretieren zu müssen. Bei der Fehlersuche mit git bisect liefert ein type: fix sofort den Hinweis, dass ein Commit gezielt ein Problem beheben sollte, was die Eingrenzung von Regressionen beschleunigt. Auch Code-Reviews profitieren: Ein Reviewer sieht am Betreff sofort, ob ein Pull-Request feature-, bugfix- oder wartungsgetrieben ist, bevor er überhaupt den Diff öffnet.
Für Audits und Compliance-Nachweise, etwa bei sicherheitsrelevanten fix-Commits, lässt sich die Historie ohne zusätzliches Tooling nach Type filtern und exportieren. Unstrukturierte Commit-Messages wie "kleine Anpassung" oder "Fixes" verhindern all das systematisch: Sie sind für Menschen im Nachhinein kaum einordbar und für Skripte gar nicht auswertbar. Die Investition in Conventional Commits zahlt sich also nicht erst beim nächsten Release aus, sondern bei jeder Suche in der Historie, die ein Team im Laufe eines Projekts durchführt.
5. Automatisches Semantic Versioning aus Commit-Historie
Semantic Versioning folgt dem Schema MAJOR.MINOR.PATCH, und Conventional Commits liefert genau die Signale, die zur automatischen Bestimmung der nächsten Versionsnummer nötig sind: Ein fix-Commit erhöht die PATCH-Version, ein feat-Commit erhöht die MINOR-Version und setzt PATCH auf null zurück, und jede Kennzeichnung als Breaking Change erhöht die MAJOR-Version und setzt MINOR sowie PATCH zurück. Tools wie semantic-release für das npm-Ökosystem oder standard-version für lokal gesteuerte Releases werten die Commits seit dem letzten Tag aus und bestimmen daraus deterministisch die nächste Version, ohne dass ein Mensch die Versionsnummer manuell festlegen muss.
Der Ablauf ist dabei immer gleich: Das Tool sammelt alle Commits seit dem letzten veröffentlichten Tag, klassifiziert sie nach Type, ermittelt die höchste erforderliche Versionsstufe und erstellt daraus Tag, Release-Notes und gegebenenfalls den Paket-Publish-Schritt in einem einzigen automatisierten Durchlauf. Enthält ein Commit-Batch sowohl feat- als auch fix-Commits, gewinnt immer die höhere Stufe, also MINOR statt PATCH. Ein einziger Breaking-Change-Commit reicht aus, um unabhängig von allen anderen Commits eine MAJOR-Version auszulösen, selbst wenn er als fix deklariert ist.
6. Automatisierte Changelog-Generierung
Sobald Commit-Messages strukturiert vorliegen, lässt sich ein CHANGELOG.md ohne manuelle Pflege erzeugen: Tools wie conventional-changelog oder das eingebaute Changelog-Plugin von semantic-release gruppieren alle Commits seit dem letzten Release nach Type, formatieren sie als Markdown-Liste und verlinken jeden Eintrag direkt mit dem zugehörigen Commit-Hash oder Pull-Request. Features und Fixes erscheinen typischerweise in eigenen Abschnitten oben im Changelog, während chore- und ci-Commits meist ganz weggelassen werden, weil sie für Endnutzer des Pakets keinen Mehrwert bieten.
Der Vorteil gegenüber manuell gepflegten Changelogs ist nicht nur der eingesparte Aufwand, sondern die Konsistenz: Ein automatisch generiertes Changelog kann keinen Eintrag vergessen, weil es direkt aus derselben Datenquelle entsteht, die auch die Versionsnummer bestimmt. Das folgende Beispiel zeigt einen Ausschnitt aus einem generierten CHANGELOG.md, wie es semantic-release aus einer Reihe von Commits mit feat-, fix- und einem Breaking-Change-Commit erzeugen würde.
# Generated by semantic-release, excerpt from CHANGELOG.md
cat CHANGELOG.md
## [3.0.0](https://github.com/mironsoft/checkout-module/compare/v2.4.1...v3.0.0) (2026-07-10)
### BREAKING CHANGES
* api: the /v1/price endpoint has been removed, use /v2/price instead
### Features
* checkout: add express payment button (a1b2c3d)
* auth: add two-factor authentication (e4f5a6b)
### Bug Fixes
* cart: prevent duplicate items on quick add (c7d8e9f)
* checkout: correct tax calculation for digital goods (1a2b3c4)
### Performance Improvements
* catalog: cache category tree for 5 minutes (9f8e7d6)
7. Commitlint als Enforcement-Tool
Eine Spezifikation allein garantiert keine Einhaltung: Ohne technische Durchsetzung schleichen sich innerhalb weniger Wochen wieder Freitext-Commits ein. commitlint validiert jede Commit-Message gegen ein konfigurierbares Regelwerk, meist basierend auf der Preset-Konfiguration @commitlint/config-conventional, die Type, Groß-/Kleinschreibung, maximale Zeilenlänge der description und weitere Details prüft. Installiert wird commitlint als Dev-Dependency zusammen mit dem gewünschten Config-Preset, üblicherweise über npm install --save-dev @commitlint/cli @commitlint/config-conventional.
Die eigentliche Durchsetzung erfolgt über einen Git-Hook, meist verwaltet mit Husky, das den commit-msg-Hook installiert und bei jedem Commit-Versuch commitlint gegen die geschriebene Message laufen lässt. Entspricht die Message nicht dem Muster, bricht Husky den Commit-Vorgang ab, bevor er überhaupt im lokalen Repository landet. Dieselbe Prüfung lässt sich zusätzlich in der CI-Pipeline als Pull-Request-Check wiederholen, um Commits abzufangen, die den lokalen Hook umgangen haben, etwa durch --no-verify oder einen Merge von außerhalb.
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [2, "always", ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"]],
"scope-enum": [2, "always", ["checkout", "cart", "catalog", "auth", "api", "deps"]],
"subject-case": [2, "never", ["start-case", "pascal-case", "upper-case"]],
"header-max-length": [2, "always", 100]
}
}
#!/usr/bin/env sh
# .husky/commit-msg - enforce Conventional Commits before the commit is created
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"
8. CI/CD-Integration für automatisierte Releases
Sobald Commit-Messages zuverlässig strukturiert sind, lässt sich der komplette Release-Prozess in die CI/CD-Pipeline verlagern: Ein Push oder Merge auf den main-Branch löst einen Workflow aus, der zunächst Tests und statische Analyse ausführt und danach semantic-release aufruft. Das Tool ermittelt die nächste Version aus den Commits seit dem letzten Tag, generiert das Changelog, erstellt einen Git-Tag und veröffentlicht das Paket, ohne dass ein Entwickler manuell npm publish oder composer config eingibt.
Für PHP- und Magento-Projekte ist der Mechanismus identisch, nur die Zielplattform unterscheidet sich: Statt npm publish steht am Ende ein Push in ein privates Packagist-Repository oder ein Satis-Rebuild, ausgelöst durch dasselbe Versionsschema. Wichtig ist, den Release-Job auf geschützte Branches zu beschränken und Secrets wie NPM_TOKEN oder Packagist-Credentials ausschließlich über die CI-Secret-Verwaltung bereitzustellen, niemals im Repository selbst. Das folgende Beispiel zeigt einen minimalen GitHub-Actions-Workflow, der genau diesen Ablauf automatisiert.
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
9. Typische Fehler, Monorepos und der Vergleich zu Freeform-Commits
In der Praxis scheitert die Einführung von Conventional Commits seltener an der Spezifikation selbst als an fehlender Disziplin im Team. Der häufigste Fehler ist ein unkontrolliert wachsender Satz an scope-Werten, weil jeder Entwickler eigene Bezeichnungen erfindet, statt sich an eine dokumentierte Liste zu halten, wodurch die Suchbarkeit der Historie wieder verloren geht. Fast ebenso häufig ist die Verwendung von fix oder chore als Auffang-Type für praktisch jede Änderung, weil die Unterscheidung zwischen den Types nicht im Team abgestimmt wurde. Der dritte klassische Fehler: Die Konvention wird nur als Empfehlung kommuniziert statt technisch über commitlint und Husky erzwungen, wodurch sie nach wenigen Wochen erodiert.
In Monorepos mit mehreren unabhängig versionierten Paketen, etwa verwaltet mit Nx, Lerna oder Changesets, übernimmt der scope zusätzlich die Aufgabe, ein Paket eindeutig zu identifizieren, etwa feat(ui-kit): oder fix(api-client):. Tools wie semantic-release-monorepo oder die nativen Release-Mechanismen von Nx werten dann nur die Commits aus, die Dateien im jeweiligen Paketpfad betreffen, und bestimmen für jedes Paket eine eigene, unabhängige Versionsnummer. Ohne konsistente scope-Namen lässt sich diese Zuordnung nicht zuverlässig automatisieren, weshalb gerade in Monorepos eine per commitlint erzwungene scope-enum-Regel unverzichtbar ist.
| Kriterium | Freeform Commit-Messages | Conventional Commits | Auswirkung |
|---|---|---|---|
| Changelog-Erstellung | Manuell, oft veraltet | Automatisch aus der Historie generiert | Kein manueller Pflegeaufwand mehr |
| Versionsnummer bestimmen | Manuelle Einschätzung, fehleranfällig | Automatisch via semantic-release/standard-version | Konsistente, deterministische Releases |
| Historie durchsuchen | Freitext-Suche mit grep | git log --grep="^feat" strukturiert filterbar | Schnellere Fehlersuche und Audits |
| Breaking Changes erkennen | Nur durch Lesen jedes Diffs | Explizit über ! oder BREAKING CHANGE | Konsumenten werden zuverlässig gewarnt |
| CI/CD-Automatisierung | Nicht ohne zusätzliches Parsing möglich | Direkter Trigger für Release-Pipelines | Releases ohne manuellen Eingriff |
In der Summe zeigt der Vergleich, dass Conventional Commits keine stilistische Präferenz ist, sondern die Voraussetzung für jede Form von Automatisierung entlang des Release-Prozesses. Je mehr Pakete und Teams an einer Codebasis arbeiten, desto größer wird der Hebel, weil sich die eingesparte manuelle Arbeit mit jedem zusätzlichen Repository und jedem zusätzlichen Release multipliziert.
Mironsoft
Git-Workflows, Release-Automatisierung und CI/CD-Pipelines für Magento- und PHP-Teams
Conventional Commits im Team einführen und automatisch Releases fahren?
Wir richten commitlint und Husky in eurem Repository ein, verbinden semantic-release mit eurer CI/CD-Pipeline und sorgen dafür, dass Changelog, Versionsnummer und Paketveröffentlichung bei jedem Merge automatisch entstehen, egal ob npm, Composer oder ein privates Packagist-Repository das Ziel ist.
Commitlint-Setup
Regelwerk, scope-enum und Husky-Hooks passend zu eurem Projekt konfigurieren
Release-Automatisierung
semantic-release oder standard-version mit Changelog und Versionierung verdrahten
Monorepo-Versionierung
Paketbezogene scopes und unabhängige Versionsnummern für Nx, Lerna oder Changesets
10. Zusammenfassung
Conventional Commits löst ein Grundproblem, das viele Teams unterschätzen: Ohne strukturierte Commit-Messages bleibt jede Form von Release-Automatisierung Handarbeit. Das Format type(scope): description macht jeden Commit maschinenlesbar, die Types feat und fix steuern automatisches Semantic Versioning, und Breaking Changes über ! oder den BREAKING CHANGE-Footer warnen Konsumenten zuverlässig vor inkompatiblen Änderungen. Aus derselben Datenquelle entstehen automatisch generierte Changelogs, die nie einen Eintrag vergessen, weil sie direkt aus der Commit-Historie abgeleitet werden.
Der Schlüssel zum langfristigen Erfolg liegt in der technischen Durchsetzung: commitlint mit einer projektspezifischen Konfiguration und ein Husky-commit-msg-Hook verhindern, dass die Konvention nach wenigen Wochen erodiert. In Kombination mit semantic-release oder standard-version in der CI/CD-Pipeline wird der komplette Release-Prozess, von der Versionsermittlung über die Changelog-Generierung bis zur Paketveröffentlichung, vollständig automatisiert, egal ob für ein einzelnes npm-Paket, ein Composer-Modul oder ein Monorepo mit mehreren unabhängig versionierten Paketen.
Conventional Commits: Standardisierte Commit-Messages - Das Wichtigste auf einen Blick
Spezifikation
type(scope): description mit festen Types wie feat, fix, docs, chore, refactor, test, perf, build und ci.
Semantic Versioning
fix erhöht PATCH, feat erhöht MINOR, BREAKING CHANGE erhöht MAJOR - automatisch via semantic-release.
Changelog-Automatisierung
CHANGELOG.md wird direkt aus der Commit-Historie generiert, gruppiert nach Type, ohne manuelle Pflege.
Enforcement
commitlint plus Husky-commit-msg-Hook verhindert Freitext-Commits schon vor dem lokalen Commit.