Программирование и IT
git rebase
Команда переносит коммиты твоей ветки так, будто ты начал работу не от старого состояния, а от текущего. История становится линейной, но коммиты при этом создаются заново — с новыми хешами. Отсюда главное ограничение: переносить можно только то, чем не пользуется никто другой.
В этой статье
Что происходит при переносе#
Каждый коммит хранит ссылку на родителя. Ветка — это цепочка, которая растёт
от точки ветвления. Пока ты работал, основная ветка ушла вперёд, и твоя
цепочка висит на устаревшем родителе.
Перенос берёт твои коммиты по одному, вычисляет их изменения и накладывает эти
изменения поверх новой вершины. Результат выглядит так, будто ты начал
работать сегодня утром. Но это уже другие коммиты: у них другой родитель,
другое время применения и, как следствие, другие хеши. Старые объекты
остаются в базе до сборки мусора, и добраться до них можно через журнал ссылок.
Из этого следует правило, которое стоит запомнить раньше синтаксиса.
Переписывать историю ветки, которую кто-то уже забрал себе, нельзя. У
коллеги старые коммиты, у тебя новые, содержимое одинаковое — и git при
следующем слиянии покажет каждое изменение дважды. Для ветки, живущей только
на твоей машине, ограничение не действует.
Синтаксис и частые флаги#
Полезно знать ещё два. Флаг --autostash сам прячет незакоммиченные правки
перед переносом и возвращает после. Флаг --rebase-merges сохраняет структуру
слияний внутри переносимого участка вместо того, чтобы их растворить.
Пример первый: ветка на свежую основу#
Три строки делают ровно одно: твои коммиты теперь лежат поверх того состояния
main, которое лежит на сервере прямо сейчас. Проверить результат удобно
сокращённым журналом — подробнее про его настройку —
в статье о git log:
Первый вывод после переноса почти всегда пугает командой git status: она
сообщает, что ветка «разошлась» с её копией на сервере и там столько-то
коммитов у тебя, столько-то у них. Это ожидаемо — коммиты заменены новыми.
Пример второй: уборка перед отправкой#
Интерактивный режим открывает редактор со списком коммитов, самый старый
сверху:
Слово слева — что делать с коммитом. pick — оставить как есть, reword —
изменить сообщение, squash — приклеить к предыдущему с объединением
сообщений, fixup — то же, но сообщение выбросить, drop — удалить,
edit — остановиться на этом коммите и дать поправить содержимое. Строки
можно менять местами: порядок в файле станет порядком в истории.
Замени pick у второй строки на fixup, сохрани файл — и опечатка
растворится в черновике парсера. Вместо трёх коммитов останется два.
Перенос или слияние#
Обе команды соединяют работу двух веток, но отвечают на разные вопросы.
Слияние сохраняет факт: «вот две линии, вот момент, когда их объединили».
История ветвистая, ничего не переписывается, безопасно для любой ветки, включая
общую. Цена — коммиты слияния, среди которых тяжелее читать историю.
Перенос сохраняет удобство чтения: одна прямая линия, каждый коммит
самодостаточен, git bisect по такой истории работает предсказуемо. Цена —
переписанные хеши и обязательство никому не мешать.
Практический компромисс встречается чаще всего такой: свою ветку перед
отправкой чистят переносом, а в основную ветку вливают слиянием. Если во время
переноса всплыл конфликт, разрешается он так же, как при слиянии, —
последовательность разобрана в статье
как разрешать конфликт слияния.
Частые ошибки и выход из них#
Перенос начат не на той ветке. Пока процесс не закончен, спасает
git rebase --abort: рабочая копия и указатель ветки вернутся в исходное
состояние.
Перенос уже закончен, и результат не тот. Ветку возвращает журнал ссылок:
Здесь c41ab8e — вершина ветки до начала переноса. Журнал хранит все
положения HEAD за последние недели, поэтому «потерянные» переносом коммиты на
самом деле никуда не делись. Про поведение reset и про то, чем опасен
--hard, — в статье о git reset.
Отправка на сервер отклонена. После переноса обычный git push откажется
работать, и соблазн дописать --force велик. Форсированная отправка стирает на
сервере всё, что ты не видел, — включая коммиты коллеги, залитые пять минут
назад. Безопасная замена:
Этот вариант отправляет изменения, только если вершина ветки на сервере та
самая, которую ты видел при последнем fetch. Появилось что-то новое —
отправка отклоняется, и ты сначала смотришь, что там появилось.
Конфликт повторяется в каждом коммите. Так бывает, когда переносится
десяток коммитов, правящих одно место. Включи git rerere (git config --global rerere.enabled true): git запомнит первое разрешение и применит его
к повторяющимся конфликтам сам.
План по этапам
- Убедиться, что ветка личнаяЕсли ветку кто-то забрал себе, переносить нельзя — только слияние.
- Обновить основуgit fetch origin, чтобы переносить на актуальное состояние, а не на вчерашнее.
- Запустить переносgit rebase origin/main из своей ветки; при конфликте — разрешить и git rebase --continue.
- Прибраться интерактивноgit rebase -i: fixup для мелких правок, reword для сообщений.
- Проверить историюgit log --oneline: порядок, сообщения, отсутствие лишних коммитов.
- Отправить без затирания чужогоgit push --force-with-lease; при отказе разобраться, что появилось на сервере.
Начать изучать эту тему у себя
План ляжет в твой репозиторий: отмечай этапы, веди конспект — история изменений покажет, как ты продвинулся.
Проверь себя
1.Перенос пошёл не так, но ещё не закончен. Какая команда вернёт всё в исходное состояние?
2.Какая отправка на сервер не затрёт коммиты, появившиеся там после твоего последнего fetch?
3.Что делает слово fixup в списке интерактивного переноса?
Источники
-
Pro Git, русское изданиеГлава про ветвление и перенос — с разбором, когда переписывать историю нельзябесплатно
-
Справка git rebaseВсе флаги и режимы первоисточникомбесплатно
-
Справка git reflogЖурнал положений HEAD — способ вернуть ветку после неудачного переносабесплатно
Было полезно?