Продвинутый Git: rebase, cherry-pick, worktree, bisect и восстановление через reflog
Профессиональные техники Git с актуальными командами: switch и restore, interactive rebase и autosquash, worktree, автоматический bisect, reflog, sparse-checkout, безопасный force push и полезные настройки .gitconfig.
Базовых команд Git — commit, push, pull, merge — хватает на обычный день. Проблемы начинаются в необычный: срочный хотфикс в три релизные ветки, история из сорока «wip»-коммитов перед ревью, регрессия, которую никто не может локализовать, случайный reset --hard на ветке с работой за три дня. В этих ситуациях теряются часы, а иногда и код. Ниже — инструменты Git, которые решают такие задачи, с актуальными командами и предупреждениями о том, где они опасны. switch и restore: команды вместо перегруженного checkout Если вы до сих пор делаете всё через git checkout , начните с этого. В Git 2.23 (2019) у checkout появилось два более понятных преемника: git switch работает с ветками, git restore — с файлами. Checkout никуда не делся, но новые команды труднее применить не по назначению: нельзя случайно затереть файл, собираясь переключить ветку. # Переключиться на ветку git switch main # Создать ветку и переключиться на неё git switch -c feature/payments # Вернуться на предыдущую ветку git switch - # Отменить незакоммиченные изменения в файле git restore src/app.ts # Убрать файл из индекса, сохранив изменения в рабочей копии git restore --staged src/app.ts # Достать версию файла из другой ветки git restore --source=main -- src/config.ts Cherry-pick: перенести конкретный коммит git cherry-pick применяет изменения выбранного коммита к текущей ветке, не перенося ветку целиком. Типичные случаи: хотфикс нужно доставить в несколько поддерживаемых релизов; из неудачной ветки нужно спасти один готовый коммит. # Перенести один коммит и дописать в сообщение ссылку на оригинал git cherry-pick -x a1b2c3d # Перенести диапазон: коммиты после a1b2c3d и до f4e5d6c включительно git cherry-pick a1b2c3d..f4e5d6c # То же, но включая сам a1b2c3d git cherry-pick a1b2c3d^..f4e5d6c # Применить изменения без коммита, чтобы доработать git cherry-pick --no-commit a1b2c3d # После разрешения конфликта git cherry-pick --continue # Отказаться от операции git cherry-pick --abort Флаг -x стоит сделать привычкой для хотфиксов: в сообщении нового коммита появится строка «cherry picked from commit …», и через полгода будет понятно, откуда взялось изменение. Важный нюанс: cherry-pick создаёт новый коммит с тем же содержимым, но другим хешем. Чтобы увидеть, какие изменения из одной ветки уже есть в другой, используйте сравнение через три точки: # Коммиты release/2.3, изменений которых ещё нет в main (перенесённые cherry-pick скрываются) git log --oneline --cherry-pick --right-only main...release/2.3 # Или короче: какие коммиты feature ещё не перенесены в main git cherry -v main feature/export Cherry-pick хорош для точечных переносов. Для регулярной синхронизации веток он неудобен: дублирующиеся коммиты запутывают историю, и лучше использовать merge или rebase. Interactive rebase: привести историю в порядок до ревью git rebase -i позволяет переписать историю своей ветки: переставить коммиты, объединить, разделить, переименовать или удалить. Цель — чтобы ревьюер и коллега через год видели осмысленные шаги, а не «fix», «fix again» и «wip». # Последние 5 коммитов git rebase -i HEAD~5 # Все коммиты ветки относительно main git rebase -i main В редакторе каждая строка начинается с команды: pick — оставить коммит; reword (r) — оставить, но изменить сообщение; edit (e) — остановиться на коммите для правки; squash (s) — объединить с предыдущим, сообщения склеить; fixup (f) — объединить с предыдущим, сообщение этого коммита отбросить; drop (d) — удалить коммит; exec (x) — выполнить команду оболочки, например прогнать тесты после каждого коммита. # Было pick 1a2b3c4 feat: add user authentication pick 5d6e7f8 wip: fix typo pick 9a0b1c2 wip: another fix pick 3d4e5f6 feat: add password reset # Стало pick 1a2b3c4 feat: add user authentication fixup 5d6e7f8 wip: fix typo fixup 9a0b1c2 wip: another fix pick 3d4e5f6 feat: add password reset Fixup-коммиты и autosquash Удобнее не разбирать мусор в конце, а сразу помечать исправления. Команда git commit --fixup создаёт коммит с сообщением «fixup! …», а rebase с autosquash сам поставит его на нужное место: # Исправление к конкретному коммиту git commit --fixup 1a2b3c4 # Перед отправкой на ревью git rebase -i --autosquash main Разделить один коммит на несколько # В списке rebase ставим edit на нужный коммит; Git останавливается на нём git reset HEAD^ # отменяем коммит, изменения остаются в рабочей копии git add src/auth/login.ts git commit -m "feat: add login endpoint" git add src/auth/register.ts git commit -m "feat: add registration endpoint" git rebase --continue Стек зависимых веток: --update-refs Если ветка B основана на ветке A, а вы переписали A, раньше приходилось вручную перебазировать B. С Git 2.38 флаг --update-refs (или настройка rebase.updateRefs = true ) переносит указатели всех веток, которые указывают на переписываемые коммиты. Правило безопасности: не переписывайте историю веток, на которых работают другие. После rebase уже отправленной ветки хеши меняются, и у коллег возникают конфликты. Если отправить переписанную ветку всё же нужно — только git push --force-with-lease --force-if-includes , а не голый --force : такой push откажет, если на сервере появились чужие коммиты, которых вы не видели. Worktree: несколько рабочих копий одного репозитория git worktree появился в Git 2.5 (2015) и до сих пор малоизвестен. Он позволяет держать несколько рабочих директорий, привязанных к одному репозиторию: объекты Git общие, повторно клонировать ничего не нужно. Сценарий: вы в середине большой фичи, прилетает срочный баг. Без worktree приходится прятать работу в stash, переключаться, потом возвращаться и надеяться, что stash применится без конфликтов. С worktree хотфикс делается в соседней папке, а основная копия остаётся нетронутой — вместе с запущенным dev-сервером и открытыми файлами. # Новая рабочая копия с новой веткой от main git worktree add -b hotfix/critical-bug ../project-hotfix main # Рабочая копия для существующей ветки git worktree add ../project-release release/2.3 # Посмотреть ветку коллеги для ревью (detached HEAD, ветка не создаётся) git fetch origin git worktree add --detach ../review-142 origin/feature/new-dashboard # Список и удаление git worktree list git worktree remove ../project-hotfix git worktree prune Где ещё пригождается: сравнить поведение двух версий приложения, запустив их на разных портах; оставить долгую сборку или прогон тестов в одной копии и продолжать работать в другой; параллельно вести несколько задач с AI-ассистентами, каждому — своя рабочая копия. Одна ветка не может быть одновременно выбрана в двух worktree — Git это запрещает, чтобы копии не мешали друг другу. Bisect: бинарный поиск коммита с регрессией git bisect находит коммит, после которого что-то сломалось. Вы указываете «хорошую» точку, где всё работало, и «плохую», где уже нет. Git каждый раз выбирает середину диапазона, и за log₂(N) шагов виновник найден: на тысячу коммитов — около десяти проверок. git bisect start git bisect bad # текущее состояние сломано git bisect good v2.1.0 # в этом релизе работало # Git переключился на середину диапазона. Проверяем и отмечаем: git bisect good # или git bisect bad # Коммит невозможно проверить (например, не собирается) git bisect skip # Завершить и вернуться туда, откуда начали git bisect reset Автоматический bisect Если баг воспроизводится тестом или скриптом, поиск проходит без участия человека. Код возврата 0 означает «хорошо», 1–127 — «плохо», а 125 — «пропустить коммит». git bisect start HEAD v2.1.0 git bisect run npm test -- -t "payment processing" # Или собственный скрипт git bisect run ./scripts/check-bug.sh Bisect работает тем лучше, чем аккуратнее история: если каждый коммит собирается и проходит тесты, результат указывает на небольшое осмысленное изменение. Это ещё один довод в пользу интерактивного rebase перед слиянием. Reflog: как вернуть «потерянные» коммиты Reflog — локальный журнал перемещений HEAD и веток. Даже если коммит больше не виден ни в одной ветке, запись о нём остаётся в журнале, пока не истечёт срок хранения. По умолчанию записи, достижимые из текущих веток, хранятся 90 дней ( gc.reflogExpire ), а недостижимые — то есть как раз «потерянные» после reset или удаления ветки — 30 дней ( gc.reflogExpireUnreachable ). Затем сборщик мусора может удалить и сами объекты. git reflog # 5e6f7a8 HEAD@{0}: reset: moving to HEAD~15 # a1b2c3d HEAD@{1}: commit: feat: complete dashboard redesign # 9b8c7d6 HEAD@{2}: commit: feat: dashboard filters # Создать ветку на потерянном коммите git branch feature/recovered a1b2c3d # Или вернуть текущую ветку в состояние до reset git reset --hard HEAD@{1} Важные ограничения: reflog локален — его нет на сервере и в свежем клоне; незакоммиченные изменения, стёртые reset --hard или restore , через reflog не вернуть. Поэтому правило простое: коммитьте чаще, а приводите историю в порядок потом, через rebase. Stash: больше, чем «спрятать и достать» # Сохранить с понятным описанием git stash push -m "wip: payment form validation" # Вместе с неотслеживаемыми файлами git stash push -u -m "wip: new migration" # Только выбранные фрагменты git stash push -p # Только конкретные файлы git stash push -m "config experiment" -- config/app.yaml git stash list git stash show -p stash@{1} # посмотреть diff git stash apply stash@{1} # применить, не удаляя из списка # Превратить stash в ветку git stash branch feature/extracted-work stash@{0} git stash branch создаёт ветку от коммита, на котором был сделан stash, применяет изменения и удаляет запись — конфликтов с тем, что изменилось с тех пор в текущей ветке, не будет. Если временная работа выросла в полноценную задачу, это самый чистый путь. Поиск по истории: log, pickaxe, blame История репозитория — документация, которую никто не пишет специально. Найти в ней ответ на «когда и зачем это поменяли» помогают несколько команд. # Граф истории всех веток git log --oneline --graph --all --decorate # Коммиты, где менялось число вхождений строки (pickaxe) git log -S "processPayment" --oneline --all # Коммиты, где в изменениях встречается регулярное выражение git log -G "api/v[0-9]+/payments" --oneline # История конкретной функции git log -L :processPayment:src/payments/processor.ts # Коммиты автора за период git log --author="Ivan Petrov" --since="2 weeks ago" --oneline # История файла, включая переименования git log --follow -p -- src/payments/processor.ts # Кто менял строки 42–60, игнорируя пробелы и перемещения кода git blame -w -C -L 42,60 src/payments/processor.ts Массовые правки форматирования портят blame: каждая строка «принадлежит» коммиту с Prettier. Решение — файл .git-blame-ignore-revs со списком таких коммитов и настройка blame.ignoreRevsFile . GitHub и GitLab учитывают этот файл в веб-интерфейсе. # Список изменений за спринт git log --since="2026-08-01" --until="2026-08-14" \ --pretty=format:"%h | %ad | %an | %s" --date=short # Что есть в feature, но нет в main git log --oneline main..feature/new-api # Статистика изменений между ветками git diff --stat main...feature/new-api # Сравнить две версии одной ветки до и после rebase git range-diff main feature@{1} feature git range-diff особенно полезен ревьюеру: после того как автор переписал ветку, видно, что именно изменилось между версиями, а не весь diff заново. Большие репозитории: sparse-checkout и частичный клон В монорепозитории на гигабайты не обязательно выкачивать и разворачивать всё. Частичный клон не загружает содержимое файлов, пока оно не понадобится, а sparse-checkout (отдельная команда с Git 2.25) оставляет в рабочей копии только нужные каталоги. # Клон без содержимого файлов и без разворачивания рабочей копии git clone --filter=blob:none --no-checkout https://git.example.ru/company/monorepo.git cd monorepo # Оставить в рабочей копии только нужные каталоги git sparse-checkout set apps/web packages/ui git switch main # Добавить каталог позже git sparse-checkout add packages/api-client # Вернуть полную рабочую копию git sparse-checkout disable Для крупных репозиториев стоит включить и фоновое обслужива