Changelog für Menschen: Keep a Changelog
Wer schon einmal wissen wollte, was sich in der neuen Version einer Bibliothek geändert hat, kennt zwei Enttäuschungen. Die erste: Es gibt gar kein Changelog. Die zweite: Es gibt eins, aber es hilft nicht weiter. Der letzte Eintrag ist zwei Jahre alt, mal steht die neueste Version oben und mal unten, Daten fehlen, und hinter „Various bug fixes and improvements“ kann sich alles verbergen.
Wozu ein Changelog?
Ein Changelog ist eine Datei, in der du die Änderungen an deinem Projekt festhältst, geordnet nach Versionen. Das klingt nach Pflichtübung, aber der Nutzen ist größer als der Aufwand.
Anwender können Updates einschätzen
Bevor jemand eine neue Version einspielt, will er wissen, was ihn erwartet. Gibt es neue Funktionen? Wurde ein Fehler behoben, der ihn betrifft? Bricht etwas, das bisher funktioniert hat? Ohne Changelog bleibt nur, den Code zu vergleichen oder es auszuprobieren. Mit Changelog dauert die Antwort eine Minute.
Du selbst profitierst auch
Ein Changelog ist das Gedächtnis des Projekts. Wann habe ich dieses Verhalten geändert und warum? Seit welcher Version gibt es die Funktion? Nach einem halben Jahr weiß das niemand mehr auswendig, auch du als Entwickler:in nicht. Und wenn es an den Release geht, hast du die Release Notes praktisch schon geschrieben.
Das Team bleibt auf demselben Stand
Wer im Team arbeitet, bekommt nicht jede Änderung mit. Das Changelog zeigt auf einen Blick, was in den letzten Wochen passiert ist, ohne dass jemand durch Merge Requests blättern muss. Wird er im selben Merge Request gepflegt, der die Änderung einführt, kann das Review gleich auch eine zweite Frage klären: Ist die Änderung für Anwender verständlich beschrieben?
Probleme lassen sich eingrenzen
Wenn nach einem Update etwas nicht mehr funktioniert, hilft das Changelog bei der Suche. Was wurde zwischen der letzten funktionierenden und der aktuellen Version geändert?
Warum nicht einfach git log?
Der naheliegende Gedanke: Die Historie steht doch schon in Git. Das stimmt, aber sie beantwortet eine andere Frage. Die Commit-Historie erzählt, wie der Code entstanden ist. Ein Changelog beantwortet, was die Änderung für den Leser bedeutet.
Ein Commit wie Refactor user lookup ist für Mitentwickler nützlich, für Anwender aber ohne Aussage.
Dazu kommt viel Rauschen:
Tippfehler-Korrekturen, Zwischenstände und Merge-Commits.
Ein ungefilterter Dump des Git-Logs zählt deshalb kaum als Changelog.
Der nächste Schritt: ein gemeinsames Format
Damit wäre die Frage nach dem ob geklärt, aber es bleibt die nach dem wie. Fängst du ohne Vorgabe an, wirst du schnell Entscheidungen treffen müssen, die nichts mit dem Projekt zu tun haben: Neueste Version oben oder unten? Welches Datumsformat? Wo kommen noch nicht veröffentlichte Änderungen hin? Wie trennst du neue Funktionen von Fehlerkorrekturen?
Jedes Projekt beantwortet das anders und der Leser muss sich jedes Mal neu zurechtfinden. Auch ein Team, das das Changelog gemeinsam pflegt, driftet ohne gemeinsame Regeln auseinander. Der eine schreibt Prosa, der andere Stichpunkte, der dritte vergisst das Datum. Mit den Jahren habe ich gelernt, wie angenehm es sein kann auf bestehende Konventionen aufzusetzen.
Genau hier setzt Keep a Changelog an. Die Konvention nimmt dir diese Entscheidungen ab und ersetzt sie durch ein paar klare Regeln. Das bringt drei Vorteile:
- Leser finden sich überall zurecht: Wer das Format kennt, weiß in jedem Projekt, wo er suchen muss.
- Du musst nichts erfinden: Die Struktur ist fertig, du füllst sie nur.
- Tools kommen damit klar: Weil das Format fest ist, lässt es sich auch maschinell auswerten, etwa für automatische Release Notes.
Die Konvention sagt dazu selbst: Changelogs sind für Menschen gemacht, nicht für Maschinen.
Changelogs are for humans, not machines.
Die Maschinenlesbarkeit ist ein Nebeneffekt der klaren Struktur, aber nicht ihr Zweck.
Die Grundsätze
Die Konvention fasst sich in wenigen Regeln zusammen (hier aus dem Englischen übersetzt):
- Changelogs sind für Menschen gemacht, nicht für Maschinen.
- Es gibt einen Eintrag für jede Version.
- Gleichartige Änderungen werden gruppiert.
- Versionen und Abschnitte sind verlinkbar.
- Die neueste Version steht oben.
- Das Veröffentlichungsdatum jeder Version steht dabei.
- Es ist angegeben, ob das Projekt Semantic Versioning folgt.
Datumsangaben folgen ISO 8601, also 2026-10-02 statt 02.10.26 oder 10/02/26.
So ist das Datum eindeutig, egal woher der Leser kommt.
Die sechs Gruppen
Innerhalb einer Version werden die Änderungen nach Art sortiert. Dafür gibt es genau sechs Überschriften:
| Gruppe | Wofür |
|---|---|
Added | neue Funktionen |
Changed | Änderungen an bestehendem Verhalten |
Deprecated | Funktionen, die bald entfernt werden |
Removed | Funktionen, die jetzt entfernt wurden |
Fixed | behobene Fehler |
Security | Korrekturen bei Schwachstellen |
Du verwendest nur die Gruppen, zu denen es Einträge gibt.
Die feste Auswahl hat einen Nutzen:
Wer ein Update plant, sucht zuerst nach Removed und Changed, weil dort die Dinge stehen, die etwas kaputt machen können.
Security ist die Gruppe, bei der Betreiber sofort hellhörig werden sollten.
Ein Beispiel
So sieht das Ganze in der Praxis aus. Die Einträge sind bewusst ausführlicher als eine Commit-Nachricht und erklären, was sich für den Leser ändert:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Export search results as CSV.
The new "Export" button in the results view downloads the current result list, including the active filters.
## [1.1.0] - 2026-09-15
This release adds full-text search.
Existing installations need no manual steps, but the first start after the update rebuilds the search index and may take a few minutes on large databases.
### Added
- Full-text search across titles and descriptions.
The search field is available in the header on every page.
### Changed
- Uploads larger than 10 MB are now rejected with a clear error message instead of timing out after 30 seconds.
### Fixed
- Opening an empty file no longer crashes the application.
Empty files are now shown as a blank document.
## [1.0.0] - 2026-08-01
First stable release.
### Added
- User accounts with login and password reset by e-mail.
- Document upload and management.
[Unreleased]: https://github.com/example/project/compare/v1.1.0...HEAD
[1.1.0]: https://github.com/example/project/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/example/project/releases/tag/v1.0.0
Zwei Teile des Beispiels verdienen einen genaueren Blick.
Der Abschnitt Unreleased
Ganz oben steht immer ein Abschnitt für alles, was noch nicht veröffentlicht ist.
Entwickler tragen ihre Änderung ein, sobald sie diese machen, und zwar im selben Merge Request.
Das ist viel leichter als bei einem Release rückwirkend zu rekonstruieren, was in den letzten Wochen passiert ist und nichts wird vergessen.
Beim Release wird eine Überschrift mit der Versionsnummer inklusive Datum ergänzt und ein leerer Unreleased-Abschnitt verbleibt darüber.
Die Links am Ende
Die Versionsnummern in den Überschriften sind Links auf den Vergleich zur vorherigen Version.
In Markdown erreichst du das mit Referenz-Links:
Die Überschrift ## [1.1.0] - 2026-09-15 verweist auf eine Definition, die du am Dateiende sammelst.
Die Links selbst baust du aus den Git-Tags der Releases. Die Vergleichsansicht von GitHub und GitLab nimmt zwei Tags und zeigt alle Änderungen dazwischen:
[1.1.0]: https://github.com/example/project/compare/v1.0.0...v1.1.0
Bei GitLab lautet das Muster https://gitlab.com/example/project/-/compare/v1.0.0...v1.1.0.
Die erste Version hat keinen Vorgänger.
Sie verweist deshalb direkt auf den Tag, bei GitHub mit /releases/tag/v1.0.0, bei GitLab mit /-/tags/v1.0.0.
Der Link für [Unreleased] vergleicht den letzten Tag mit HEAD, also mit dem aktuellen Stand des Hauptzweigs.
Beim Release gibt es eine Besonderheit, denn du schreibst den Changelog-Eintrag, bevor du den Tag setzt.
Der Tag soll ja schon das fertige Changelog enthalten.
Der Link auf v1.2.0 zeigt in diesem Moment also ins Leere, du rätst ihn gewissermaßen.
Das ist in Ordnung: Sobald du den Tag gepusht hast, funktioniert der Link.
Vergiss dabei nicht, den [Unreleased]-Link auf v1.2.0...HEAD umzustellen.
Wer wissen will, was genau im Code passiert ist, klickt auf den Link und sieht den vollständigen Diff. Das Changelog liefert die Zusammenfassung, der Vergleich die Details.
Zurückgezogene Versionen
Manchmal geht eine Veröffentlichung schief, etwa wegen eines schwerwiegenden Fehlers oder einer Sicherheitslücke. Version 1.1.0 der Spezifikation hat dafür eine Kennzeichnung ergänzt:
## [1.1.1] - 2026-09-20 [YANKED]
Die Version bleibt in der Historie stehen, ist aber als zurückgezogen markiert. Löschen wäre die schlechtere Wahl, denn dann wüsste niemand, warum die Versionsnummern eine Lücke haben.
Der Partner: Semantic Versioning
Keep a Changelog sagt dir nicht, welche Versionsnummer ein Release bekommt.
Das regelt Semantic Versioning, kurz SemVer, mit dem Schema MAJOR.MINOR.PATCH:
- MAJOR erhöhst du bei inkompatiblen Änderungen.
- MINOR erhöhst du bei neuen Funktionen, die abwärtskompatibel sind.
- PATCH erhöhst du bei abwärtskompatiblen Fehlerkorrekturen.
Beide Konventionen ergänzen sich gut, denn die Gruppen im Changelog verraten, welche Nummer fällig ist.
Steht unter Removed oder in einem Changed mit Breaking Change etwas, wird es eine neue Hauptversion.
Gibt es nur Added, reicht eine neue Nebenversion.
Gibt es nur Fixed, bleibt es bei einem Patch-Release.
Schreibtipps
Überleg dir zuerst, wer dein Changelog liest. Ein Endanwender will wissen, was sich in der Bedienung ändert. Ein Ops-Admin interessiert sich für neue Konfigurationsoptionen, Migrationsschritte und geänderte Abhängigkeiten. Ein Security-Spezialist sucht nach behobenen Schwachstellen und deren Schwere. Jeder dieser Leser braucht andere Details, und davon hängt ab, wie ausführlich und wie technisch du schreibst. Manchmal heißt das auch, dass mehrere Zielgruppen in einer Datei bedient werden müssen und dann helfen die Gruppen von oben beim Navigieren.
Ein paar weitere Erfahrungen aus der Praxis:
- Schreib aus Sicht des Lesers: Statt
Refactor ParserbesserParser erkennt jetzt verschachtelte Listen. - Beschreibe die Auswirkung statt der Umsetzung: Ein bis zwei Sätze genügen, bei Bedarf mit Link auf das Issue oder den Merge Request.
- Erwähne Breaking Changes deutlich, denn genau danach suchen die Leser.
- Pflege das Changelog beim Ändern, nicht erst beim Release.
Nicht jede Änderung braucht einen Eintrag
Ein Changelog enthält nur nennenswerte Änderungen, sonst wird es zum git log mit anderem Namen.
Refactorings ohne Verhaltensänderung, Tests, CI-Konfiguration oder Tippfehler im Code kannst du getrost weglassen.
Frag dich dafür:
Würde ein Leser aus meiner Zielgruppe wegen dieser Änderung etwas anders machen oder anders erwarten?
Wenn nicht, bleibt der Eintrag weg.
Ein zweiter Blick lohnt sich bei Abhängigkeiten und scheinbar kleinen Korrekturen.
Ein Update, das eine Sicherheitslücke schließt, gehört unter Security.
Eine neue Mindestversion von Python oder der Datenbank betrifft Ops-Admins.
Und ein behobener Fehler bleibt einen Eintrag wert, auch wenn die Korrektur nur eine Zeile umfasst.
Im Team hilft es, das Weglassen bewusst zu machen, zum Beispiel mit einem Hinweis in der Vorlage für Merge Requests („Kein Changelog-Eintrag nötig, weil …“). So sieht ein fehlender Eintrag nicht wie ein Versehen aus.
Gitmoji
Wer mag, ergänzt jede Zeile um ein Gitmoji, also ✨ für Neues und 🐛 für Fehlerkorrekturen. Das ist keine Vorgabe von Keep a Changelog, sondern eine persönliche Vorliebe, die das Überfliegen erleichtert.
Fazit
Keep a Changelog ist keine Technik, sondern eine Vereinbarung.
Sie kostet fast nichts, macht Updates planbar und zeigt deinen Anwendern, dass du ihre Zeit respektierst.
Fang mit einer CHANGELOG.md und einem leeren Unreleased-Abschnitt an, der Rest ergibt sich mit der Zeit.
Kommentare
Es gibt noch keinen Kommentar zu diesem Beitrag.