Системы контроля версий
Git выглядит набором команд, но под ними лежит очень маленькая модель, и почти каждый вопрос на собеседовании проверяет именно её. Коммит — неизменяемый объект, который хранит снимок всего дерева файлов, ссылки на родительские коммиты, автора, коммиттера и сообщение; идентификатор коммита — хэш от всего этого содержимого. Поэтому смена родителя или метаданных даёт другой коммит с другим хэшем, а не «отредактированный старый». Ветка — не контейнер с коммитами, а обычная ссылка, файл с одним хэшем внутри; HEAD — символическая ссылка на текущую ветку. Между рабочим каталогом и репозиторием стоит индекс — черновик следующего коммита, куда git add кладёт содержимое файлов.
Из этих трёх сущностей выводится всё остальное. merge дописывает в граф коммит с двумя родителями и не трогает существующие — история остаётся правдивой, но ветвистой. rebase пересоздаёт ваши коммиты поверх чужой вершины — история становится линейной ценой новых хэшей, из-за чего удалённая ветка расходится с локальной и обычный push отклоняется. cherry-pick копирует изменение одного коммита, оставляя оригинал на месте. Хуки — исполняемые файлы, которые Git запускает в фиксированных точках и слушает их код возврата.
Самого Python в Git нет, но он есть в последствиях. У Python нет компилятора, который поймал бы неудачно применённый патч — после сомнительного rebase или cherry-pick модуль по-прежнему импортируется и падает лишь в рантайме, на конкретной ветке кода. Поэтому шлюзом качества служит pre-commit — фреймворк, кстати, сам написанный на Python, — гоняющий ruff, black и mypy до записи коммита. Три ловушки назовём сразу — rebase опубликованной ветки, --force вместо --force-with-lease и вера в то, что хук на вашей машине что-то гарантирует всей команде.
Карта темы
- Git Flow и модель ветвления — ветка как подвижная ссылка и пять ролей веток —
master,develop,feature,release,hotfix. - merge против rebase —
mergeдописывает в граф коммит с двумя родителями,rebaseпересоздаёт ваши коммиты заново с новыми хэшами. - rebase, интерактив и force push — перепроигрывание коммитов, todo-список
rebase -iи почему--force-with-leaseбезопаснее--force. - cherry-pick — копия изменения — перенос одного коммита между ветками, новый хэш, ловушки зависимостей и дубликатов.
- Хуки и pre-commit — порядок запуска клиентских хуков, код возврата как вето и почему хук не заменяет CI.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Считать ветку контейнером коммитов | Непонятно, почему создание ветки мгновенно и почему её удаление не удаляет ни одного коммита |
Делать rebase ветки, которую уже забрали коллеги | У них остаются старые коммиты, у вас новые копии тех же изменений; при следующем слиянии история задваивается |
Использовать git push --force вместо --force-with-lease | Коммиты, отправленные коллегой после вашего fetch, становятся на удалёнке недостижимыми и молча пропадают |
Ожидать, что cherry-pick сохранит хэш или уберёт коммит из исходной ветки | Появляется копия с новым хэшем, оригинал остаётся на месте — при последующем слиянии возможны дубли и конфликты |
Отводить hotfix от develop, а не от master | В продакшен вместе с исправлением уезжает ещё не выпущенный код из develop |
Слить release только в master, забыв про develop | Правки, сделанные при стабилизации релиза, теряются и возвращаются регрессией в следующей версии |
Считать pre-commit серверной или GitHub-функцией | Проверка отключается флагом --no-verify и не работает у того, кто не поставил хук |
| Форматировать файлы в хуке и не добавлять их в индекс заново | В коммит уходит старое содержимое из индекса, а исправленное остаётся только в рабочем каталоге |
Значение для собеседований
На junior-уровне тему трогают коротко и проверяют не команды, а картину мира. Про Git Flow ждут два долгоживущих ствола (master с релизами, develop с интеграцией) плюс три короткоживущих типа веток с правилом «откуда отвели — туда и вернули», где release и hotfix возвращаются в обе. Про cherry-pick ждут слово «копирует», а не «переносит». Про pre-commit ждут, что это клиентский хук самого Git, отменяющий коммит ненулевым кодом возврата, а вовсе не возможность хостинга.
С middle начинается главный вопрос темы — разница merge и rebase. Правильный ответ строится от модели — merge создаёт коммит с двумя родителями и не меняет ни одного существующего хэша, rebase создаёт новые коммиты с тем же диффом, но другим родителем, поэтому и хэши другие; отсюда сам собой выводится запрет на rebase опубликованных веток. Дальше идут интерактивный rebase (squash сохраняет оба сообщения, fixup выбрасывает своё; удаление строки в todo-списке удаляет коммит) и senior-вопрос про force push — почему обычный push отклоняется, чем --force-with-lease отличается от --force и как git reflog возвращает потерянное. Ошибка здесь одна и та же — кандидат помнит рецепт «сделал rebase, сделал force push», но не может сказать, что именно проверяет lease.