Git: история, которую можно читать через год
Через год после написания кода вы не вспомните, почему выбрали именно это решение. Git-история — единственное место, где это могло сохраниться. Обычно там написано «fix», «fix2» и «работает».
Атомарный коммит
Коммит должен содержать одно логически завершённое изменение — такое, которое можно откатить целиком, не задев ничего постороннего.
Практический признак: если в описании коммита приходится писать «и», скорее всего, это два коммита. «Добавил валидацию email и обновил зависимости» — точно два.
Отдельно стоит держать рефакторинг и изменение поведения. Когда переименование двухсот переменных смешано с правкой логики, увидеть эту правку в диффе невозможно — а именно её и нужно проверить на ревью.
Сообщение коммита
Работает простая структура: короткая строка в повелительном наклонении, пустая строка, затем объяснение почему.
Исправить утечку соединений в пуле воркеров
Соединение возвращалось в пул только при успешном ответе.
При исключении оно оставалось занятым, и через несколько часов
пул исчерпывался — воркер вставал без явной ошибки в логах.
Возврат перенесён в блок finally.
Тело сообщения — это то, чего нет в диффе. Дифф показывает, что изменилось; сообщение объясняет, зачем, и какие альтернативы были отвергнуты.
Merge или rebase
Спор бесконечный, но правило простое.
Rebase хорош для своей ветки, которую ещё никто не видел: он превращает историю в линейную последовательность и убирает шум промежуточных merge-коммитов.
Merge — для интеграции в общую ветку: он сохраняет факт, что работа велась параллельно, и не переписывает существующие коммиты.
Единственное жёсткое ограничение: не переписывайте историю, которую уже забрали другие. Rebase создаёт новые объекты с новыми хешами, и у коллег после этого расходится история.
Приведение ветки в порядок перед PR
Интерактивный rebase позволяет собрать серию «fix, fix2, ещё раз fix» в один осмысленный коммит:
git rebase -i origin/master
В открывшемся списке pick заменяется на
squash (объединить с предыдущим), reword
(переписать сообщение) или drop (выбросить). Порядок
строк тоже можно менять.
Когда нужно найти, где сломалось
git bisect находит виновный коммит двоичным поиском —
на истории в тысячу коммитов это около десяти проверок:
git bisect start
git bisect bad # текущее состояние сломано
git bisect good v1.4.0 # здесь работало
# Git переключается на середину; проверяем и отвечаем
git bisect good # или git bisect bad
git bisect reset
Процесс автоматизируется, если проверка описывается командой:
git bisect run pytest tests/test_orders.py
Именно ради этого стоит держать коммиты атомарными: bisect укажет на коммит, и если тот меняет одну вещь, причина видна сразу.
Полезное в повседневной работе
# Кто и когда менял конкретные строки
git log -L 10,25:src/pricing.py
# Найти коммит по содержимому изменения
git log -S "calculate_discount" --oneline
# Отменить коммит, уже попавший в общую ветку, новым коммитом
git revert <hash>
# Временно отложить незакоммиченное
git stash push -m "черновик валидации"
Что в итоге
История коммитов — это документация, которую невозможно забыть обновить: она пишется одновременно с кодом. Атомарные коммиты и объяснение причин в сообщении стоят пары минут при написании и экономят часы через год — включая ваши собственные.