Работа с git и версионирование
Обзор
Git — твоя страховочная сетка. Относись к коммитам как к точкам сохранения, к веткам — как к песочницам, к истории — как к документации. Когда AI-агенты порождают код на высокой скорости, дисциплина контроля версий — это механизм, который держит изменения управляемыми, пригодными для ревью и обратимыми.
Когда применять
Всегда. Любое изменение кода проходит через git.
Базовые принципы
Разработка вокруг ствола (рекомендуется)
Держи main всегда готовой к выкатке. Работай в короткоживущих ветках фич, которые вливаются обратно за 1–3 дня. Долгоживущие ветки разработки — это скрытые издержки: они расходятся, порождают конфликты слияния и откладывают интеграцию. Исследования DORA стабильно показывают, что разработка вокруг ствола коррелирует с высокоэффективными инженерными командами.
main ──●──●──●──●──●──●──●──●──●── (всегда готова к выкатке)
╲ ╱ ╲ ╱
●──●─╱ ●──╱ ← короткоживущие ветки фич (1–3 дня)
Это рекомендуемое умолчание. Команды, использующие gitflow или долгоживущие ветки, могут адаптировать принципы (атомарные коммиты, мелкие изменения, описательные сообщения) под свою модель ветвления — дисциплина коммитов важнее конкретной стратегии ветвления.
- Ветки разработки — это издержки. Каждый день жизни ветки накапливает риск слияния.
- Релизные ветки приемлемы. Когда нужно стабилизировать релиз, пока main движется вперёд.
- Фича-флаги > длинные ветки. Лучше выкатывать незаконченную работу под флагом, чем неделями держать её в ветке.
1. Коммить рано, коммить часто
Каждый успешный инкремент получает свой коммит. Не копи большие незакоммиченные изменения.
Рабочий ритм:
Реализовал срез → Тест → Проверил → Коммит → Следующий срез
А не так:
Реализовал всё → Понадеялся, что работает → Гигантский коммит
Коммиты — это точки сохранения. Если следующее изменение что-то сломает, можно мгновенно откатиться к последнему заведомо рабочему состоянию.
2. Атомарные коммиты
Каждый коммит делает одну логическую вещь:
# Хорошо: каждый коммит самодостаточен
git log --oneline
a1b2c3d Add task creation endpoint with validation
d4e5f6g Add task creation form component
h7i8j9k Connect form to API and add loading state
m1n2o3p Add task creation tests (unit + integration)
# Плохо: всё смешано
git log --oneline
x1y2z3a Add task feature, fix sidebar, update deps, refactor utils
3. Описательные сообщения
Сообщения коммитов объясняют почему, а не только что:
# Хорошо: объясняет замысел
feat: add email validation to registration endpoint
Prevents invalid email formats from reaching the database.
Uses Zod schema validation at the route handler level,
consistent with existing validation patterns in auth.ts.
# Плохо: описывает то, что и так видно в диффе
update auth.ts
Формат:
<тип>: <короткое описание>
<необязательное тело, объясняющее почему, а не что>
Типы:
feat— новая функциональностьfix— исправление багаrefactor— изменение кода, которое не чинит баг и не добавляет функциональностиtest— добавление или обновление тестовdocs— только документацияchore— инструменты, зависимости, конфигурация
4. Разделяй разные вещи
Не смешивай изменения форматирования с изменениями поведения. Не смешивай рефакторинг с фичами. Каждый тип изменения — отдельный коммит, а в идеале отдельный пулл-реквест:
# Хорошо: разное — раздельно
git commit -m "refactor: extract validation logic to shared utility"
git commit -m "feat: add phone number validation to registration"
# Плохо: всё вперемешку
git commit -m "refactor validation and add phone number field"
Отделяй рефакторинг от работы над фичей. Рефакторинг и фича — два разных изменения, отправляй их раздельно. Так каждое изменение легче отревьюить, откатить и понять в истории. Мелкие уборки (переименование переменной) можно на усмотрение ревьюера включить в коммит с фичей.
5. Соразмеряй изменения
Целься в ~100 строк на коммит/пулл-реквест. Изменения больше ~1000 строк надо разбивать. Как разбивать крупные изменения — см. стратегии разбиения в code-review-and-quality.
~100 строк → Легко отревьюить, легко откатить
~300 строк → Приемлемо для одного логического изменения
~1000 строк → Разбить на более мелкие изменения
Стратегия ветвления
Ветки фич
main (всегда готова к выкатке)
│
├── feature/task-creation ← Одна фича на ветку
├── feature/user-settings ← Параллельная работа
└── fix/duplicate-tasks ← Исправления багов
- Ответвляйся от
main(или ветки по умолчанию, принятой в команде) - Держи ветки короткоживущими (вливай за 1–3 дня) — долгоживущие ветки это скрытые издержки
- Удаляй ветки после вливания
- Для незаконченных фич предпочитай фича-флаги долгоживущим веткам
Именование веток
feature/<короткое-описание> → feature/task-creation
fix/<короткое-описание> → fix/duplicate-tasks
chore/<короткое-описание> → chore/update-deps
refactor/<короткое-описание> → refactor/auth-module
Работа с worktree
Для параллельной работы AI-агентов используй git worktree, чтобы вести несколько веток одновременно:
# Создать worktree под ветку фичи
git worktree add ../project-feature-a feature/task-creation
git worktree add ../project-feature-b feature/user-settings
# Каждый worktree — отдельный каталог со своей веткой
# Агенты могут работать параллельно, не мешая друг другу
ls ../
project/ ← ветка main
project-feature-a/ ← ветка task-creation
project-feature-b/ ← ветка user-settings
# Когда закончил — влить и прибраться
git worktree remove ../project-feature-a
Преимущества:
- Несколько агентов могут одновременно работать над разными фичами
- Не нужно переключать ветки (у каждого каталога своя)
- Если эксперимент провалился — удали worktree, ничего не потеряно
- Изменения изолированы до явного слияния
Паттерн точек сохранения
Агент начинает работу
│
├── Вносит изменение
│ ├── Тест прошёл? → Коммит → Дальше
│ └── Тест упал? → Откат к последнему коммиту → Разбираться
│
├── Вносит следующее изменение
│ ├── Тест прошёл? → Коммит → Дальше
│ └── Тест упал? → Откат к последнему коммиту → Разбираться
│
└── Фича готова → Все коммиты образуют чистую историю
Этот паттерн означает, что ты никогда не теряешь больше одного инкремента работы. Если агент пошёл вразнос, git reset --hard HEAD вернёт к последнему успешному состоянию.
Сводки изменений
После любой правки давай структурированную сводку. Это упрощает ревью, документирует дисциплину границ и вскрывает непреднамеренные изменения:
ЧТО ИЗМЕНЕНО:
- src/routes/tasks.ts: добавлен middleware валидации на POST-эндпоинт
- src/lib/validation.ts: добавлена TaskCreateSchema на Zod
ЧЕГО Я НЕ ТРОГАЛ (намеренно):
- src/routes/auth.ts: там похожая дыра в валидации, но это вне задачи
- src/middleware/error.ts: формат ошибок можно улучшить (отдельная задача)
ВОЗМОЖНЫЕ ВОПРОСЫ:
- Схема Zod строгая — отклоняет лишние поля. Подтвердите, что так и надо.
- Добавлен zod как зависимость (72 КБ в gzip) — уже был в package.json
Этот паттерн рано ловит неверные допущения и даёт ревьюерам ясную карту изменения. Раздел «Чего я не трогал» особенно важен: он показывает, что ты соблюдал границы и не устроил самовольный ремонт.
Гигиена перед коммитом
Перед каждым коммитом:
# 1. Посмотреть, что именно коммитишь
git diff --staged
# 2. Убедиться, что нет секретов
git diff --staged | grep -i "password\|secret\|api_key\|token"
# 3. Прогнать тесты
npm test
# 4. Прогнать линтер
npm run lint
# 5. Прогнать проверку типов
npx tsc --noEmit
Автоматизируй это через git-хуки:
// package.json (через lint-staged + husky)
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}
Что делать со сгенерированными файлами
- Коммить сгенерированные файлы только если проект этого ожидает (например,
package-lock.json, миграции Prisma) - Не коммить результат сборки (
dist/,.next/), файлы окружения (.env) и настройки IDE (.vscode/settings.json, если они не общие) - Иметь
.gitignore, покрывающий:node_modules/,dist/,.env,.env.local,*.pem
Использование git для отладки
# Найти коммит, который внёс баг
git bisect start
git bisect bad HEAD
git bisect good <known-good-commit>
# Git переключается на середины диапазона; на каждой гоняй свой тест, чтобы сузить
# Посмотреть, что изменилось недавно
git log --oneline -20
git diff HEAD~5..HEAD -- src/
# Найти, кто последним менял конкретную строку
git blame src/services/task.ts
# Поискать ключевое слово в сообщениях коммитов
git log --grep="validation" --oneline
Релизы и версионирование
Коммиты — то, как отслеживаешь изменения ты; версия — то, как их отслеживают твои потребители. В тот момент, когда от твоего кода начинает зависеть что-то ещё — другая команда, опубликованный пакет, развёрнутый клиент, — «последнее из main» перестаёт быть достаточным ответом на вопрос «что у меня запущено и безопасно ли обновляться?». Номер версии и changelog — это контракт, который на него отвечает.
Семантическое версионирование
Для всего, у чего есть потребители, версионируй как MAJOR.MINOR.PATCH и пусть число несёт смысл:
MAJOR ломающее изменение — потребителям придётся менять свой код, чтобы обновиться
MINOR новая функциональность, обратно совместимая — обновляться безопасно
PATCH исправление бага, обратно совместимое — обновляться безопасно
Число — это обещание, поэтому пусть код ему соответствует. «Патч», меняющий поведение, на которое полагались потребители, — это мажорное изменение в маскировке (закон Хайрама, см. скилл api-and-interface-design). Когда неясно, ломающее ли изменение, считай, что ломающее: неожиданный мажор гораздо дешевле сломанного потребителя.
Помечай релиз тегом, и пусть тег будет источником истины
Релиз — неизменяемая точка в истории, а не движущаяся ветка. Помечай его тегом, чтобы его всегда можно было воспроизвести:
git tag -a v1.4.0 -m "Release 1.4.0"
git push origin v1.4.0
Выводи версию из тега, а не правь её руками в разбросанных файлах, — тогда артефакт, тег и changelog никогда не разойдутся.
Веди changelog, написанный для людей
Changelog — это не git log. Это отобранный, обращённый к потребителю ответ на вопрос «что изменилось и важно ли мне это?» — сгруппированный по разделам Добавлено / Изменено / Исправлено / Устарело / Удалено / Безопасность, свежее сверху, каждая запись сформулирована через влияние на пользователя, а не через внутреннюю механику.
## [1.4.0] - 2025-06-12
### Добавлено
- Массовый импорт задач через CSV
### Исправлено
- Сдвиг часовых поясов в сроках повторяющихся задач
### Устарело
- `GET /v1/tasks/all` — используйте постраничный `GET /v1/tasks` (удаление в 2.0)
Пиши запись в том же изменении, которое её вызывает, пока влияние свежо, а не восстанавливай раскопками по коммитам во время релиза. Ломающие изменения получают заметку о миграции и окно устаревания (по скиллу deprecation-and-migration); собственно выкатка релиза — работа скилла shipping-and-launch, а этот раздел — контракт версионирования, который его питает.
Типовые самооправдания
| Самооправдание | Как на самом деле |
|---|---|
| «Закоммичу, когда фича будет готова» | Один гигантский коммит невозможно отревьюить, отладить или откатить. Коммить каждый срез. |
| «Сообщение не важно» | Сообщения — это документация. Будущему тебе (и будущим агентам) понадобится понять, что изменилось и почему. |
| «Потом всё сожму в один коммит» | Сжатие уничтожает повествование о разработке. Лучше сразу делать чистые инкрементальные коммиты. |
| «Ветки — лишние накладные расходы» | Короткоживущие ветки бесплатны и не дают конфликтующей работе столкнуться. Проблема в долгоживущих — вливай за 1–3 дня. |
| «Разобью это изменение потом» | Крупные изменения труднее ревьюить, рискованнее выкатывать и труднее откатывать. Разбивай до отправки, а не после. |
| «Мне не нужен .gitignore» | До того момента, пока не закоммитится .env с продакшн-секретами. Настрой немедленно. |
| «Это же мелкая правка, подниму патч» | Проверь, что могут наблюдать потребители. Изменение поведения, на которое они полагались, — это мажор, каким бы маленьким ни был дифф. |
| «Changelog — это просто лог коммитов» | Коммиты для тебя, changelog для потребителей и отобран по влиянию. Сгенерированный из сырых коммитов, он хоронит главное. |
| «Напишем changelog во время релиза» | К тому моменту влияние восстанавливается по памяти, и половина теряется. Пиши запись вместе с изменением. |
Тревожные признаки
- Копятся большие незакоммиченные изменения
- Сообщения коммитов вида «fix», «update», «misc»
- Изменения форматирования вперемешку с изменениями поведения
- В проекте нет
.gitignore - Коммитятся
node_modules/,.envили артефакты сборки - Долгоживущие ветки, сильно разошедшиеся с main
- Принудительная отправка (force-push) в общие ветки
- Ломающее изменение выпущено под минорным или патч-поднятием версии
- Релиз без тега или номер версии, правленный руками и разошедшийся с тегом
- Пользовательский релиз без записи в changelog или changelog, куда просто свалены сообщения коммитов
Проверка
Для каждого коммита:
- Коммит делает одну логическую вещь
- Сообщение объясняет «почему» и следует соглашениям о типах
- Тесты проходят до коммита
- В диффе нет секретов
- Изменения только форматирования не смешаны с изменениями поведения
-
.gitignoreпокрывает стандартные исключения
Для каждого релиза (всего, у чего есть потребители):
- Поднятие версии соответствует изменению: ломающее → мажор, добавляющее → минор, исправление → патч
- Релиз помечен тегом, а версия выведена из тега, а не правлена руками вразнобой
- В changelog есть отобранная, читаемая человеком запись для этой версии, сгруппированная по влиянию