Changelog'и и релизы: генерируемые заметки, метки PR и человекочитаемая запись
Changelog — человекочитаемый дифф между двумя тегами. Генерируй его из conventional commits или меток PR, чтобы он не разошёлся с отгруженным, прикрепи к неизменяемому GitHub Release, связанному с тегом и артефактом, и запись станет аудируемым ответом на что изменилось.
Запрос аудита был в одну строку: «покажите каждое изменение, тронувшее логику округления платежей за последний квартал, и кто его одобрил». У команды был CHANGELOG.md — поддерживаемый руками, что значило: это художественное произведение. Половина записей гласила «различные фиксы и улучшения». У трёх релизов записи не было вовсе, потому что кто-то забыл. Одна запись описывала фичу, откаченную до отгрузки. Реальные изменения жили в 1400 коммитах и стене смерженных PR, и реконструкция аудит-следа руками заняла у двух инженеров три дня. Action item постмортема был не «писать changelog’и лучше». Он был «перестать их писать»: выводить релизные заметки механически из тех же коммитов и PR, что произвели сборку, чтобы запись не могла разойтись с отгруженным — и прикреплять эту запись к неизменяемому релизу, который аудитор читает напрямую.
Changelog — производный артефакт, а не рукописный
Вспомни, сколько релизных заметок ты писал под давлением — и сколько из них были точными три месяца спустя. Changelog отвечает человеку на два вопроса: что изменилось между прошлым релизом и этим и надо ли что-то делать перед апгрейдом. Отказ из Hook был обращением с ним как с прозой, что кто-то пишет — что гарантирует расхождение с реальностью, потому что писатель — отдельный, ошибающийся шаг от отгрузки. Фикс — сделать его функцией тех же входов, что произвели сборку. Доминируют два источника:
- Conventional commits: релиз-инструмент группирует коммиты с последнего тега по типу —
feat:под Features,fix:под Bug Fixes, всё сBREAKING CHANGE:под заметную секцию Breaking Changes — и выдаёт Markdown-секцию на релиз. Поскольку коммиты те же, что собрали артефакт, заметки не могут описать то, что не отгрузилось. - Метки / заголовки PR: автоматические релизные заметки GitHub (и инструменты вроде
release-drafter) категоризируют смерженные PR по метке —type: feature,type: fix,breaking— в настроенные секции и линкуют каждую строку на её PR и автора. Это модель, когда ты squash-merge’ишь и единица изменения — заголовок PR, а не отдельные коммиты.
В любом случае changelog генерируется в CI на момент тега, дописывается в CHANGELOG.md и используется как тело релиза. Дисциплина смещается с «пиши хорошие записи» на «размечай PR и пиши хорошие темы коммитов/PR» — работа, что уже происходит на каждом изменении, принуждаемая проверкой меток/commit-lint, так что запись — побочный продукт, а не рутина.
Почему changelog, сгенерированный из conventional commits или меток PR, надёжнее для аудита, чем поддерживаемый руками CHANGELOG.md?
GitHub Release — это запись, а не файл
CHANGELOG.md удобен для просмотра в репо, но несущий артефакт — это GitHub Release: объект, привязанный к конкретному тегу, несущий генерируемые заметки как тело, артефакты сборки (или ссылки на опубликованный образ/пакет) и — через тег — неизменяемый ref и digest, что отгрузил релиз. Релиз — точка соединения всего в этом юните: semver-имя, неизменяемый тег, digest отгруженного, человекочитаемый changelog и (опционально) provenance-аттестация, доказывающая, как это собрано. Это соединение — ровно то, чего хотел аудитор и что не мог дать поддерживаемый руками файл — единая запись на версию, связывающая что изменилось с что отгрузилось и кто одобрил. Считай опубликованный релиз неизменяемым по духу: ты правишь заметки для ясности, но версия, тег и артефакт, на которые он указывает, не меняются; последующий фикс — это новый релиз, а не правка старого. Pre-release (v2.5.0-rc.1, помеченный «pre-release») позволяют release candidate’ам нести генерируемые заметки и артефакты, не появляясь как «Latest»-релиз, так что потребители, пинующие latest, не вытягиваются на незаконченную сборку.
Команда хочет единое самое полезное место для ответа «что отгрузилось в v2.5.0 и как вернуть ровно те байты?». Какой артефакт связывает всё воедино и почему?
▸Почему это работает
Зачем привязывать changelog к релизному объекту, а не просто держать Markdown-файл? Потому что файл оторван от артефакта: ничто не мешает CHANGELOG.md говорить одно, пока задеплоенный образ — другое, и так файл из Hook сполз в выдумку. Релизный объект заякорен на тег, а тег резолвится в digest, так что заметки, версия и точные отгруженные байты — одна неделимая запись. Когда аудитор — или дежурный, решающий, на что откатить — открывает релиз, он видит описание изменения и идентичность артефакта в одном месте, без шанса, что эти двое разойдутся.
- 01Сопоставь генерацию релизных заметок из conventional commits и из меток PR — когда каждое подходит и что у них общего?
- 02Почему объект GitHub Release, а не CHANGELOG.md, — артефакт, что делает аудит и откат единственным lookup'ом?
Changelog отвечает человеку на два вопроса — что изменилось с прошлого релиза и что надо сделать перед апгрейдом — и надёжен лишь когда перестаёт быть прозой, что кто-то пишет, и становится функцией входов сборки. Генерируй его из conventional commits, группируя feat: под Features, fix: под Bug Fixes и BREAKING CHANGE: под заметную секцию, либо из размеченных смерженных PR через авто-заметки GitHub или release-drafter, линкуя каждую строку на её PR и автора; в любом случае он выдаётся в CI на момент тега и не может описать неотгруженное изменение, потому что выведен из тех же коммитов/PR, что произвели артефакт. Дисциплина двигается с написания записей на принуждение честных типов коммитов и меток PR lint-проверкой — работа, что уже происходит на каждом изменении. Несущая запись — не CHANGELOG.md, а GitHub Release (релизный объект, привязанный к неизменяемому тегу): он несёт генерируемые заметки, артефакты или ссылки и через тег digest отгруженного, плюс опционально provenance-аттестацию. Этот релиз — точка соединения всего юнита: semver-имя, неизменяемый тег, digest артефакта, человекочитаемый changelog и одобрение в одной записи на версию — что превращает «покажи каждое изменение и кто одобрил» и «восстанови ровно эти байты» из многодневной археологии в единственный lookup. Правь заметки для ясности, но никогда версию или артефакт, на которые указывает опубликованный релиз; последующее — это новый релиз, а release candidate отгружается как помеченный pre-release, чтобы пинующие latest потребители никогда не вытягивались на незаконченную сборку. Теперь, когда встретишь в репо поддерживаемый руками changelog, ты знаешь точно, насколько далеко он ушёл от реальности — и чем его заменить.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.