Как писать хорошие сообщения коммитов: Conventional Commits
Практика3 минРедакция Innovialabs
Коротко
Хороший коммит объясняет, что и зачем изменилось, одной короткой строкой; Conventional Commits добавляет к ней тип вроде feat или fix, и по этим типам инструменты сами считают версию и пишут changelog.
Короткий ответ
Хорошее сообщение коммита — это короткая строка в повелительном наклонении, по которой через полгода понятно, что изменилось, плюс тело с объяснением «зачем», если это не очевидно. Conventional Commits — договоренность о формате этой строки:
<тип>(<область>): <описание>
<тело — необязательно>
<футер — необязательно>
Например: fix(cart): не сбрасывать корзину после обновления страницы.
Базовые правила любого коммита
- Один коммит — одно логическое изменение. Не смешивайте фикс бага, переименование и обновление зависимостей.
- Заголовок короткий, примерно до 50–72 символов, чтобы не обрезался в
git log --onelineи интерфейсах. - Повелительное наклонение: «add», «fix», «remove» — как будто вы даете команду кодовой базе. В русскоязычных командах часто пишут «добавить», «исправить».
- Без точки в конце заголовка.
- Пустая строка между заголовком и телом.
- Тело отвечает на «зачем», а не пересказывает diff. Что изменилось — видно в коде, почему — нет.
- Ссылки на задачу — в футере:
Refs: #123.
Плохо: fix, правки, wip, update files.
Хорошо: fix(auth): обновлять токен до истечения срока, а не после.
Формат Conventional Commits
Спецификация обязательными называет только типы feat и fix, остальные — общепринятое соглашение (их предлагают, например, конфиги commitlint):
| Тип | Когда использовать |
|---|---|
feat | новая функциональность для пользователя |
fix | исправление бага |
docs | только документация |
style | форматирование, без изменения логики |
refactor | изменение кода без новых функций и фиксов |
perf | улучшение производительности |
test | добавление или правка тестов |
build | сборка, зависимости |
ci | настройки CI |
chore | прочие служебные изменения |
Область (scope) в скобках необязательна и показывает модуль: feat(api), fix(checkout).
Breaking changes
Несовместимое изменение помечается двумя способами — восклицательным знаком или футером:
feat(api)!: удалить устаревший эндпоинт /v1/orders
BREAKING CHANGE: клиенты должны перейти на /v2/orders.
Примеры
feat(search): добавить фильтр по цене
fix(payments): корректно обрабатывать повторный callback от провайдера
docs: описать запуск проекта в Docker
refactor(user): вынести валидацию в отдельный модуль
perf(catalog): кешировать список категорий
Зачем это нужно: changelog и версии
Структурированные сообщения можно разбирать машиной. Это связывает коммиты с семантическим версионированием (SemVer):
| Коммиты с момента релиза | Новая версия |
|---|---|
только fix | patch: 1.4.2 → 1.4.3 |
есть feat | minor: 1.4.2 → 1.5.0 |
есть ! или BREAKING CHANGE | major: 1.4.2 → 2.0.0 |
Инструменты вроде semantic-release, release-please или standard-version читают историю, вычисляют следующую версию, генерируют CHANGELOG.md с разделами «Features» и «Bug Fixes» и создают тег. Типы docs, chore, style в changelog обычно не попадают.
Дополнительный плюс — история становится удобной для поиска: git log --grep "^fix(payments)".
Как внедрить в команду
- Договоритесь о списке типов и областей и запишите их в README или CONTRIBUTING.
- Добавьте проверку: commitlint с конфигом
@commitlint/config-conventionalчерез git hook или в CI. - Если используете squash merge, проверяйте заголовок PR — именно он станет сообщением коммита в main.
- Подключите автоматический релиз, когда формат приживется.
Частые ошибки
- Все подряд помечают
chore. Тогда changelog пустой, а версии не растут. featдля внутренних изменений.feat— то, что заметит пользователь продукта или API.- Забытый
BREAKING CHANGE. Потребители получат несовместимое обновление как minor. - Тип есть, смысла нет:
fix: fix bugничем не лучше, чем простоfix.