Программирование и IT

git rebase

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

Обновлено
В этой статье

Что происходит при переносе#

Каждый коммит хранит ссылку на родителя. Ветка — это цепочка, которая растёт
от точки ветвления. Пока ты работал, основная ветка ушла вперёд, и твоя
цепочка висит на устаревшем родителе.

Перенос берёт твои коммиты по одному, вычисляет их изменения и накладывает эти
изменения поверх новой вершины. Результат выглядит так, будто ты начал
работать сегодня утром. Но это уже другие коммиты: у них другой родитель,
другое время применения и, как следствие, другие хеши. Старые объекты
остаются в базе до сборки мусора, и добраться до них можно через журнал ссылок.

Из этого следует правило, которое стоит запомнить раньше синтаксиса.
Переписывать историю ветки, которую кто-то уже забрал себе, нельзя. У
коллеги старые коммиты, у тебя новые, содержимое одинаковое — и git при
следующем слиянии покажет каждое изменение дважды. Для ветки, живущей только
на твоей машине, ограничение не действует.

Синтаксис и частые флаги#

git rebase <основа>            # перенести текущую ветку на <основу>
git rebase main feature        # переключиться на feature и перенести на main
git rebase -i HEAD~5           # интерактивно: правка пяти последних коммитов
git rebase --onto main old new # перенести только участок old..new
git rebase --continue          # продолжить после разрешения конфликта
git rebase --skip              # выбросить коммит, который нечего применять
git rebase --abort             # вернуть всё как было до начала

Полезно знать ещё два. Флаг --autostash сам прячет незакоммиченные правки
перед переносом и возвращает после. Флаг --rebase-merges сохраняет структуру
слияний внутри переносимого участка вместо того, чтобы их растворить.

Пример первый: ветка на свежую основу#

$ git switch feature-export
$ git fetch origin
$ git rebase origin/main
Successfully rebased and updated refs/heads/feature-export.

Три строки делают ровно одно: твои коммиты теперь лежат поверх того состояния
main, которое лежит на сервере прямо сейчас. Проверить результат удобно
сокращённым журналом — подробнее про его настройку —
в статье о git log:

$ git log --oneline -3
8f2c91a (HEAD -> feature-export) выгрузка в CSV
4b77de0 заголовки колонок
a10d33e (origin/main) обновление зависимостей

Первый вывод после переноса почти всегда пугает командой git status: она
сообщает, что ветка «разошлась» с её копией на сервере и там столько-то
коммитов у тебя, столько-то у них. Это ожидаемо — коммиты заменены новыми.

Пример второй: уборка перед отправкой#

Интерактивный режим открывает редактор со списком коммитов, самый старый
сверху:

$ git rebase -i HEAD~3
pick 1c0de11 черновик парсера
pick 7ab4409 опечатка
pick 3f19c02 тест на пустой файл

Слово слева — что делать с коммитом. pick — оставить как есть, reword
изменить сообщение, squash — приклеить к предыдущему с объединением
сообщений, fixup — то же, но сообщение выбросить, drop — удалить,
edit — остановиться на этом коммите и дать поправить содержимое. Строки
можно менять местами: порядок в файле станет порядком в истории.

Замени pick у второй строки на fixup, сохрани файл — и опечатка
растворится в черновике парсера. Вместо трёх коммитов останется два.

Перенос или слияние#

Обе команды соединяют работу двух веток, но отвечают на разные вопросы.

Слияние сохраняет факт: «вот две линии, вот момент, когда их объединили».
История ветвистая, ничего не переписывается, безопасно для любой ветки, включая
общую. Цена — коммиты слияния, среди которых тяжелее читать историю.

Перенос сохраняет удобство чтения: одна прямая линия, каждый коммит
самодостаточен, git bisect по такой истории работает предсказуемо. Цена —
переписанные хеши и обязательство никому не мешать.

Практический компромисс встречается чаще всего такой: свою ветку перед
отправкой чистят переносом, а в основную ветку вливают слиянием. Если во время
переноса всплыл конфликт, разрешается он так же, как при слиянии, —
последовательность разобрана в статье
как разрешать конфликт слияния.

Частые ошибки и выход из них#

Перенос начат не на той ветке. Пока процесс не закончен, спасает
git rebase --abort: рабочая копия и указатель ветки вернутся в исходное
состояние.

Перенос уже закончен, и результат не тот. Ветку возвращает журнал ссылок:

