Инженерия

От conventional commits к changelog

5 мин чтения обновлено

Conventional commits бесплатно дают changelog три вещи: тип каждого изменения, часть системы, которую оно затронуло, и ломает ли оно что-то. Больше они не дают ничего. Формулировка, группировка и отбор, которые и есть changelog, остаются полностью открытыми, и pipeline, притворяющийся иначе, доставляет отформатированный git log.

feat(exports): add CSV column selection
fix(auth): reject expired refresh tokens
chore(deps): bump node-pg to 8.11

Три коммита в формате Conventional Commits. Из них машина может сказать вам, что один — это функция, один — исправление, один — уборка, и какую часть системы затронул каждый. Это по-настоящему полезно, и это всё обещание конвенции: история коммитов, которую может прочитать что-то помимо человека. Ошибка — думать, что это даёт вам changelog. Это даёт вам сырьё.

Что определяет конвенция?

Тип, необязательный scope, и описание: type(scope): description. Типы условно feat, fix, chore, docs, refactor, test, perf, build, ci. Две вещи помечают breaking change: ! перед двоеточием, или футер BREAKING CHANGE:. Инструменты ориентируются на feat и fix для minor и patch версий, и на маркер breaking для major.

Коммит даёт вамChangelog нуждается вКто заполняет разрыв
feat / fix / choreAdded / Fixed / внутреннееОтображение, автоматическое
(scope)Группировку, которую узнаёт читательЧеловек, раз на scope
! или BREAKING CHANGE:Кто ломается, до когда, и что делатьЧеловек, каждый раз
Описание, написанное для ревьюераРезультат, написанный для клиенткиЧеловек, каждая запись
Один коммитОдно изменение, которое может быть многими коммитамиПравила squash, или человек

Маркер сообщает это инструменту; он не сообщает это вызывающей стороне, что является темой как деприкейтить API и что такое breaking change. Это маленькая спецификация, и её стоит соблюдать, даже если вы никогда ничего из неё не генерируете, потому что она заставляет принять одно решение на коммит: видят ли пользователи это изменение, или нет.

Где останавливаются conventional commits?

Они останавливаются на предложении. Всё, что захватывает конвенция, — это метаданные об изменении; само изменение всё ещё описано словарём ревьюера.

Сообщения коммитов написаны для ревьюеров. fix(auth): reject expired refresh tokens корректно и ничего не говорит клиентке. Читательница changelog хочет «вас выйдут из системы, когда сессия действительно истечёт, вместо периодических 401».

Scope внутренние. exports, auth, ingest — это названия модулей. Они стабильны, что делает их хорошими для группировки, и бессмысленны для тех, кто вне кодовой базы.

Одно изменение часто — это много коммитов. Функция, объединённая через одиннадцать коммитов, создаёт одиннадцать записей, десять из которых шум, а сжатие их для сокрытия этого теряет историю ревью.

chore — это мусорка, а не категория. Обновления зависимостей, изменения CI и переименования попадают туда все, и некоторые важны для пользователей, пока большинство нет.

Итак: конвенция бесплатно даёт вам тип, scope и статус breaking, и оставляет формулировку, группировку и отбор полностью открытыми. Эти три — и есть changelog. Кто на самом деле отвечает за запись в changelog разбирает, кто должен заниматься этой формулировкой, группировкой и отбором, поскольку сама конвенция не имеет на этот счёт мнения.

Как генерируется changelog из conventional commits?

В двух слоях, и второй должен быть обязательным.

Слой первый, автоматический. При merge выводите черновик записи из коммита: тип отображён на тип changelog (feat на Added, fix на Fixed, маркер breaking на Changed плюс флаг), scope сохранён как метаданные, а не текст, ссылка на PR. Поместите его в раздел Unreleased, который требует Keep a Changelog.

Слой второй, человеческий, и необходимый. Прежде чем выйдет релиз, каждая черновая запись либо получает однострочное переписывание словарём пользователя, либо помечается как внутренняя и убирается из публичного вида. Это шаг, который люди пытаются пропустить, и его пропуск создаёт changelog, читающиеся как диф.

Важная деталь дизайна в том, что второй слой не опционален в pipeline. Если релиз можно выпустить с неотредактированными черновиками, это случится, в неделю, когда все заняты. Какие шаги принадлежат машине, а какие человеку — вся суть автоматизации changelog.

Вырезка релиза — это и момент, когда git-тег, релиз и эта запись changelog либо совпадают, либо начинают расходиться; git-теги, релизы и ваш changelog разбирает, как держать эти три вещи синхронизированными.

Три ловушки

Squash merge съедает футеры. Если ваша платформа сжимает с заголовком PR как сообщением, футер BREAKING CHANGE: коммита внутри этой ветки исчезает, и ваши инструменты молча перестают видеть breaking change. Проверьте, что на самом деле сохраняет ваш шаблон squash.

Коммиты revert создают призрачные записи. fix, который откатывается на следующий день, создаёт запись для того, что никогда не было выпущено, если только вывод не примиряет revert-ы. Большинство инструментов этого не делают.

Повышение версии и changelog рассинхронизируются. Если версия вычисляется из коммитов, а changelog пишется вручную после, они расходятся примерно за два релиза. Вычисляйте оба за один проход или примите, что один из них неверен.

Если вам нужна механическая часть без pipeline

Наш генератор changelog выполняет шаг вывода в браузере: вставьте коммиты, получите сгруппированные, типизированные записи. Он намеренно детерминирован и полностью клиентский, поэтому вставляемые вами коммиты никогда не покидают вашу машину, что важно, когда сообщения из приватного репозитория. Он честно выполняет половину сбора и не пытается сделать второй слой, потому что второй слой — это суждение, а инструмент, притворяющийся им, создаёт именно тот changelog, против которого выступает эта статья.

Для версии pipeline, инструменты для changelog охватывает, что существует.

Резюме

Conventional commits надёжно и дёшево отвечают на «какого рода это изменение». Они не отвечают на «что мы должны сказать людям», и никакое количество инструментов поверх сообщения коммита этого не сделает, потому что информация никогда не была в сообщении коммита. Бюджетируйте на переписывание.

FAQ

Генерируют ли conventional commits changelog автоматически? Они автоматически генерируют черновик: типизированные, со scope, связанные записи. Формулировка для клиентки, группировка и решение о том, что опустить, всё ещё нуждаются в человеке, и pipeline, пропускающий этот шаг, публикует сообщения коммитов.

Какие типы conventional commit появляются в changelog? feat и fix всегда, как Added и Fixed. perf обычно, как Changed. chore, docs, refactor, test, build и ci по умолчанию внутренние и появляются только если человек продвигает один из них.

Как conventional commits помечают breaking change? ! после типа или scope (feat(api)!: ...), или футер BREAKING CHANGE: в теле коммита. Оба теряются, если squash merge сохраняет только заголовок PR.

Нужны ли conventional commits для автоматизации changelog? Нет. Метки PR, шаблоны PR и ссылки на issue несут те же метаданные для команд, которые объединяют через pull request. Conventional commits — самый дешёвый вариант, когда единицей изменения является коммит.


Технические утверждения в этой статье не проходили независимую проверку. Если здесь что-то не так, сообщите нам, и мы исправим.

По теме на changeloop: Генератор changelog, Сравнение инструментов changelog

changeloop
Команда, которая делает changelog, замыкающий цикл. Пользователи о чём-то просят, ваша команда это делает, тот, кто просил, узнаёт об этом.