Перейти к основному содержимому
03ОТДЕЛЬНЫЕ ТЕМЫ · ГЛАВА 03Rebase мастерски

Rebase мастерски

Прочитано 0%Квизы 0/3Тренажёры 0/2Уровень 1 · Новичок

Зачем это нужно

Глава «Git: push, PR и командная работа» познакомила с rebase на минималках: он перекладывает твои коммиты поверх чужих, история становится прямой линией, а у переставленных коммитов меняются хеши. Этого достаточно, чтобы не пугаться слова в выводе git. Но rebase — инструмент с двойным дном: в одних руках он делает историю проекта читаемой, как оглавление книги, в других — устраивает команде разъехавшиеся ветки и потерянные коммиты. Разница между этими руками — ровно два знания: когда переписывать историю можно, а когда нельзя, и как пользоваться интерактивным режимом, который превращает пачку коммитов «поправил», «ещё поправил», «опечатка» в один осмысленный коммит.

Эта страница — углубление для тех, кто уже прошёл главы про ветки и про push: слова merge, конфликт, force и origin дальше используются без объяснений.

Вспомнить механику: rebase не двигает — он копирует

Короткое повторение, потому что на нём стоит всё остальное. git rebase main, выполненный на твоей ветке, берёт каждый твой коммит и создаёт его копию поверх свежего main: то же содержимое, то же сообщение, но другой родитель — а значит, другой хеш. Старые коммиты никуда не исчезают мгновенно: они становятся «ничьими» (на них больше не указывает ни одна ветка) и живут в репозитории ещё недели, пока их не приберёт сборщик мусора.

REBASE · КОПИРУЕТ, А НЕ ПЕРЕМЕЩАЕТбыло: mainтвоя веткаa1f9c3d · 4e21b77после git rebase mainкопии, новый хешa1f9c3d, 4e21b77 —ничьи, живут в reflogrebase создаёт копии твоих коммитов с другим родителем — старые не исчезают мгновенно
Rebase не двигает твои коммиты — создаёт их копии с другим родителем и новым хешем; старые становятся «ничьими», но не исчезают мгновенно.

Из этого факта выводится всё поведение rebase:

  • История после rebase — прямая линия без merge-коммитов: как будто ты начал работу позже, чем на самом деле.
  • Для git переставленные коммиты — другие коммиты. Сравнение веток, push, чужие копии — всё считает их новыми.
  • Пока «старая» версия коммитов ни к кому не уехала, замена проходит бесследно. Как только уехала — начинаются проблемы из следующего раздела.
MERGE ПРОТИВ REBASE · ФОРМА ИСТОРИИmergemerge-коммит, два родителяrebaseкопии с новым хешем — прямая линия, без развилки
Merge оставляет развилку и коммит слияния с двумя родителями; rebase распрямляет историю — копии твоих коммитов встают в конец.

Золотое правило: что можно и что нельзя

Официальная документация формулирует это как «Опасности перемещения», а в народе оно живёт как золотое правило 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 затирает молча — в командной работе он не нужен практически никогда.

Отвечено вопросов: 0 из 4
Почему после rebase у коммитов другие хеши?
Ты сделал rebase локальной ветки, которую вчера уже отправлял в свой PR (работаешь над ней один). Как правильно отправить результат?
Коллега тоже работает над веткой feature. Можно ли её перебазировать?
Чем --force-with-lease лучше голого --force?
Отвечено верно: 0 из 4

Интерактивный 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 не понадобится.

GIT REBASE -I · ПЛАНпланpick a1f9c3dpick 4e21b77pick 90cc102правишь словаменяешь словаpick a1f9c3dfixup 4e21b77pick 90cc102git выполняетисториядобавил парсеропечатка в READMEfixup вливает 4e21b77 в предыдущий коммит без своего сообщения — было три коммита, стало два
Меняешь слово действия у коммита — fixup вливает его в предыдущий без отдельного сообщения: было три коммита, стало два.

Если в середине плана что-то пошло не так, у rebase всегда есть два выхода: git rebase --continue — продолжить после того, как ты разрулил остановку, и git rebase --abort — отменить всё и вернуть ветку ровно в состояние до начала. Запомни второй: это кнопка «как было», работающая в любой момент.

