Rebase мастерски
Зачем это нужно
Глава «Git: push, PR и командная работа» познакомила с rebase на минималках: он перекладывает твои коммиты поверх чужих, история становится прямой линией, а у переставленных коммитов меняются хеши. Этого достаточно, чтобы не пугаться слова в выводе git. Но rebase — инструмент с двойным дном: в одних руках он делает историю проекта читаемой, как оглавление книги, в других — устраивает команде разъехавшиеся ветки и потерянные коммиты. Разница между этими руками — ровно два знания: когда переписывать историю можно, а когда нельзя, и как пользоваться интерактивным режимом, который превращает пачку коммитов «поправил», «ещё поправил», «опечатка» в один осмысленный коммит.
Эта страница — углубление для тех, кто уже прошёл главы про ветки и про push: слова merge, конфликт, force и origin дальше используются без объяснений.
- Зачем это нужно
- Вспомнить механику: rebase не двигает — он копирует
- Золотое правило: что можно и что нельзя
- Интерактивный rebase: навести порядок перед PR
- Конфликты при rebase и git pull --rebase
- Как это выглядит вживую
- Практика: отрепетируй в симуляторе
- Страховка: git reflog
- Шпаргалка
- Что должен уметь
- Куда дальше: проверенные ресурсы
Вспомнить механику: rebase не двигает — он копирует
Короткое повторение, потому что на нём стоит всё остальное. git rebase main, выполненный на твоей ветке, берёт каждый твой коммит и создаёт его копию поверх свежего main: то же содержимое, то же сообщение, но другой родитель — а значит, другой хеш. Старые коммиты никуда не исчезают мгновенно: они становятся «ничьими» (на них больше не указывает ни одна ветка) и живут в репозитории ещё недели, пока их не приберёт сборщик мусора.
Из этого факта выводится всё поведение rebase:
- История после rebase — прямая линия без merge-коммитов: как будто ты начал работу позже, чем на самом деле.
- Для git переставленные коммиты — другие коммиты. Сравнение веток, push, чужие копии — всё считает их новыми.
- Пока «старая» версия коммитов ни к кому не уехала, замена проходит бесследно. Как только уехала — начинаются проблемы из следующего раздела.
Золотое правило: что можно и что нельзя
Официальная документация формулирует это как «Опасности перемещения», а в народе оно живёт как золотое правило rebase: не перебазируй коммиты, которые уже отправлены туда, где их могли забрать другие. Разложим по типичным ситуациям:
| Ситуация | Rebase? | Почему |
|---|---|---|
| Локальная ветка, ещё не было push | ✅ можно свободно | копии заменяют оригиналы, о которых никто не знал |
| Своя ветка в PR, работаешь над ней один | ⚠️ можно, но push потом только --force-with-lease | сервер уже видел старые хеши; force переписывает их, а --force-with-lease страхует от затирания чужого |
| Ветка, над которой работает кто-то ещё | ❌ нельзя | у коллеги останутся старые хеши — при следующем pull его git попытается «слить» историю саму с собой |
main / общая ветка команды | ❌ никогда | это история всей команды; переписал — сломал всем сразу |
Что именно ломается в запрещённых случаях: у коллеги в репозитории остаются старые коммиты, у тебя на сервере — новые копии с другими хешами. Git обеих сторон честно видит «две разные истории с одинаковым содержимым» и предлагает их слить — появляются дубли коммитов, странные merge-коммиты «ветка слита сама с собой» и совершенно нечитаемый лог. Чинится это неприятно и руками, поэтому правило и золотое.
И парное правило про отправку: после законного rebase своей ветки обычный git push получит rejected — сервер видит, что твоя история «не продолжает» его версию. Здесь и нужен git push --force-with-lease: он переписывает ветку на сервере, но откажется это делать, если туда кто-то успел запушить, пока ты перебазировался. Голый --force затирает молча — в командной работе он не нужен практически никогда.
Интерактивный rebase: навести порядок перед PR
Второе применение rebase не связано с чужими ветками вообще. git rebase -i (interactive) — это редактор собственной истории: перед тем как показать ветку людям, ты пересобираешь свои черновые коммиты в аккуратные.
Команда открывает в редакторе план — список коммитов от старого к новому, каждый со словом-действием:
pick a1f9c3d добавил парсер конфига
pick 4e21b77 поправил парсер
pick 90cc102 опечатка в README
Меняешь слова — git выполняет план сверху вниз. Основные действия:
pick— оставить коммит как есть (значение по умолчанию).reword— оставить, но остановиться и дать переписать сообщение.squash— влить в предыдущий коммит, объединив сообщения (git даст их отредактировать).fixup— влить в предыдущий, а сообщение выбросить: идеально для коммитов «поправил» и «опечатка».drop— выбросить коммит совсем (то же самое — просто удалить строку).- Переставил строки местами — коммиты поменяют порядок.
План выше типично превращается в:
pick a1f9c3d добавил парсер конфига
fixup 4e21b77 поправил парсер
pick 90cc102 опечатка в README
— и вместо трёх коммитов в истории остаются два осмысленных. Правило хорошего тона: наводить такой порядок до отправки ветки в PR — тогда и force не понадобится.
Если в середине плана что-то пошло не так, у rebase всегда есть два выхода: git rebase --continue — продолжить после того, как ты разрулил остановку, и git rebase --abort — отменить всё и вернуть ветку ровно в состояние до начала. Запомни второй: это кнопка «как было», работающая в любой момент.
Конфликты при rebase и git pull --rebase
Конфликты в rebase случаются по той же причине, что и в merge, но обрабатываются иначе: rebase накладывает твои коммиты по одному, и останавливается на том, который не лёг. Дальше знакомый цикл: открыть файлы с маркерами <<<<<<<, оставить правильный вариант, git add — но вместо commit сказать git rebase --continue. Если конфликтов несколько, цикл повторится для каждого коммита. Стало страшно — git rebase --abort, и ты снова до начала.
Отдельный родственник — git pull --rebase. Обычный git pull при разъехавшихся историях делает merge-коммит «Merge branch...», который в логе только шумит. С флагом --rebase git вместо этого переложит твои локальные коммиты поверх свежепришедших — история останется прямой. Это самый безопасный повседневный rebase: перекладываются только те коммиты, которых на сервере ещё нет, так что золотое правило не нарушается по построению.
Как это выглядит вживую
Три сообщения, которые rebase печатает чаще всего, — узнавай их в лицо, чтобы не гадать, на каком ты этапе:
Successfully rebased and updated refs/heads/feature.
Всё прошло без остановок: коммиты переложены, ветка обновлена. Если перед этим был -i — план выполнен целиком.
CONFLICT (content): Merge conflict in src/config.py
error: could not apply 4e21b77... поправил парсер
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Остановка на конфликтующем коммите — git сам перечисляет три выхода: разрешить и --continue, пропустить коммит через --skip (нужен редко), отменить всё через --abort. Хеш и сообщение в строке error говорят, какой именно из твоих коммитов не лёг.
interactive rebase in progress; onto 3b7e0aa
Last command done (1 command done):
pick a1f9c3d # добавил парсер конфига
Next commands to do (2 remaining commands):
reword 4e21b77 # поправил парсер
pick 90cc102 # опечатка в README
(use "git rebase --edit-todo" to view and edit)
Так git status отчитывается посреди интерактивного rebase: что уже сделано, что впереди. Потерялся — git status всегда расскажет, где ты и что git от тебя ждёт.
Одна деталь, которая сбивает с толку: хеш после onto (3b7e0aa) — не из плана. Это база, тот самый коммит, поверх которого всё перекладывается: для git rebase -i HEAD~3 это коммит, стоявший перед тремя пересобираемыми. А решётка перед сообщением коммита — просто разделитель во внутреннем файле плана, который git показывает как есть; в редакторе плана её не видно.
Практика: отрепетируй в симуляторе
В учебном GitSim команды rebase нет — он остаётся для настоящего терминала (и раздела «Углубиться»). Зато в нём отлично репетируется то, что rebase производит: прямая история против ветвистой. Задание в свободном режиме: создай ветку, сделай в ней коммит и получи оба вида слияния — сначала fast-forward (ровно так выглядит история после rebase: прямая линия), потом настоящий merge-коммит с развилкой (то, что было бы без rebase). Сравни граф в обоих случаях — это и есть выбор «rebase или merge» глазами.
Как это устроено под капотом
А команды самого rebase набери руками — пальцы должны помнить и --continue, и особенно --abort:
Цель: точность ≥ 90%
git rebase main
Страховка: git reflog
Последний страх, который стоит снять: «а вдруг rebase потеряет мои коммиты». Почти никогда не потеряет — а на «почти» есть git reflog: журнал всех перемещений HEAD за последние недели. Старые, «ничьи» коммиты после rebase остаются в репозитории, и reflog показывает их хеши. Вернуться к состоянию до неудачного rebase можно всегда:
git reset --hard ORIG_HEAD # самый короткий путь: git сам запомнил, где ветка была до rebase
ORIG_HEAD — служебная ссылка, которую git ставит перед каждой «крупной» операцией вроде rebase или merge. Если она уже затёрта следующей операцией, состояние ищут глазами в журнале:
git reflog # найти строку "rebase (start)" — нужна строка СРАЗУ ПОД ней
git reset --hard HEAD@{5} # вернуть ветку на состояние из журнала
Читать журнал нужно аккуратно: reflog печатает записи от самых новых к старым, сверху вниз. Строка rebase (start) — это уже первый шаг самого rebase, и хеш на ней — новая база, а не то, что тебе нужно. Состояние ветки до rebase записано строкой ниже:
2673310 HEAD@{4}: rebase (start): checkout main
b2593cf HEAD@{5}: checkout: moving from main to feature ← сюда возвращаться
2673310 HEAD@{6}: commit: правка в main
reset --hard — команда без сети безопасности (она затирает незакоммиченные изменения), поэтому пользуйся ей осознанно и только по хешу, который ты глазами увидел в reflog. Но сам факт, что она существует, меняет отношение к rebase: это не хождение по канату, а редактор с кнопкой отмены.
Шпаргалка
| Команда | Что делает |
|---|---|
git rebase main | переложить коммиты текущей ветки поверх main (копии, новые хеши) |
git rebase -i HEAD~3 | интерактивно пересобрать три последних коммита |
pick / reword / squash / fixup / drop | оставить / переименовать / влить с сообщением / влить без / выбросить |
git rebase --continue | продолжить после разрешённого конфликта или reword |
git rebase --abort | отменить rebase целиком, вернуть как было |
git pull --rebase | забрать чужое, переложив свои неотправленные коммиты наверх |
git push --force-with-lease | отправить переписанную ветку со страховкой от затирания чужого |
git reflog | журнал перемещений HEAD — найти «потерянный» коммит |
git reset --hard ORIG_HEAD | вернуть ветку туда, где она была до rebase (или merge) |
| Золотое правило | не перебазировать то, что уже есть у других (и никогда — общие ветки) |
Углубиться
--onto: пересадка ветки целиком. Полная форма git rebase --onto новая-база старая-база ветка умеет пересаживать диапазон коммитов куда угодно — например, ветку, случайно начатую от чужой фичи, переставить на main. Читается тяжело, используется редко, но когда нужна — незаменима; в официальной документации ей посвящён отдельный раздел с картинками.
--autosquash: fixup без ручной правки плана. Если коммит-поправку сразу создавать командой git commit --fixup a1f9c3d, git запишет его с сообщением fixup! ..., а git rebase -i --autosquash сам расставит такие коммиты в плане под их целями со словом fixup. Конвейер для аккуратной истории без единого редактирования плана.
Merge или rebase — вечный спор. Одни команды требуют прямую историю (rebase перед вливанием, squash-merge в кнопке PR), другие принципиально сохраняют merge-коммиты как честную летопись «что когда сливалось». Правильного ответа нет — есть соглашение конкретной команды, и первое, что стоит спросить в новом проекте: «как у вас принято вливать ветки?».
Почему «база»? Слово rebase буквально значит «сменить базу»: базой (base) называется коммит, от которого ветка растёт. rebase отцепляет ветку от старой базы и прищепляет к новой — вся терминология команды становится прозрачной, если помнить эту картинку.
Дальше по теме:
- git-rebase — официальная документация — все режимы и флаги, включая --onto и --autosquash
- Pro Git: Перебазирование — та же тема по-русски, с разделом «Опасности перемещения», из которого выросло золотое правило
Что должен уметь
- Объяснять, почему rebase создаёт новые коммиты и что происходит со старыми.
- Применять золотое правило: по описанию ситуации отвечать, можно ли перебазировать, и почему.
- Пересобирать свою историю через
git rebase -i: squash, fixup, reword, drop, перестановка коммитов. - Разрешать конфликт в середине rebase и знать оба выхода:
--continueи--abort. - Объяснять, чем
git pull --rebaseотличается от обычного pull и почему он не нарушает золотое правило. - Отправлять переписанную ветку через
--force-with-leaseи находить «потерянные» коммиты через reflog.
Куда дальше: проверенные ресурсы
-
git-rebase — официальная документация — первоисточник: точное описание каждого действия плана и каждого флага.
-
Pro Git: Перебазирование — глава официальной книги по-русски: базовый и продвинутый rebase с картинками графов.
-
git-reflog — официальная документация — как устроен журнал ссылок и синтаксис HEAD@{n}.
-
GitHub Docs: О слиянии pull request — как кнопка Merge связана с rebase и squash: те же идеи на стороне сервера.
-
Видео и материалы сообщества — ниже — ролики по теме и ссылки от студентов и преподавателей смотри в самом низу страницы.
Команда git rebase --abort — редкий случай, когда самая важная команда инструмента ничего не делает с твоим кодом. Опытные пользователи git советуют выучить её раньше самого rebase: смелость экспериментировать берётся из знания, где выход.
Комментарии
Комментарии появятся после настройки. Нужен аккаунт GitHub — вход прямо в виджете выше.