$ git reflog
8f2c91a HEAD@{0}: rebase (finish): returning to refs/heads/feature-export
4b77de0 HEAD@{1}: rebase (pick): выгрузка в CSV
c41ab8e HEAD@{2}: checkout: moving from main to feature-export
$ git reset --hard c41ab8e

Здесь c41ab8e — вершина ветки до начала переноса. Журнал хранит все
положения HEAD за последние недели, поэтому «потерянные» переносом коммиты на
самом деле никуда не делись. Про поведение reset и про то, чем опасен
--hard, — в статье о git reset.

Отправка на сервер отклонена. После переноса обычный git push откажется
работать, и соблазн дописать --force велик. Форсированная отправка стирает на
сервере всё, что ты не видел, — включая коммиты коллеги, залитые пять минут
назад. Безопасная замена:

git push --force-with-lease

Этот вариант отправляет изменения, только если вершина ветки на сервере та
самая, которую ты видел при последнем fetch. Появилось что-то новое —
отправка отклоняется, и ты сначала смотришь, что там появилось.

Конфликт повторяется в каждом коммите. Так бывает, когда переносится
десяток коммитов, правящих одно место. Включи git rerere (git config --global rerere.enabled true): git запомнит первое разрешение и применит его
к повторяющимся конфликтам сам.

План по этапам

  1. Убедиться, что ветка личнаяЕсли ветку кто-то забрал себе, переносить нельзя — только слияние.
  2. Обновить основуgit fetch origin, чтобы переносить на актуальное состояние, а не на вчерашнее.
  3. Запустить переносgit rebase origin/main из своей ветки; при конфликте — разрешить и git rebase --continue.
  4. Прибраться интерактивноgit rebase -i: fixup для мелких правок, reword для сообщений.
  5. Проверить историюgit log --oneline: порядок, сообщения, отсутствие лишних коммитов.
  6. Отправить без затирания чужогоgit push --force-with-lease; при отказе разобраться, что появилось на сервере.

Начать изучать эту тему у себя

План ляжет в твой репозиторий: отмечай этапы, веди конспект — история изменений покажет, как ты продвинулся.

Начать план

Проверь себя

1.Перенос пошёл не так, но ещё не закончен. Какая команда вернёт всё в исходное состояние?

2.Какая отправка на сервер не затрёт коммиты, появившиеся там после твоего последнего fetch?

3.Что делает слово fixup в списке интерактивного переноса?

Источники

  • Pro Git, русское изданиеГлава про ветвление и перенос — с разбором, когда переписывать историю нельзя
    бесплатно
  • Справка git rebaseВсе флаги и режимы первоисточником
    бесплатно
  • Справка git reflogЖурнал положений HEAD — способ вернуть ветку после неудачного переноса
    бесплатно

Было полезно?

Ещё темы

Программирование и IT git merge Слияние переносит работу из одной ветки в другую. Команда git merge берёт указанную ветку и соединяет её историю с текущей — иногда просто сдвигая указатель, иногда создавая отдельный коммит, а иногда останавливаясь и прося помощи. Программирование и IT Что такое API API — это условленный способ одной программы попросить что-то у другой. Никакого экрана и кнопок: запрос уходит текстом по сети, ответ приходит в машиночитаемом виде. Разобравшись с четырьмя частями HTTP-запроса, ты сможешь читать документацию любого сервиса. Программирование и IT Как разрешить конфликт слияния Конфликт — не поломка, а вопрос. Git сливает изменения автоматически, пока они в разных местах файла; когда две ветки правят одни и те же строки, выбрать правильный вариант может только человек. Задача — прочитать оба варианта, собрать из них рабочий код и сказать git, что вопрос закрыт. Программирование и IT git checkout Исторически `git checkout` делает сразу несколько несвязанных вещей: переводит рабочую копию на другую ветку, создаёт ветку, достаёт отдельный файл из коммита. Из-за этой перегруженности в git 2.23 появились две отдельные команды — `switch` и `restore`. Разберём обе стороны: старый способ и современную замену. Программирование и IT Что такое Docker Docker упаковывает программу вместе со всем, что ей нужно для запуска, в один образ — и этот образ одинаково стартует на ноутбуке разработчика и на сервере. Ниже — что такое образ, чем он отличается от контейнера, как написать первый Dockerfile и где эта технология лишняя. Программирование и IT Как создать репозиторий с нуля Репозиторий — это папка проекта плюс скрытая папка `.git`, в которой хранится вся история. Создаётся одной командой, но между ней и первым осмысленным коммитом есть несколько шагов, которые потом трудно переделать: имя ветки, список игнорируемых файлов, привязка к серверу.

Ещё сценарии