Что делает коммит хорошим
Хороший коммит атомарен — одно логическое изменение само по себе — с понятным сообщением: тема в повелительном наклонении до ~50 символов, пустая строка, затем тело про «зачем». Разбираем Conventional Commits (type(scope): subject) и как разбить шумную работу на чистые коммиты.
Двое разработчиков выкатывают одну и ту же фичу. Один оставляет единственный коммит: git commit -m "stuff", сваливший вместе исправление бага, переименование и новый эндпоинт. Другой оставляет три коммита, каждый про одно, каждый с предложением, объясняющим зачем. Полгода спустя кто-то ловит регрессию и запускает git bisect или git log. Первая история бесполезна — каждый коммит спутанный ком. Вторая читается как changelog. Тот же код, разительно разная поддерживаемость.
К концу этого урока ты сможешь писать атомарные коммиты с сообщениями, которыми будущий инженер реально воспользуется, и разбивать грязный сеанс правок на чистые коммиты.
После этого урока ты сможешь объяснить, что делает коммит атомарным, писать сообщение коммита с темой в повелительном наклонении и телом про «зачем», применять формат Conventional Commits и разбивать одно шумное изменение на несколько чистых коммитов.
Хороший коммит атомарен: он фиксирует ровно одно логическое изменение, и проект на этом коммите всё ещё собирается и проходит тесты. Атомарный не значит «маленький» — он значит самодостаточный. Исправление бага — один коммит; переименование переменной — другой; новая фича — третий. Проверка: можешь ли ты откатить этот единственный коммит начисто, не утащив с ним несвязанную работу? Если откат «исправить логин» отменяет и переименование, коммит не был атомарным. Атомарные коммиты заставляют git revert, git bisect, git cherry-pick и код-ревью работать так, как они задуманы.
Строка темы — это однострочное резюме в повелительном наклонении, до примерно 50 символов. Пиши её как команду — что применение коммита сделает, — а не как отчёт в прошедшем времени:
Add rate limiting to login endpoint ← повелительное, хорошо
Added rate limiting ← прошедшее время, избегай
Fixes the thing where login breaks ← размыто, избегайПовелительное наклонение естественно читается с собственной фразой git: «если применить, этот коммит Add rate limiting…». Держи тему до ~50 символов, чтобы git log --oneline и GitHub её не обрезали. Заглавная первая буква, без точки в конце — это заголовок, а не предложение.
После темы оставь пустую строку, затем тело, объясняющее ЗАЧЕМ — не что. Diff и так показывает, что изменилось; задача тела — рассуждение, которое diff передать не может:
Add rate limiting to login endpoint
Brute-force attempts were spiking auth CPU and occasionally
locking out real users. Cap to 5 attempts per IP per minute;
chose a sliding window over a fixed one to avoid burst gaming
at minute boundaries.
Closes #482Переноси тело примерно на 72 символах, чтобы оно хорошо читалось в терминалах и git log. Пустая строка между темой и телом обязательна — git и его инструменты опираются на неё, чтобы отделить заголовок от описания. Спустя месяцы «зачем» — единственное, что объясняет неочевидную строку.
Conventional Commits придают теме машиночитаемую структуру: type(scope): subject. Небольшой, фиксированный словарь типов префиксует сообщение:
feat(auth): add rate limiting to login
fix(api): correct off-by-one in pagination
refactor(db): extract query builder
docs(readme): document env vars
test(cart): cover empty-basket checkout
chore(deps): bump eslint to 9.xЧастые типы — feat, fix, refactor, docs, test и chore; необязательный (scope) называет затронутую область. Команды принимают это, потому что оно разбираемо: инструменты автоматически генерируют changelog, а feat против fix против маркера !/BREAKING CHANGE автоматически двигает семантическое версионирование. Формат превращает сообщения коммитов в инфраструктуру релизов.
▸Почему это работает
Почему атомарность делает git bisect волшебным? Bisect бинарным поиском проходит твою историю, чтобы найти коммит, внёсший баг, — он переключается на коммит, ты помечаешь его хорошим или плохим, и он делит диапазон пополам. Это работает, только если каждый коммит собирается и представляет одно изменение: «плохой» коммит тогда указывает на одно логическое изменение, а не на путаницу из пяти. Один мега-коммит, мешающий исправление, переименование и фичу, заставляет bisect приземлиться на ком, который тебе всё равно придётся распутывать руками, — ты выбросил ту самую точность, ради которой инструмент существует.
Разбиваем спутанный день на чистую историю.
Ты провёл день, и в рабочем дереве теперь три несвязанные вещи: исправление в auth.js, переименование util.js → helpers.js по всему коду и новый эндпоинт /health. Коммит всего разом дал бы один неоткатываемый ком. Вместо этого ты индексируешь по смыслу:
git add auth.js
git commit -m "fix(auth): reject expired session tokens"
git add util.js helpers.js src/ # переименование и места вызова
git commit -m "refactor: rename util to helpers"
git add routes/health.js app.js
git commit -m "feat(api): add /health liveness endpoint"Для исправления бага ты добавляешь тело с объяснением соображений безопасности (git commit без -m открывает редактор). Теперь git log --oneline читается как три намеренных шага. Если эндпоинт /health позже вызовет проблему, git revert уберёт только тот коммит — исправление и переименование нетронуты. Каша из дня стала историей, которую коллега может прочитать и пройти bisect.
▸Частая ошибка
Распространённый антипаттерн — «свалка WIP»: кодить весь день, затем git add . && git commit -m "wip" в конце. Это быстро, но уничтожает всякую пользу истории — нельзя откатить одну часть, bisect приземляется на стену несвязанных изменений, а ревью безнадёжно. Если ты честно закоммитил грязный work-in-progress локально, причеши его перед публикацией (интерактивным rebase, позже), чтобы опубликованная история была атомарной. Правило большого пальца: сообщения коммитов пишутся для того, кто будет дебажить твой код в 2 часа ночи через полгода, — часто это ты сам.
Какой коммит лучше всего следует конвенциям атомарного коммита с хорошим сообщением?
Хороший коммит атомарен — одно логическое изменение, которое собирается и проходит само по себе, так что git revert, git bisect и ревью работают начисто. У его сообщения тема в повелительном наклонении до ~50 символов («Add», не «Added»), обязательная пустая строка, затем тело, объясняющее зачем, с переносом на ~72 символах. Conventional Commits структурируют тему как type(scope): subject с типами feat, fix, refactor, docs, test, chore, что позволяет инструментам автоматически генерировать changelog и двигать семантические версии. Разбивай шумную работу на односмысловые коммиты, индексируя подмножества, — избегай свалки «wip», выбрасывающей всякую пользу истории. Теперь ты завершил цикл записи изменений: индексируй точно, читай, что изменил, игнорируй лишнее и записывай это как чистую атомарную историю.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.