Отвечено вопросов: 0 из 4
Чем fixup отличается от squash?
Что делает git rebase --abort?
Как выбросить коммит из истории через rebase -i?
Когда лучше всего наводить порядок в коммитах через rebase -i?
Отвечено верно: 0 из 4

Конфликты при 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) и merge-коммит (развилку)»: получи оба вида слияния — перемотку и merge-коммит
шаг 1 — fast-forward: слей ветку, пока main не двигался (без расхождения) шаг 2 — merge-коммит: вернись, создай расхождение (свой коммит и в main, и в ветке) и слей снова
Пустая папка проекта. Начни с git init. Полный список команд — help.
project $
Команды: git config · init · status · add · commit -m · diff · log · branch · switch · merge · restore · edit <файл> <текст> — изменить файл · help · clear
Файлы проекта
пока пусто — создай файл: edit README.md Привет
История коммитов
история пуста — сделай первый коммит
Как это устроено под капотом
Внутри — не настоящий git, а его модель в памяти: коммит — объект с хешем, сообщением, списком родителей и полным снимком файлов, а ветка — просто указатель на хеш коммита. Команды меняют этот граф объектов, при этом формулировки вывода сняты с настоящего git 2.x, чтобы глаз привыкал к реальному терминалу. Граф справа рисуется по тем же объектам: SVG-кружки — коммиты, линии — связи «родитель — потомок», а merge с расхождением создаёт коммит с двумя родителями, ровно как в настоящем git. Push и pull в режиме с origin — упрощённая пересылка недостающих коммитов между двумя такими репозиториями.

А команды самого 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
GIT REFLOG · ЖУРНАЛ ПЕРЕМЕЩЕНИЙ HEADновые записи сверху, старые снизуHEAD@{4}: rebase (start): checkout mainHEAD@{5}: checkout: moving from main to featureHEAD@{6}: commit: правка в main← сюдаgit reset --hard HEAD@{5}rebase (start) — уже первый шаг rebase; состояние ветки ДО него записано строкой ниже
Reflog читают сверху вниз, от новых записей к старым: строка rebase (start) — уже начало rebase, состояние ДО него записано строкой ниже.

reset --hard — команда без сети безопасности (она затирает незакоммиченные изменения), поэтому пользуйся ей осознанно и только по хешу, который ты глазами увидел в reflog. Но сам факт, что она существует, меняет отношение к rebase: это не хождение по канату, а редактор с кнопкой отмены.

Отвечено вопросов: 0 из 4
Чем разрешение конфликта при rebase отличается от разрешения при merge?
Что делает git pull --rebase?
После неудачного rebase кажется, что коммит пропал. Где его искать?
Почему git reset --hard требует осторожности?
Отвечено верно: 0 из 4

Шпаргалка

КомандаЧто делает
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 отцепляет ветку от старой базы и прищепляет к новой — вся терминология команды становится прозрачной, если помнить эту картинку.

Дальше по теме:

Что должен уметь

  • Объяснять, почему rebase создаёт новые коммиты и что происходит со старыми.
  • Применять золотое правило: по описанию ситуации отвечать, можно ли перебазировать, и почему.
  • Пересобирать свою историю через git rebase -i: squash, fixup, reword, drop, перестановка коммитов.
  • Разрешать конфликт в середине rebase и знать оба выхода: --continue и --abort.
  • Объяснять, чем git pull --rebase отличается от обычного pull и почему он не нарушает золотое правило.
  • Отправлять переписанную ветку через --force-with-lease и находить «потерянные» коммиты через reflog.

Куда дальше: проверенные ресурсы

Интересный факт

Команда git rebase --abort — редкий случай, когда самая важная команда инструмента ничего не делает с твоим кодом. Опытные пользователи git советуют выучить её раньше самого rebase: смелость экспериментировать берётся из знания, где выход.

Комментарии

Комментарии появятся после настройки. Нужен аккаунт GitHub — вход прямо в виджете выше.