Культура code review и Conventional Commits

Рейтинг: 55.2% · 12 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

Культура code review и Conventional Commits

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits (вы здесь)
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Боль, которую решает этот урок

Ты открыл pull request. Прошла неделя. Ревьюер написал "посмотрю позже". Потом написал 40 комментариев. Ты переписал половину, конфликты разрешал три раза подряд (одни и те же), история коммитов превратилась в "fix", "fix2", "ну теперь точно", "ревью", "ревью2". Релиз-менеджер вручную собирает changelog по этой каше и ставит версию пальцем в небо.

Так выглядит code review без культуры. И ни Git, ни ревьюер тут ни при чём - проблема в процессе. Хороший PR проходит ревью с первого-второго раза не потому, что автор гений, а потому что он выстроил поток: маленький diff, чистая история, понятное описание, дисциплинированный цикл ответа на правки. А поверх этого - Conventional Commits, формат сообщений, из которого робот сам собирает changelog и считает версию по SemVer.

Этот урок про то, как делать pull request так, чтобы его хотелось мёржить, и как оформление коммитов превращается из бюрократии в автоматизацию. Механику rebase --autosquash мы дожмём в уроке 24, валидацию формата через хук commit-msg - в уроке 41, здесь же выстроим картину целиком и пройдём её руками.

Изображение

Анатомия PR, который проходит code review git с первого раза

Ревьюер - человек с ограниченным вниманием. Его пропускная способность падает нелинейно: diff на 50 строк он вычитает построчно и найдёт настоящие баги, diff на 1500 строк он пролистает и поставит "LGTM", потому что мозг сдаётся. Это доказанный эффект, а не лень. Значит, первое правило: маленький и сфокусированный diff. Один PR - одна логическая задача. Рефакторинг отдельно, фича отдельно, переименование файла отдельно. Если в одном PR ты и переименовал переменную в 200 местах, и поменял логику - настоящее изменение утонет в шуме.

Второе - осмысленная история. Ревьюер часто читает не итоговый diff, а коммиты по очереди, как главы. Если коммиты чистые ("добавил парсер", "подключил парсер к роутеру", "тесты на парсер") - ревью идёт по нарастающей и логика видна. Чистую историю делают через интерактивный rebase перед публикацией: склеиваешь "fix typo" в нужный коммит, переставляешь, переписываешь сообщения.

Код: Выделить всё

git log --oneline feature/parser ^main
a1b2c3d добавил json-парсер
e4f5a6b подключил парсер к роутеру
b7c8d9e тесты на парсер
Вот так выглядит готовая к ревью ветка: три коммита, каждый - законченный шаг, ни одного "wip". Сравни с тем, что было до причёсывания: семь коммитов с "fix", "опять fix", "забыл файл". Разница - в уважении к чужому времени.

Третье - описание. В теле PR отвечай на три вопроса: что меняем, зачем (какую проблему/тикет закрываем), как проверить. Не "исправил баг", а "форма теряла данные при двойном сабмите - блокирую кнопку до ответа сервера, проверять на /checkout с медленной сетью". Ревьюеру не надо реконструировать твой замысел.

Четвёртое и недооценённое - саморевью. Перед тем как звать людей, открой собственный diff и прочитай его глазами врага. Закомментированный код, отладочный console.log, случайно закоммиченный .env, TODO без тикета - всё это ты обязан выловить сам. Команда git diff --staged перед коммитом и просмотр финального diff в интерфейсе PR экономят половину раундов ревью.

Код: Выделить всё

git diff --stat origin/main...HEAD
 src/parser/json.ts       | 84 +++++++++++++++++++
 src/router/index.ts      |  6 +-
 tests/parser.test.ts     | 41 ++++++++++
 3 files changed, 128 insertions(+), 3 deletions(-)
Видишь три файла и 128 строк - это здоровый PR. Увидел бы 30 файлов - сигнал резать на части.

Цикл ответа на ревью: git fixup autosquash на практике

Ревью пришло. Десять комментариев. Дальше есть два пути, и один из них портит всё.

Плохой путь: правишь и делаешь git commit --amend поверх старого коммита либо переписываешь историю на лету. Ревьюер открывает PR и не понимает, что изменилось с прошлого раунда - старые коммиты переписаны, его прежние комментарии повисли на исчезнувших строках. Он перечитывает весь PR заново. Ты только что удвоил ему работу.

Хороший путь: на каждую правку - отдельный коммит, адресно привязанный к тому коммиту, который ты чинишь. Для этого есть commit --fixup:

Код: Выделить всё

git add src/parser/json.ts
git commit --fixup a1b2c3d
Это создаёт обычный коммит, но с особым заголовком - "fixup! добавил json-парсер". Git запомнил, к какому коммиту относится правка. Во время ревью эти fixup-коммиты видны отдельно: ревьюер смотрит только их и видит ровно то, что ты поменял по его замечаниям. Когда правок несколько и они смысловые, можно зафиксировать ещё и новое сообщение через git commit --squash a1b2c3d - тогда при сборке Git предложит объединить тексты.

Когда ревью одобрено и пора мёржить - схлопываешь fixup-ы обратно в их родительские коммиты одной командой:

Код: Выделить всё

git rebase -i --autosquash main
Флаг --autosquash сам находит "fixup!"/"squash!" коммиты, переставляет каждый под его цель и проставляет действие fixup/squash в todo-листе. Тебе остаётся только сохранить. Результат - снова чистые три коммита, как будто правок и не было, но при этом весь процесс ревью был прозрачным. Это и есть связка git fixup autosquash: грязно и честно во время ревью, чисто перед мёржем.

Код: Выделить всё

pick   a1b2c3d добавил json-парсер
fixup  9f8e7d6 fixup! добавил json-парсер
pick   e4f5a6b подключил парсер к роутеру
fixup  1a2b3c4 fixup! подключил парсер к роутеру
Вот так autosquash раскладывает todo за тебя - fixup-строки уже стоят под своими pick и помечены как fixup. Чтобы не включать флаг руками каждый раз, поставь rebase.autosquash = true в конфиге. Глубокую механику интерактивного rebase и краевые случаи разберём в уроке 24 - здесь важно усвоить ритм: правка -> fixup -> в конце один autosquash.

rerere: не разрешай один конфликт дважды

Длинный PR, который ты несколько раз ребейзишь на свежий main, упирается в одну и ту же боль: при каждом ребейзе всплывает один и тот же конфликт в одном и том же месте, и ты разрешаешь его руками снова и снова. Git умеет это запомнить. Включи:

Код: Выделить всё

git config --global rerere.enabled true
rerere - это reuse recorded resolution, "переиспользуй записанное разрешение". Первый раз ты разрешаешь конфликт сам, Git запоминает пару "вот такой конфликт -> вот так я его решил". При следующем появлении того же конфликта он применяет твоё решение автоматически и говорит об этом:

Код: Выделить всё

Resolved 'src/router/index.ts' using previous resolution.
Тебе остаётся проверить и git add. На долгоживущих ветках и регулярных ребейзах это экономит часы и нервы. Записи лежат в .git/rr-cache. Важная оговорка: rerere доверяет тебе - если первый раз ты разрешил конфликт неправильно, он будет тиражировать ошибку. Поэтому первое разрешение делай внимательно.

Conventional Commits: формат, из которого рождаются changelog и версия

Теперь про оформление коммитов. Conventional Commits - это соглашение о структуре заголовка коммита:

Код: Выделить всё

<тип>[область]: краткое описание

[тело]

[футер]
Тип - из фиксированного набора: feat (новая функциональность), fix (исправление бага), docs, style, refactor, perf, test, build, ci, chore. Область в скобках необязательна и уточняет подсистему. Примеры:

Код: Выделить всё

feat(parser): поддержка вложенных массивов в json
fix(router): не терять query при редиректе
docs: описать переменные окружения в readme
refactor(auth): вынести проверку токена в middleware
Зачем эта дисциплина? Не ради красоты. Тип коммита - это машиночитаемый сигнал. fix означает патч-релиз, feat - минорный, а пометка о ломающем изменении - мажорный. Ломающее изменение помечают либо восклицательным знаком после типа, либо футером:

