Пошук уроків, статей та іншого контенту
Формуйте коміти за стандартом Conventional Commits для автоматизації changelog, перевірок і релізів.
Conventional Commits — це домовленість про єдиний формат повідомлень Git-комітів.
Замість довільних повідомлень:
змінив авторизаціюкоманда використовує структуровані повідомлення:
feat(auth): додати вхід через GoogleТакий формат дає змогу автоматизувати:
формування changelog;
перевірку повідомлень під час створення коміту;
визначення версії наступного релізу;
пошук комітів певного типу;
виявлення змін, які порушують зворотну сумісність.
Conventional Commits стосується саме повідомлення коміту, а не назви гілки чи тексту pull request.
Базова форма:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]Приклад:
feat(api): додати пагінацію для списку користувачівПовідомлення складається з таких частин:
type — тип зміни;
scope — необов’язкова область коду;
description — короткий опис;
body — необов’язкове розгорнуте пояснення;
footer — необов’язкова додаткова інформація.
Тип описує призначення зміни. Найчастіше використовують:
feat — додавання нової функціональності;
fix — виправлення помилки;
docs — зміни документації;
style — зміни форматування, які не впливають на логіку;
refactor — зміна структури коду без виправлення помилки чи додавання функціональності;
perf — покращення продуктивності;
test — додавання або зміна тестів;
build — зміни системи збирання або залежностей;
ci — зміни конфігурації CI;
chore — інші технічні зміни, які не змінюють функціональність.
Наприклад:
fix: виправити обробку порожнього списку
docs: описати формат помилок API
test: додати тести для сервісу замовлень
refactor: спростити перевірку прав доступуТип feat зазвичай означає функціональну зміну, яку можна показати користувачеві. Тип fix означає виправлення неправильного поводження програми.
Область, або scope, уточнює, яку частину системи змінено:
feat(auth): додати відновлення пароля
fix(cart): правильно обчислювати загальну вартість
test(users): додати перевірку ролейScope необов’язковий. Його варто використовувати, якщо в проєкті є зрозумілий і стабільний набір компонентів:
auth
api
cart
database
uiНе потрібно додавати scope лише для того, щоб повідомлення виглядало складніше:
fix(code): виправити кодЯкщо область не додає корисної інформації, її краще пропустити:
fix: виправити обробку помилкиПісля двокрапки пишуть короткий опис зміни:
feat(profile): додати редагування аватараРекомендації:
описуйте, що змінилося;
не завершуйте короткий опис крапкою;
не пишіть загальні фрази на кшталт оновити код;
не змішуйте в одному коміті кілька незалежних змін;
використовуйте одну мову та один стиль у всій команді.
Порівняйте:
fix: зміниі:
fix(parser): обробляти лапки всередині рядкаДругий варіант значно корисніший для перегляду історії та автоматичного changelog.
Для простих змін достатньо першого рядка:
fix(cache): не використовувати прострочені значенняЯкщо потрібен контекст, додайте тіло після порожнього рядка:
fix(cache): не використовувати прострочені значення
Перед читанням кешу перевіряємо час його створення.
Прострочені записи видаляються та завантажуються повторно.У першому рядку має залишатися короткий підсумок. Тіло пояснює:
чому виникла проблема;
як саме її вирішено;
які обмеження залишилися;
чому обрано конкретний підхід.
Не варто копіювати в тіло весь diff. Код уже доступний у коміті, а повідомлення має пояснювати мотивацію та наслідки зміни.
Зміна є breaking change, якщо старий код, API або спосіб використання більше не працює сумісно з новою версією.
Є два поширені способи позначити таку зміну.
Знак ! ставлять перед двокрапкою:
feat(api)!: перейменувати поле userId на idЯкщо використовується scope:
refactor(auth)!: замінити формат токена доступуBREAKING CHANGEМожна додати пояснення в нижній частині повідомлення:
feat(api): змінити формат відповіді профілю
Поле `name` тепер повертається як окремі `firstName` і `lastName`.
BREAKING CHANGE: клієнти мають припинити використовувати поле `name`.Знак ! зручний для коротких повідомлень, а footer дає змогу пояснити міграцію.
За потреби можна використовувати обидва способи:
feat(api)!: змінити формат відповіді профілю
BREAKING CHANGE: поле `name` видалено, використовуйте `firstName` і `lastName`.Для автоматичного визначення breaking changes інструмент має враховувати і !, і footer BREAKING CHANGE:.
Footer розміщують після порожнього рядка. Він може містити метадані коміту:
fix(api): повертати статус 404 для невідомого користувача
Refs: PROJ-142Або:
fix(auth): не скидати сесію після оновлення профілю
Closes: PROJ-318Назви footer узгоджуються всередині команди. Важливо не змішувати кілька форматів без потреби.
Посилання на задачу не замінює опис зміни:
fix: PROJ-318Це недостатньо інформативно. Краще:
fix(auth): не скидати сесію після оновлення профілю
Refs: PROJ-318Повідомлення можна передати безпосередньо команді git commit:
git add src/auth.js
git commit -m "feat(auth): додати вихід із системи"Для тіла та footer зручніше відкрити редактор:
git add src/auth.js
git commitУ редакторі введіть:
fix(auth): продовжувати сесію після оновлення токена
Клієнт оновлює access token до завершення терміну його дії.
Це запобігає непередбаченому виходу користувача із системи.
Refs: PROJ-204Один коміт має представляти одну логічну зміну. Наприклад, не слід об’єднувати в одному коміті:
виправлення помилки в API;
форматування всього проєкту;
оновлення документації.
Краще створити окремі коміти:
fix(api): виправити перевірку прав доступу
style: відформатувати файли API
docs(api): оновити опис відповідіОскільки формат є угодою, його потрібно перевіряти автоматично. Перевірка під час створення коміту не дозволяє поступово накопичувати повідомлення, які неможливо надійно обробити.
Нижче наведено простий валідатор на Node.js без зовнішніх залежностей. Він перевіряє:
наявність файлу повідомлення;
дозволений тип;
необов’язковий scope;
знак breaking change;
двокрапку та пробіл після заголовка;
непорожній опис;
правильний footer BREAKING CHANGE.
// validate-commit.js
const fs = require("node:fs");
const filePath = process.argv[2];
if (!filePath) {
console.error("Використання: node validate-commit.js <шлях-до-повідомлення>");
process.exit(1);
}
if (!fs.existsSync(filePath)) {
console.error(`Файл не знайдено: ${filePath}`);
process.exit(1);
}
const message = fs.readFileSync(filePath, "utf8").trimEnd();
const lines = message.split(/\r?\n/);
const header = lines[0];
const allowedTypes = new Set([
"build",
"chore",
"ci",
"docs",
"feat",
"fix",
"perf",
"refactor",
"revert",
"style",
"test",
]);
const headerMatch = header.match(
/^([a-z]+)(\(([^)\n]+)\))?(!)?: ([^\n]+)$/
);
if (!headerMatch) {
console.error(
"Неправильний формат заголовка. Очікується: type(scope): description"
);
process.exit(1);
}
const [, type, , scope, breakingMark, description] = headerMatch;
if (!allowedTypes.has(type)) {
console.error(`Непідтримуваний тип коміту: ${type}`);
process.exit(1);
}
if (scope && scope.trim().length === 0) {
console.error("Scope не може бути порожнім");
process.exit(1);
}
if (description.trim().length === 0) {
console.error("Опис коміту не може бути порожнім");
process.exit(1);
}
const hasBreakingFooter = lines.some((line) =>
/^BREAKING CHANGE: .+/.test(line)
);
if (hasBreakingFooter && !message.includes("BREAKING CHANGE:")) {
console.error("Некоректний footer BREAKING CHANGE");
process.exit(1);
}
if (breakingMark || hasBreakingFooter) {
console.log("Коміт позначено як breaking change");
}
console.log("Повідомлення коміту має коректний базовий формат");Створіть тестовий файл:
feat(api)!: змінити формат відповіді
BREAKING CHANGE: поле `name` більше не повертається.Запустіть перевірку:
node validate-commit.js commit-message.txtРезультат:
Коміт позначено як breaking change
Повідомлення коміту має коректний базовий форматcommit-msg hookGit передає шлях до файлу повідомлення в hook commit-msg. Тому валідатор можна підключити так:
#!/bin/sh
# Перевірити повідомлення коміту перед його створенням
node scripts/validate-commit.js "$1"Збережіть скрипт у .git/hooks/commit-msg і надайте йому право виконання:
chmod +x .git/hooks/commit-msgТепер Git запускатиме перевірку для кожного локального коміту. Якщо валідатор завершиться з кодом 1, коміт не буде створено.
Локальні hooks не потрапляють до репозиторію як частина Git-конфігурації. Тому в командних проєктах перевірку також варто запускати в CI. Це гарантує однакові правила для всіх учасників і для комітів, створених в інших середовищах.
Структуровані коміти можна фільтрувати стандартними командами Git:
git log --oneline --grep='^feat'Показати виправлення:
git log --oneline --grep='^fix'Показати всі коміти між двома тегами:
git log v1.4.0..v1.5.0 --onelineОтримати повідомлення у форматі, зручному для подальшого оброблення:
git log v1.4.0..HEAD --pretty=format:'%s'Наприклад, історія:
fix(auth): продовжити термін дії сесії
feat(api): додати пагінацію
docs(api): описати параметри пагінації
refactor(db): винести побудову запитівможе бути розподілена за категоріями changelog:
Features — feat;
Bug Fixes — fix;
Documentation — docs;
Performance — perf;
Refactoring — refactor.
Інструмент генерації changelog може використовувати тип, scope, тіло та footer без додаткового аналізу довільного тексту.
Тип коміту може використовуватися для автоматичного визначення рівня оновлення версії.
Типова домовленість має такий вигляд:
fix — patch-оновлення;
feat — minor-оновлення;
breaking change — major-оновлення.
Наприклад:
fix(parser): виправити обробку пробілівможе збільшити версію з 1.4.0 до 1.4.1.
feat(parser): додати підтримку вкладених виразівможе збільшити версію з 1.4.0 до 1.5.0.
feat(parser)!: видалити застарілий формат конфігураціїможе збільшити версію з 1.4.0 до 2.0.0.
Це не автоматична властивість Git. Правила версій визначає інструмент або процес релізу, який аналізує коміти.
Якщо останній коміт ще не опубліковано, його повідомлення можна виправити:
git commit --amend -m "fix(api): повернути правильний статус помилки"Якщо потрібно змінити старіший коміт, це зазвичай роблять під час інтерактивного rebase. Такі зміни переписують історію, тому опубліковані коміти не слід змінювати без узгодження з командою.
Якщо коміт уже потрапив у спільну гілку, часто безпечніше додати новий коміт із правильним повідомленням, а не переписувати історію.
new: додати пошукЯкщо команда не домовилася про тип new, автоматичні інструменти можуть його не розпізнати.
Краще:
feat(search): додати пошукНеправильно:
feat додати пошукПравильно:
feat: додати пошукНеправильно:
fix: виправленняКраще:
fix(cart): не додавати товар із нульовою кількістюНеправильно:
feat: додати звіти, виправити логін і оновити документаціюКраще розділити зміни:
feat(reports): додати звіти
fix(auth): виправити вхід після завершення сесії
docs: оновити опис налаштування автентифікаціїНедостатньо написати:
feat(api): змінити формат відповідіЯкщо зміна несумісна зі старими клієнтами, це потрібно позначити:
feat(api)!: змінити формат відповідіабо:
feat(api): змінити формат відповіді
BREAKING CHANGE: клієнти мають використовувати поле `id` замість `userId`.Якщо частина комітів має англомовні описи, а частина — україномовні, changelog стає непослідовним. Команда має заздалегідь домовитися про:
мову описів;
дозволені типи;
формат scope;
максимальну довжину першого рядка;
формат footer;
правила позначення breaking changes.
Перед створенням коміту перевірте:
Чи описує коміт одну логічну зміну?
Який тип найточніше відповідає зміні?
Чи потрібен scope?
Чи короткий опис пояснює результат зміни?
Чи є зміна breaking change?
Чи потрібне тіло або посилання на задачу?
Чи пройде повідомлення автоматичну перевірку?
Приклад завершеного повідомлення:
feat(checkout): додати повторну оплату невдалого замовлення
Користувач може повторити оплату без повторного створення замовлення.
Для повторної спроби використовується ідентифікатор платіжної сесії.
Refs: PAY-87Conventional Commits задає структурований формат повідомлень Git-комітів.
Основна форма: type(scope): description.
Типи feat і fix використовують для функціональності та виправлень.
Scope уточнює компонент, якого стосується зміна.
Тіло коміту пояснює причини та наслідки зміни.
Несумісні зміни позначають ! або footer BREAKING CHANGE:.
Єдиний формат дає змогу автоматизувати перевірки, changelog і визначення версій.
Перевірку повідомлень можна запускати через commit-msg hook і CI.
Один коміт має містити одну логічну зміну та точний опис її результату.