Ежедневный рабочий процесс
Что это такое
Ежедневный рабочий процесс — небольшой набор операций, которые вы выполняете постоянно: подготовить ровно то, что войдёт в коммит, создать ветку под кусок работы, свести свою работу с чужой и что-то отменить. Каждая из них — прямое следствие объектной модели. Сами команды значат куда меньше, чем решение, которое каждая из них выражает, поэтому понимание, зачем вы их запускаете, делает их безопасными.
Почему это важно
Большинство людей учит git плохо: копируя команды из результата поиска в момент паники. Это работает, пока ситуация не отличается чуть-чуть от той, что в ответе, а тогда уничтожает работу. Команды здесь — те самые, что всплывают в панические моменты, — reset, revert, rebase, force push, — и у каждой точный смысл: какие коммиты она двигает, какие файлы меняет и заметит ли это кто-нибудь ещё; знание этого превращает грозный инструмент в обычный.
Ключевые идеи
Подготовка выбирает снимок
Коммит — это содержимое индекса, а не рабочего дерева. git add позволяет решить, что в этот снимок войдёт, и поэтому коммитить всё сразу часто ошибка: коммит, где рефакторинг смешан с исправлением, трудно рецензировать и невозможно откатить чисто. Интерактивная подготовка позволяет выбирать фрагменты, так что одно логическое изменение становится одним коммитом.
working tree: fix + rename + debug print
stage: the fix only, then commit
result: a commit that can be reviewed and reverted on its own
Ветка — это имя, которое вы двигаете
Создание ветки ничего не стоит, поэтому заводите её под каждый кусок работы: незаконченная работа не попадает на основную линию, а изменение можно рецензировать как единое целое. git switch покрывает обычный случай смены ветки, а более старый checkout решает ту же задачу среди прочих. Именно поэтому новая команда читается яснее.
git switch -c fix/rounding # start a line of work
git switch main # abandon the context, not the work
Слияние соединяет две линии; fast-forward обходится без коммита
Слияние фиксирует, что две линии истории сошлись. Если цель не двигалась, git просто передвигает имя вперёд — fast-forward, оставляя линейную историю. Если двинулись обе стороны, он создаёт коммит слияния с двумя родителями. Это не накладные расходы: он фиксирует точку интеграции, и его сохранение показывает, когда функция приземлилась как единое целое.
A <- B <- C (main)
\ \
D <- E <- M (feature merged into main)
Rebase переписывает коммиты, поэтому он для личной истории
git rebase проигрывает ваши коммиты на новой базе, создавая новые коммиты с новыми хешами. На ветке, которая есть только у вас, это сохраняет историю линейной и читаемой. На ветке, которую другие уже стянули, он заменяет коммиты, которые у них есть, и их следующая выгрузка дублирует работу или требует force push, который выбрасывает чужие изменения. Правило простое: rebase — для того, что вы не делили, слияние — для того, что уже разделили.
before: A <- B <- C (main)
\ D <- E (feature)
rebase onto main: A <- B <- C <- D' <- E'
Отмена зависит от того, чего вы хотите на самом деле
Это разные задачи под похожими именами. Исправить сообщение или содержимое последнего неотправленного коммита — amend, двинуть ветку назад, потому что последние коммиты были неверны и ни у кого их нет, — reset, отменить коммит, который уже есть у других, — revert (новый коммит, который обращает старый), а отбросить изменения в одном файле — restore. Сначала назовите задачу — и перестанете тянуться не за тем инструментом.
"wrong message, not pushed" -> amend
"these three commits were a dead end" -> reset to the last good commit
"this shipped and is wrong" -> revert the commit
"undo my edits to this file" -> restore the file from the last commit
Практика
Разделите одно изменение на два коммита
Внесите изменение, содержащее две несвязанные правки. Подготовьте и закоммитьте их по отдельности, затем посмотрите диф каждого коммита.
Готово, когда: каждый коммит читается как одна самостоятельная идея, а откат любого оставляет другой нетронутым.
Отмените тремя разными способами
В черновом репозитории попрактикуйте всё три: исправьте последнее сообщение, откатите ветку на два коммита назад и отмените коммит, уже находящийся в основной ветке.
Готово, когда: для каждого случая вы можете сказать, какие коммиты сдвинулись, какие файлы изменились и заметит ли это другой клон.
Частые ошибки
- Коммит всего одним сообщением — каждая несвязанная правка запутывается навсегда; исправить: подготовьте одно логическое изменение и закоммитьте только его.
- Rebase ветки, которую уже стянули другие — их коммиты заменяются, и история расходится; исправить: сливайте общие ветки и делайте rebase только своей работы.
- Reset, чтобы отменить уже опубликованное — клоны коллег сохраняют старые коммиты, и следующая отправка возвращает их; исправить: используйте revert.
- Разрешение конфликта взятием одной стороны целиком — замысел другой стороны молча исчезает; исправить: прочитайте все три версии и сведите смысл.
- Force push без проверки того, что будет потеряно — чужие коммиты выбрасываются на удалённом репозитории; исправить: сначала посмотрите разницу и предпочитайте force, который падает, если удалённый репозиторий ушёл вперёд.
Что читать дальше
- Объектная модель и история (01-cs-fundamentals/12-git/01-the-object-model-and-history) — что именно двигают эти команды, так что прочитайте сначала её.
- Стратегии ветвления в Git (01-cs-fundamentals/22-git-branching-strategies) — о чём команда договаривается насчёт веток, когда механика стала привычкой.
- GitHub (01-cs-fundamentals/23-github) — где коммиты, которые вы готовите и отправляете, становятся общим pull request.