Код: Выделить всё

feat(api)!: убрать поле legacy_id из ответа

BREAKING CHANGE: клиенты, читавшие legacy_id, должны перейти на id.
Из этих сигналов инструмент сам считает следующую версию по SemVer (MAJOR.MINOR.PATCH) и собирает changelog, сгруппировав фичи и фиксы. Сравни два мира: в одном релиз-инженер вручную листает 80 коммитов и гадает, минорный это релиз или мажорный; в другом - формат коммита уже содержит ответ, и человек вообще не нужен в этой части. Заодно история становится читаемой: git log --oneline по типам сразу показывает, чего в релизе больше - фич или латания дыр.

Автоматизация релизов: semantic release и release please

Поверх Conventional Commits работают два популярных инструмента, и важно не путать их модели.

semantic release делает релиз сразу при пуше в релизную ветку: анализирует новые коммиты, вычисляет версию, проставляет git-тег, генерирует changelog и публикует пакет (npm, и т.д.) - всё в одном прогоне CI, без участия человека. Подходит командам, которым нужен непрерывный автоматический выпуск: смёржил feat - через минуту вышла новая минорная версия.

release please (от Google) работает иначе и часто удобнее для команд, которым нужен контроль момента релиза. Он не релизит сразу, а держит постоянно обновляемый Release PR: копит коммиты, в реальном времени пересчитывает будущую версию и changelog прямо в этом PR. Когда вы готовы выпустить - просто мёржите Release PR, и только тогда ставится тег и создаётся релиз. То есть релиз становится осознанным кликом, а не побочным эффектом мёржа фичи.

Общий принцип у обоих один: версия и changelog выводятся из истории коммитов, а не пишутся руками. Поэтому мусорный коммит "fix2" без типа - это не косметика, он реально ломает расчёт версии: инструмент его проигнорирует, и патч может не выйти, либо changelog недосчитается строки. Формат коммита - это вход в конвейер релиза. Отсюда прямое следствие: формат надо валидировать на входе.

Валидация формата: хук commit-msg (анонс урока 41)

Дисциплина, которую держат силой воли, рано или поздно протекает. Поэтому проверку формата автоматизируют локально - через клиентский хук commit-msg. Это скрипт в .git/hooks/commit-msg, которому Git передаёт путь к файлу с сообщением коммита; если скрипт вернёт ненулевой код, коммит отклоняется.

Код: Выделить всё

#!/bin/sh
# .git/hooks/commit-msg
pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\(.+\))?!?: .+'
if ! grep -qE "$pattern" "$1"; then
  echo "Сообщение не по Conventional Commits. Пример: feat(api): ..."
  exit 1
fi
Попытка закоммитить "поправил тут" завершится так:

Код: Выделить всё

$ git commit -m "поправил тут"
Сообщение не по Conventional Commits. Пример: feat(api): ...
Коммит не создан, формат под защитой ещё до пуша. В реальных командах это собирают через commitlint и менеджер хуков, чтобы хук жил в репозитории, а не только на твоей машине (хуки по умолчанию не клонируются - важная деталь). Полную механику хуков, их типы, обход и серверную сторону разберём в уроке 41 - сейчас держи в голове, что формат коммитов не на честном слове, а под автоматической проверкой.

Этика и тон ревью

Технику разобрали, но code review - это в первую очередь общение между людьми. Несколько правил, которые отличают команду, где ревью двигает дело, от команды, где его боятся.
  • Критикуй код, а не человека. "Эта функция делает три вещи, давай разобьём" вместо "ты опять намешал".
  • Различай уровни замечаний. Блокирующее ("тут утечка памяти") и вкусовое ("я бы назвал иначе") - это разные веса. Помечай некритичное как nit, чтобы автор понимал, что мёрж это не держит.
  • Объясняй "почему", а не только "что". "Перенеси проверку выше, иначе ниже разыменуем null" учит, а "перенеси выше" - приказывает.
  • Хвали хорошее. Если решение красивое - скажи об этом. Ревью не обязано состоять только из претензий.
  • Автор: не защищайся рефлекторно. Комментарий к коду - не к тебе. Если не согласен - аргументируй спокойно, остальное правь.
Хороший тон ревью - это не мягкотелость, а скорость: команда, которая не выясняет отношения в комментариях, мёржит быстрее.

Типичные грабли
  • Гигантский PR "за неделю работы" - его никто не вычитает по-настоящему. Режь на серию.
  • commit --amend во время ревью вместо fixup - теряются комментарии ревьюера и контекст раунда.
  • Забыл --autosquash перед мёржем - в main улетают сырые "fixup!" коммиты. Лечится rebase.autosquash = true.
  • rerere включил, но первый раз разрешил конфликт неверно - теперь он тиражирует ошибку. Чисти запись через git rerere forget <путь>.
  • Коммиты не по формату при включённом semantic release/release please - версия считается неверно или релиз не выходит. Спасает хук commit-msg.
  • BREAKING CHANGE написан в теле с опечаткой (BREAKING-CHANGE, нижний регистр) - инструмент не распознает мажор. Футер должен быть ровно "BREAKING CHANGE:" или тип с "!".
Мини-лаба: пройди цикл руками
  • Создай песочницу: git init lab-review && cd lab-review, переименуй ветку в main: git branch -m main.
  • Включи помощников: git config rerere.enabled true и git config rebase.autosquash true.
  • Сделай 2 коммита по формату: положи файл, git commit -m "feat(core): добавить сумматор"; правь, git commit -m "test(core): тест на сумматор".
  • Имитируй ревью: внеси правку в первый коммит, затем git add . && git commit --fixup <хеш_первого_коммита>. Посмотри git log --oneline - увидишь строку "fixup!".
  • Схлопни: git rebase -i --autosquash main. Сохрани todo не редактируя - fixup уже стоит под целью. Проверь git log: правка вмёржена в feat-коммит, "fixup!" исчез.
  • Поставь хук: создай .git/hooks/commit-msg из примера выше, chmod +x на него. Попробуй git commit --allow-empty -m "просто так" - должно отклонить. Потом git commit --allow-empty -m "chore: проверка хука" - пройдёт.
Контрольные вопросы
  • Почему во время ревью лучше делать commit --fixup, а не commit --amend?
  • Что именно делает флаг --autosquash при интерактивном rebase и какие коммиты он ищет?
  • Как из типа коммита (feat/fix и пометка ломающего изменения) выводится номер версии по SemVer?
  • Чем модель release please отличается от semantic release по моменту выпуска релиза?
Итог

PR проходит ревью с первого раза не магией, а процессом: маленький сфокусированный diff, чистая история, внятное описание, саморевью. Отвечаешь на правки честным циклом fixup -> autosquash, а rerere избавляет от повторного разрешения тех же конфликтов. Conventional Commits превращают сообщения коммитов в машиночитаемый сигнал, из которого semantic release или release please сами считают версию по SemVer и собирают changelog, а хук commit-msg стоит на входе и не пускает мусор. Глубже rebase - в уроке 24, глубже хуки - в уроке 41. А этику ревью держи всегда: ты ревьюишь код, а не человека.
👍2 ❤️4 🔥1 😄 🤔1
Аватара пользователя
arch_kun
Сообщения: 1
Зарегистрирован: 15 май 2026, 16:46

Re: Культура code review и Conventional Commits

Сообщение arch_kun »

Вот про fixup плюс autosquash наконец дошло, почему amend во время ревью бесит ревьюеров. Раньше тупо амендил и удивлялся, что комменты слетают.
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
johnny58
Сообщения: 1
Зарегистрирован: 14 май 2026, 11:04

Re: Культура code review и Conventional Commits

Сообщение johnny58 »

А хук commit-msg же локальный, его в репе нет по умолчанию. Получается у каждого в команде надо руками ставить или ждём урок 41 где про commitlint?
👍2 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Merge queue и merge trains: безопасное вливание в trunk
Следующая глава →
git reset: три дерева и отмена индексации и коммитов

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git commit: фиксация измененийGit hooks: хуки и автоматизация

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость