linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it works
  • Уроки
  • База знаний
  • Собеседование
Часть III — Ежедневный Git

$ глава 7 · 40 минут

Коммиты по-взрослому

Коммитить умеют все. Коммитить хорошо - мало кто. Эта глава - про правила, которыми пользуются опытные команды: атомарность, предсказуемые сообщения, разделение работы на коммиты, в которых человек разберётся через год.

Звучит как излишний педантизм. На практике эти правила окупаются: bisect находит регрессию за секунды, blame отвечает на вопрос «почему это здесь», cherry-pick переносит один точечный фикс без побочных изменений. Все эти инструменты бесполезны на коммитах вида «fixed stuff».

7.1 Атомарность коммита

Атомарный коммит делает одну вещь. Не «допилил login + поменял header + обновил версию react». Три коммита: один про login, один про header, один про react.

Правила:

  1. Одна логическая правка - один коммит. Если в сообщении появляется «и», это часто два коммита.
  2. Сборка проходит на каждом коммите. Не «коммит N сломан, в N+1 починил». Каждый коммит должен компилироваться, тесты проходить. Иначе bisect сломается.
  3. Коммит обратим. Если этот коммит revert'нуть - приложение должно работать дальше. Не «без этого коммита всё развалится потому что в коммите N+5 мы на него опёрлись».
  4. Никаких "WIP", "fix typo", "addresses comments". Это процессуальный шум, которому не место в публичной истории. Перед PR такие коммиты сжимаются (squash или interactive rebase).

Зачем это нужно:

  • git bisect за log(N) шагов находит коммит-источник бага. Если в истории есть «сломанные» промежуточные коммиты - bisect врёт.
  • git revert <sha> откатит проблемный коммит без боли. Если в коммите три не связанных правки, revert заденет и невинные.
  • git blame покажет «кто и когда» написал строку с проблемой. Если коммит «допилил всё подряд» - не поможет понять контекст.

См. atomic-commit.

7.2 Структура сообщения коммита

Самый влиятельный стандарт - короткий пост Tim Pope «A Note About Git Commit Messages» (2008). Семь правил:

Краткая суть в повелительном наклонении, ≤ 50 символов
Развёрнутое описание, если нужно. Перенос по 72 колонкам.
Объясняет «почему», не «что» - что и так видно в diff'е.
- Допустимы маркеры
- И ссылки на тикеты
Closes #123
Co-authored-by: Имя <email@example.com>

Семь правил:

  1. Отделяй заголовок от тела пустой строкой.
  2. Заголовок ≤ 50 символов.
  3. Заголовок с заглавной буквы.
  4. Без точки в конце заголовка.
  5. Повелительное наклонение: Add, Fix, не Added, Adds.
  6. Тело перенесено по 72 колонкам.
  7. Тело объясняет «что» и «почему», не «как».

Повелительное - это не каприз. Если читать сообщение коммита как «если применить этот коммит, он …», то фраза должна продолжаться естественно: «Add login form» (если применить, добавит форму). «Added login form» - звучит криво. Так же пишут GitHub, GitLab и сам Git в авто-сообщениях merge/revert.

7.3 Conventional Commits

Поверх Tim Pope-стиля многие команды надстраивают формат - Conventional Commits. Идея: префикс заголовка по типу изменения.

<type>(<scope>): <subject>
[optional body]
[optional footer]

Типы:

  • feat - новая фича для пользователя
  • fix - баг-фикс
  • docs - только документация
  • style - форматирование, точки с запятой, whitespace
  • refactor - изменение кода без новой фичи и без фикса
  • perf - улучшение производительности
  • test - добавление/исправление тестов
  • chore - обслуживание (обновление зависимостей, конфигов)
  • build, ci - инфраструктура сборки и CI
  • revert - отмена другого коммита

Scope опционален - модуль или подсистема. Subject - то же, что в Tim Pope-стиле: повелительное, без точки, ≤ 50 (с учётом префикса).

Примеры:

feat(auth): add password reset flow
fix(api): handle null in user.profile
docs(readme): document SSL certificate setup
chore(deps): bump react to 18.3.1
refactor!: drop support for Node 16

Восклицательный знак ! после типа - пометка «ломающее изменение». Альтернатива - BREAKING CHANGE: в футере:

feat(api)!: rename POST /user to POST /users
BREAKING CHANGE: endpoint /user no longer exists,
use plural /users.

Зачем это надо. Главные две причины:

  1. Автоматическая генерация changelog. Инструменты вроде standard-version или release-please парсят историю, группируют по типам, создают CHANGELOG.md и git-тег.
  2. Автоматический выбор версии для semver. fix: → patch, feat: → minor, BREAKING CHANGE: → major.

Не нужен - если нет процесса автоматического релиза. Тогда возвращайся к простому Tim Pope-стилю, никто не пострадает.

См. conventional-commits.

7.4 Semver: что значат три цифры

Semver (semantic versioning) - конвенция: MAJOR.MINOR.PATCH, например 1.4.2.

  • PATCH (1.4.2) - баг-фиксы, обратно-совместимые. Если пользователь обновится с 1.4.1 на 1.4.2 - ничего не сломается.
    • MINOR (1.4.0) - новая функциональность, обратно совместимая. Можно использовать новое; старое продолжает работать.
    • MAJOR (1.0.0) - ломающие изменения. Что-то старое больше не работает или работает иначе. Обновление требует миграции.

Дополнительные ярлыки:

  • 1.4.2-beta.1, 1.4.2-rc.3 - pre-release.
  • 1.4.2+20260527.git7c8a1 - build-метаданные.

На что чаще ошибаются:

  • 0.x.y - пока major = 0, semver не действует строго. Версия считается «нестабильной», ломающие изменения могут быть и в minor. Это «обещание не давать обещаний».
  • 1.0.0 означает «API стабильный». Не «продакшен-готовый», а именно «теперь обещаю semver-совместимость». Многие проекты годами сидят на 0.x именно поэтому.
  • PATCH с переписанным внутри допустим, если поведение снаружи не поменялось. Внутренние правки никого не волнуют, пока публичный контракт остаётся.

Связь с Conventional Commits через инструменты типа semantic-release: посмотри коммиты с последнего тега, найди BREAKING CHANGE → major, feat: → minor, иначе patch. Создай тег, запушь. Всё автоматически.

См. semver.

7.5 git commit --amend: можно и нельзя

--amend переписывает последний коммит. Не создаёт новый - именно переписывает. Используется для:

  1. Поправить сообщение - git commit --amend -m "новое".
  2. Добавить забытый файл - git add forgot.txt && git commit --amend --no-edit. Файл добавится в предыдущий коммит, сообщение останется как было.
  3. Поправить содержимое - если только что закоммитил с ошибкой, можно отредактировать файлы, git add, git commit --amend.

Физически --amend создаёт новый коммит (с новым SHA, так как содержимое поменялось) и двигает ветку на него. Старый коммит остаётся в .git/objects/ как dangling - reflog его ещё месяц видит.

Когда нельзя

Если коммит уже запушен и кто-то его подтянул - --amend ломает историю. У тебя локально один SHA, у других разработчиков другой. При следующем push Git откажет:

! [rejected]        feature -> feature (non-fast-forward)

Варианты:

  • Force-push через --force-with-lease. Безопасно для своей feature-ветки, если никто другой её не успел затащить.
  • Не amend'ить, а сделать новый коммит. Это правильный вариант для общих веток (main, develop).

Правило: amend разрешён до первого push. После push - только на личных ветках и только с force-with-lease.

См. amend.

7.6 Разбить накопившуюся правку через git add -p

Часто случается: писал-писал, изменил пять файлов на разные темы. Хочется закоммитить их как три атомарных коммита.

git add -p (patch mode) разбирает каждый файл на hunk'и (куски ±3 строки контекста) и спрашивает по каждому:

Stage this hunk [y,n,q,a,d,s,e,?]?

Главные ответы:

  • y - застейджить
  • n - пропустить
  • s - разбить на более мелкие куски (если Git может)
  • e - открыть в редакторе и убрать ненужные строки руками
  • q - выйти, оставив остальные неrastress'енными

Типичный сценарий: одна правка касается auth/, вторая - billing/, третья - мелкий refactor в utils/.

bash
git add -p src/auth/
git commit -m "feat(auth): add OAuth provider"
git add -p src/billing/
git commit -m "fix(billing): handle null currency"
git add -p src/utils/
git commit -m "refactor(utils): extract date helpers"

Если правки переплетены внутри одного файла - Git разделит на hunk'и автоматически, и патч-режим спросит по каждому. Если Git не угадал (две правки на смежных строках) - s или e помогут.

Альтернатива GUI - git gui (idle интерфейс из стандартной поставки Git), там разделение по hunk'ам через клики мышью. Большинство IDE (VS Code, JetBrains) умеют то же самое в встроенной staging-панели.

7.6.1 Что НЕ класть в коммит

Так же важно знать, чего в коммит лучше не пускать.

Секреты. Пароли, токены, ключи. Если попали в commit и запушены - считай скомпрометированы. Даже если потом удалить коммит, история остаётся в reflog и у тех, кто успел склонить. Менять ключ - единственный надёжный путь. Защита - pre-commit hook с gitleaks или trufflehog, плюс scanner на сервере (GitHub Secret Scanning, GitLab Secret Detection).

Большие бинарные файлы. PSD, MP4, ZIP, дамп БД. Git хранит снимки, и каждое изменение бинаря - это полная копия. Репозиторий быстро разрастается. Решение - Git LFS (Large File Storage), при котором сам файл живёт во внешнем хранилище, а в Git только pointer.

Артефакты сборки. node_modules/, build/, *.pyc, target/. Они генерируются из исходников, дублируют объём, провоцируют конфликты. .gitignore обязателен.

IDE-файлы пользователя. .idea/workspace.xml, .vscode/* (кроме общих настроек), *.swp, *.bak. Часть общих настроек проекта (.vscode/settings.json, .editorconfig) можно и нужно коммитить.

Локальные конфиги с реальными данными. .env, config.local.json. Шаблон (.env.example) - да. Реальный файл - никогда.

Закомментированный мёртвый код. Git помнит всё; если код понадобится - посмотри в git log или git show. Хранить рядом для «вдруг» - путь к каше.

Уроки в sandbox

lab-7.1. Разбить смешанную правку на три атомарных коммита

Цель - освоить git add -p и понять, как из накопившейся работы вытащить чистые атомарные коммиты. После лабы становится понятно, что атомарность - не теория, а двухминутная привычка.

  1. Создай репозиторий с одним файлом и закоммить: mkdir atomic-lab && cd atomic-lab && git init && cat > server.js << 'EOF' function add(a, b) { return a + b } function sub(a, b) { return a - b } function mul(a, b) { return a * b } module.exports = { add, sub, mul } EOF git add server.js && git commit -m "initial server.js".

  2. Сделай три не связанных правки в одном файле: переименуй sub в subtract (refactor), добавь функцию div (feat), добавь null-проверку в add (fix). Открой server.js и поправь руками.

  3. Запусти git status - увидишь один modified файл. Запусти git diff - увидишь три не связанные правки.

  4. Запусти git add -p server.js. Git предложит первый hunk - скорее всего объединит несколько правок (они на близких строках). Ответь s для попытки разбить на меньшие куски, потом y/n для каждого.

  5. Цель - застейджить только refactor (переименование). Если разделить не получается чисто - e (edit) и убери из патча лишние строки руками. Закоммить: git commit -m "refactor: rename sub to subtract".

  6. Повтори с feat: git add -p server.js, выбери только добавление div. Закоммит: git commit -m "feat: add div function".

  7. Третий проход: всё что осталось - git add server.js && git commit -m "fix: handle null in add".

  8. Сверь итог: git log --oneline должен показать 4 коммита (initial + три по теме). git show HEAD~2, git show HEAD~1, git show HEAD - каждый показывает ровно одну логическую правку.

sandbox с автопроверкой - открыть в песочнице

Резюме

  • Атомарный коммит делает одну вещь. Сборка проходит на каждом коммите, коммит обратим через revert без побочных эффектов.
  • Сообщение коммита: повелительное наклонение, ≤ 50 символов в заголовке, пустая строка, тело на 72 колонки. Объясняет «почему», не «что».
  • Conventional Commits - формат `<type>(<scope>): <subject>`. Используется для авто-генерации changelog и выбора версии при релизе.
  • Semver: MAJOR.MINOR.PATCH. Patch - обратно-совместимые баг-фиксы, minor - новая совместимая фича, major - ломающие изменения.
  • `git commit --amend` переписывает последний коммит. Можно до первого push, нельзя - после (если ветку видят другие).
  • Накопившуюся смешанную правку разбивай через `git add -p`. По hunk'ам выбираешь, что войдёт в текущий коммит.
  • В коммит НЕ кладут: секреты, бинарные файлы, артефакты сборки, IDE-метаданные, реальные конфиги, закомментированный мёртвый код.

Контрольные вопросы

  1. Почему «один файл - один коммит» - плохое правило, а «одна логическая правка - один коммит» - хорошее?

    Показать ответ

    Файл и логическая правка - разные вещи. Один большой рефакторинг может тронуть 30 файлов и должен ехать одним коммитом. Один файл с двумя не связанными правками должен ехать двумя коммитами. Файлы - это структура хранения, коммиты - структура изменений. Правило «один файл = один коммит» приведёт к раздробленной истории, где git bisect не сможет нормально локализовать баг, потому что половинный коммит ломает сборку.

  2. Я сделал `git commit --amend` после `git push`. Теперь push отказывается. Что делать?

    Показать ответ

    Зависит от того, личная ли это ветка. Если это feature-ветка, которой пользуешься только ты - git push --force-with-lease (не --force!). Lease-вариант проверит, что remote не успел уйти вперёд, иначе откажет. Если ветку видят другие разработчики или это main/master - --force нельзя. Правильное решение - сделать обычный новый коммит с правкой, а amend забыть. Для будущего: договорись с собой не amend'ить после push на любые не-личные ветки.

  3. У меня в репозитории случайно оказался файл с паролем в открытом виде. Я его удалил, закоммитил, запушил. Этого достаточно?

    Показать ответ

    Нет. Удаление в новом коммите оставляет старый коммит с паролем в истории. Все, у кого есть копия (через clone, fork, зеркало, кэш CI/CD), видят пароль в git log -p или git show <старый-sha>. Считай пароль скомпрометированным - сначала смени его, потом думай про историю. Зачистка истории возможна через git filter-repo + force-push, но требует координации со всеми, кто работает с репо. Часто проще смириться, что в reflog'е оно ещё месяц, и сменить пароль.

  4. Зачем нужен префикс `feat:` / `fix:` в Conventional Commits, если у меня и так линейная история без релизов?

    Показать ответ

    Если нет процесса автоматического релиза - может и не нужен. Conventional Commits окупаются в двух сценариях: (1) автогенерация CHANGELOG.md из истории, (2) автоматический выбор semver-уровня при релизе. Если CHANGELOG ты пишешь руками, а версию ставишь руками - формат добавит только обсуждение в команде «как писать сообщения». Если запускаешь релизы через semantic-release / release-please / standard-version - формат становится обязательным, потому что эти инструменты только так умеют. Бери его именно под потребность, не «потому что модно».

  5. Чем `--amend --no-edit` отличается от `--amend` без флагов?

    Показать ответ

    Без флагов git commit --amend открывает редактор для редактирования сообщения. С --no-edit - оставляет старое сообщение как есть. Используется когда --amend нужен только чтобы подмешать в коммит дополнительные изменения (забытый файл, поправку существующего). Без --no-edit придётся редактор каждый раз закрывать вручную, что замедляет повтор.

← Предыдущая06-branchesСледующая →08-merge-vs-rebase
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки