Олег Марков
Интерактивное перебазирование Git rebase -i - подробное практическое руководство
Введение
Интерактивное перебазирование в Git — это мощный инструмент для переписывания истории коммитов. Команда git rebase -i позволяет вам изменить порядок коммитов, объединить несколько коммитов в один, разбить один коммит на несколько, отредактировать сообщения и даже удалить ненужные изменения.
Смотрите, здесь важно понимать одну вещь. Обычное git rebase просто переносит коммиты на новую базу, а интерактивное git rebase -i дает вам точный контроль над каждым коммитом. Вы буквально по шагам говорите Git, что сделать с каждым изменением.
В этой статье я покажу вам, как использовать интерактивное перебазирование осознанно и безопасно. Мы разберем ключевые сценарии:
- Как аккуратно «почистить» историю перед merge в основную ветку
- Как исправить неудачное сообщение к коммиту
- Как объединить несколько мелких коммитов в один логичный
- Как удалить «мусорные» коммиты
- Как решить конфликты во время интерактивного rebase
И параллельно вы увидите примеры команд и типичные рабочие сценарии, которые часто встречаются в реальной разработке.
Что такое интерактивное перебазирование
Кратко о rebase и том, что делает флаг -i
Обычное перебазирование git rebase переносит последовательность коммитов на новую «базу» — другой коммит. Проще говоря, вы как будто «написали» историю заново, начиная с другого места.
Интерактивное перебазирование git rebase -i делает то же самое, но с дополнительным шагом: оно открывает специальный список коммитов в редакторе, где вы можете указать, что делать с каждым из них.
Когда вы запускаете:
git rebase -i HEAD~5
# Здесь мы запускаем интерактивное перебазирование
# для последних 5 коммитов относительно текущей HEAD
Git собирает последние пять коммитов, строит из них список и предлагает вам «сценарий» действий: оставить как есть, объединить, отредактировать, удалить и т.д.
В каких случаях использовать git rebase -i
Давайте посмотрим, когда это действительно полезно.
Интерактивное перебазирование удобно, когда вы:
- Работаете в отдельной feature-ветке и хотите привести историю в аккуратный вид перед тем, как отправить код на ревью
- Случайно сделали несколько «сыроватых» коммитов и хотите объединить их в один осмысленный
- Забыли что-то добавить в коммит и хотите «как будто бы» сразу сделать его правильным
- Залили в историю лишний или экспериментальный коммит и теперь хотите его убрать
- Хотите изменить порядок коммитов так, чтобы в истории они шли более логично
Важно помнить про одно ограничение. Интерактивный rebase почти всегда переписывает историю. Это значит, что для публичных веток, которые уже используют другие разработчики, нужно действовать особенно осторожно. Лучше всего применять git rebase -i в ваших собственных ветках до того, как вы сделали git push в общий репозиторий (или хотя бы до того, как кто-то начал на нее опираться).
Базовый сценарий работы с git rebase -i
Общий принцип работы
Смотрите, принцип всегда один и тот же:
- Вы запускаете
git rebase -iс указанием диапазона коммитов. - Git открывает редактор с планом действий по каждому коммиту.
- Вы редактируете этот файл-план: меняете команды, порядок, объединяете строки.
- Сохраняете и закрываете редактор.
- Git последовательно «проигрывает» ваши указания, создавая новые коммиты на новой базе.
Важно понять, что Git в процессе создает новые коммиты, а старые как бы оставляет в стороне. Поэтому хеши коммитов меняются.
Как выбрать диапазон коммитов
Чаще всего используют одну из двух схем:
- Указать количество последних коммитов:
git rebase -i HEAD~N
# N — количество последних коммитов, которые вы хотите переписать
Например, если вы хотите «поработать» с последними тремя коммитами:
git rebase -i HEAD~3
# Здесь вы изменяете коммиты:
# HEAD — самый последний
# HEAD~1 — предыдущий
# HEAD~2 — еще один назад
- Указать базовый коммит, от которого хотите перебазировать:
git rebase -i <base-commit>
# Здесь будут взяты все коммиты после base-commit до текущего HEAD
Например:
git rebase -i main
# Здесь Git возьмет все коммиты, которые есть в вашей текущей ветке,
# но отсутствуют в ветке main
Это очень удобно для feature-веток: вы как бы говорите «возьми все мои коммиты поверх последнего состояния main, но дай мне возможность их почистить».
Как выглядит интерактивный список коммитов
После запуска команды Git откроет ваш редактор (обычно Vim, Nano или тот, который настроен в Git) с таким примерно содержанием:
pick a1b2c3d Добавлен API-клиент
pick e4f5g6h Исправлена авторизация
pick 123abcd Логирование запросов
# Rebase 987zyx on 654wvu (3 commands)
#
# Commands:
# p, pick = использовать коммит как есть
# r, reword = использовать коммит, но изменить сообщение
# e, edit = приостановиться на этом коммите для его редактирования
# s, squash = объединить с предыдущим коммитом, объединяя сообщения
# f, fixup = объединить с предыдущим коммитом, отбросив сообщение
# d, drop = удалить коммит
Смотрите, Git прямо подсказывает вам доступные команды. Вы редактируете только верхнюю часть — строки, начинающиеся с pick. Комментарии ниже нужны для справки, их можно не трогать.
Команды интерактивного rebase и что они делают
Теперь давайте разберем основные команды, которые вы будете использовать в файле интерактивного rebase.
Команда pick — оставить коммит как есть
pick — команда по умолчанию. Она говорит Git: «применить этот коммит как есть».
Строка выглядет так:
pick a1b2c3d Добавлен API-клиент
# Команда pick означает что коммит будет перенесен без изменений
Если вы не хотите ничего менять в каком-то коммите, просто оставляйте строку pick как есть.
Команда reword — изменить только сообщение коммита
Команда reword полезна, когда содержимое коммита вас устраивает, но сообщение получилось неудачным.
Пример:
reword a1b2c3d Добавлен API-клиент
pick e4f5g6h Исправлена авторизация
После того как вы сохраните файл и закроете редактор, Git:
- Дойдет до коммита
a1b2c3d - Остановится и откроет второй редактор — уже с текущим сообщением коммита
- Даст вам возможность переписать текст сообщения
Здесь вы можете, например, поправить стиль, формат, добавить номер задачи, сделать сообщение более осмысленным.
Команда edit — изменить содержимое коммита
edit — более мощная команда. Она приостанавливает процесс rebase на выбранном коммите и позволяет вам изменить не только сообщение, но и содержимое изменений.
Посмотрите на пример:
pick a1b2c3d Добавлен API-клиент
edit e4f5g6h Исправлена авторизация
pick 123abcd Логирование запросов
После запуска rebase:
- Git применит первый коммит.
- Дойдет до коммита
e4f5g6hи остановится, показав что-то вроде:
Stopped at e4f5g6h... Исправлена авторизация
You can amend the commit, then run
git rebase --continue
Теперь у вас есть возможность:
# Внести правки в код
# Например, вы заметили, что забыли один важный if в авторизации
# Проверить статус
git status
# Команда покажет какие файлы изменены
# Добавить изменения в индекс
git add path/to/file.go
# Здесь вы добавляете файлы которые хотите включить в переписанный коммит
# Переписать коммит
git commit --amend
# Здесь вы можете при необходимости изменить и сообщение и содержимое
# Продолжить перебазирование
git rebase --continue
# Git пойдет дальше по списку коммитов
Таким образом вы как бы «отматываете» историю до нужного коммита, меняете его и продолжаете проигрывание остальных.
Команда squash — объединить коммит с предыдущим, объединив сообщения
swap коммитов и их объединение — один из самых частых сценариев интерактивного rebase. Представьте, что вы сделали два последовательных коммита:
- «Реализован API-клиент»
- «Исправления по API-клиенту»
Часто логично объединить их в один осмысленный коммит.
Используем squash:
pick a1b2c3d Реализован API-клиент
squash e4f5g6h Исправления по API-клиенту
Смотрите, здесь squash (или коротко s) говорит Git: «объедини этот коммит с предыдущим, но дай мне возможность объединить и сообщения».
После сохранения Git:
- Применит первый коммит как есть.
- Применит второй коммит, объединив его изменения с первым.
- Откроет редактор с объединенным сообщением, примерно таким:
# Это объединенное сообщение коммита
Реализован API-клиент
# Это сообщение второго коммита
Исправления по API-клиенту
Вы можете отредактировать этот текст и оставить, например:
Реализован и доработан API-клиент
Команда fixup — объединить коммит с предыдущим, отбросив сообщение
fixup похож на squash, но без лишних вопросов. Он объединяет изменения с предыдущим коммитом, вообще не спрашивая о сообщениях. Сообщение второго коммита просто игнорируется.
Пример:
pick a1b2c3d Реализован API-клиент
fixup e4f5g6h Исправления по API-клиенту
В итоге вы получите один коммит с сообщением «Реализован API-клиент», который включает и первоначальную реализацию, и правки. Это удобно, когда второй коммит явно «доводка» первого и не заслуживает отдельного упоминания в истории.
Команда drop — удалить коммит
Команда drop просто удаляет коммит из истории. Здесь нужно быть особенно осторожным, потому что с точки зрения Git это как будто изменений вообще не было.
Пример:
pick a1b2c3d Реализован API-клиент
drop e4f5g6h Временный отладочный лог
pick 123abcd Логирование запросов
После rebase:
- Коммита
e4f5g6hбольше не будет в вашей новой истории. - Все его изменения исчезнут, если только они не перекрыты более поздними коммитами.
drop полезен, если вы случайно закоммитили отладочный код, временные эксперименты или файлы, которые не должны были попасть в репозиторий.
Перестановка и группировка коммитов
Как менять порядок коммитов
Изменение порядка — одна из самых прямолинейных возможностей git rebase -i.
Смотрите, вы можете просто менять строки в интерактивном списке:
pick a1b2c3d A - Добавлен API-клиент
pick e4f5g6h B - Логирование запросов
pick 123abcd C - Правки авторизации
Допустим, вы хотите, чтобы коммит C шел раньше B:
pick a1b2c3d A - Добавлен API-клиент
pick 123abcd C - Правки авторизации
pick e4f5g6h B - Логирование запросов
Сохраняете файл — и Git проигрывает коммиты в новом порядке. Иногда это может привести к конфликтам, если один коммит зависит от изменений другого. Тогда Git остановится и предложит вам решить конфликт вручную.
Объединение нескольких коммитов в один «логичный» блок
Частый сценарий — вы делали задачу по шагам, и у вас накопилось 5–7 коммитов вида:
- «правки»
- «еще правки»
- «починил тесты»
- «мелкие фиксы»
Перед ревью или перед merge в main вы хотите превратить это в один чистый коммит «Реализована фича X».
Давайте разберемся на примере:
pick a1b2c3d Реализован API-клиент
pick e4f5g6h Правки по API-клиенту
pick 123abcd Фикс тестов API-клиента
pick 987zyxw Очистка кода API-клиента
Вы можете сделать так:
pick a1b2c3d Реализован API-клиент
fixup e4f5g6h Правки по API-клиенту
fixup 123abcd Фикс тестов API-клиента
fixup 987zyxw Очистка кода API-клиента
В результате вы получите один коммит «Реализован API-клиент», который включает в себя все доводки. История станет короче и понятнее.
Если вы хотите контролировать итоговое сообщение, вместо fixup используйте squash для одного из коммитов, например:
pick a1b2c3d Реализован API-клиент
squash e4f5g6h Правки по API-клиенту
fixup 123abcd Фикс тестов API-клиента
fixup 987zyxw Очистка кода API-клиента
Тогда Git спросит вас, какое финальное сообщение вы хотите оставить, а остальные коммиты просто «вклеит» без своих сообщений.
Исправление существующих коммитов
Исправление самого последнего коммита без rebase
Для самого свежего коммита зачастую не нужен даже интерактивный rebase. Можно обойтись командой git commit --amend.
Пример:
# Вы забыли добавить файл в последний коммит
git add forgotten_file.go
# Добавляем файл в индекс
git commit --amend
# Переписываем последний коммит добавляя в него новый файл
# Здесь можно при желании изменить и сообщение
Это удобно, если вы еще не отправляли этот коммит в общий репозиторий.
Исправление любого более старого коммита через edit
Если нужно исправить не последний, а, скажем, третий коммит от HEAD, используем git rebase -i и команду edit.
Допустим, история:
- Коммит 3 — «Добавлен кэш»
- Коммит 2 — «Оптимизация запросов»
- Коммит 1 — «Начальная реализация»
Вы хотите поправить коммит «Оптимизация запросов».
- Запускаем интерактивный rebase:
git rebase -i HEAD~3
# Берем последние три коммита
- В появившемся списке меняем:
pick a1b2c3d Начальная реализация
pick e4f5g6h Оптимизация запросов
pick 123abcd Добавлен кэш
на:
pick a1b2c3d Начальная реализация
edit e4f5g6h Оптимизация запросов
pick 123abcd Добавлен кэш
- Сохраняем файл. Git остановится на нужном коммите. Далее:
# Вносим изменения в файлы
# Например, упрощаем один из SQL-запросов
git add path/to/file.sql
# Добавляем измененный файл в индекс
git commit --amend
# Переписываем текущий коммит включая новые изменения
git rebase --continue
# Продолжаем проигрывать оставшиеся коммиты
Таким образом вы аккуратно «чините» старый коммит, а история выглядит так, как будто он всегда был правильным.
Удаление и разделение коммитов
Удаление ненужного коммита
Если вы хотите убрать целый коммит из истории, есть два варианта:
- Просто удалить строку с коммитом из интерактивного списка.
- Явно указать
drop <hash>(или короткоd).
Оба варианта эквивалентны. Давайте посмотрим:
pick a1b2c3d Добавлен API-клиент
pick e4f5g6h Временный эксперимент
pick 123abcd Логирование
Можно сделать так:
pick a1b2c3d Добавлен API-клиент
drop e4f5g6h Временный эксперимент
pick 123abcd Логирование
Или просто удалить строку:
pick a1b2c3d Добавлен API-клиент
pick 123abcd Логирование
В обоих случаях «Временный эксперимент» исчезнет. Обратите внимание: если более поздние коммиты зависят от изменений из удаленного коммита, могут возникнуть конфликты. Тогда Git попросит вас вручную привести код к рабочему виду.
Разделение одного коммита на несколько
Git не дает прямой команды «split» в интерактивном rebase, но мы можем сделать это через edit.
Смотрите, шаги такие:
- Отмечаем нужный коммит командой
edit. - Когда Git остановится на нем, «разбиваем» его с помощью
git reset HEAD^. - Создаем несколько новых коммитов вместо одного.
Давайте покажу на примере.
Допустим, у вас есть один коммит, который одновременно:
- Меняет модель данных
- Обновляет бизнес-логику
- Обновляет тесты
Исторически это неудобно. Лучше разделить его на три коммита.
- Запускаем интерактивный rebase.
git rebase -i HEAD~3
# Допустим нужный коммит входит в последние три
- В списке ставим
editдля нужного коммита:
pick a1b2c3d Предыдущие изменения
edit e4f5g6h Смешанный коммит модели логика тесты
pick 123abcd Последующие изменения
Сохраняем, Git останавливается на коммите
e4f5g6h.Теперь «разбиваем» его:
git reset HEAD^
# Здесь мы откатываем индекс и рабочую директорию к состоянию
# перед этим коммитом но изменения коммита остаются в рабочем каталоге
# как незафиксированные
git status
# Увидите все файлы которые изменил тот самый коммит
- Теперь вы можете постепенно группировать файлы и создавать отдельные коммиты:
# 1. Коммит для модели данных
git add models/*
# Добавляем только файлы модели
git commit -m "Обновлена модель данных"
# 2. Коммит для бизнес-логики
git add services/*
git commit -m "Обновлена бизнес-логика под новую модель"
# 3. Коммит для тестов
git add tests/*
git commit -m "Обновлены тесты под новую модель и логику"
# Продолжаем rebase
git rebase --continue
В результате один «громоздкий» коммит превращается в три более понятных и логичных.
Решение конфликтов при интерактивном rebase
Как выглядят конфликты при rebase
Во время интерактивного rebase конфликты решаются так же, как и при обычном git rebase или git merge.
Если Git не может автоматически применить очередной коммит, вы увидите:
CONFLICT (content): Merge conflict in path/to/file.go
error: could not apply 123abcd... Сообщение коммита
hint: Fix conflicts and then run
hint: git add <path>
hint: git rebase --continue
Статус подскажет, что происходит:
git status
# Здесь вы увидите файлы в состоянии both modified
Шаги по решению конфликта
- Открываете конфликтующий файл. Там будут вставки вида:
func Example() {
<<<<<<< HEAD
// Ваша текущая версия
doOld()
=======
// Версия из коммита применяемого при rebase
doNew()
>>>>>>> 123abcd Сообщение коммита
}
- Ручками приводите код в правильный вид:
func Example() {
// Объединенная логика после ручного решения конфликта
doNew()
logChange()
}
- Отмечаете, что конфликт решен:
git add path/to/file.go
# Добавляем файл в индекс после ручного решения конфликта
git rebase --continue
# Сообщаем Git что конфликт решен и можно продолжать
Если еще один конфликт — повторяете процесс.
Как прервать интерактивный rebase
Если вы поняли, что зашли в тупик, сделали слишком много изменений, или просто хотите вернуться к исходному состоянию, всегда можно прервать rebase:
git rebase --abort
# Отменяет весь текущий процесс rebase и возвращает ветку в исходное состояние
Это полезно, если вы начали сложный интерактивный rebase, напутали с командами или столкнулись с конфликтами, которые сейчас не готовы решать.
Безопасность и работа с удаленным репозиторием
Правило переписывания истории
Главное правило: интерактивное rebase безопасен только для веток, которыми пользуетесь вы один, или для коммитов, которые еще не отправлены в общий репозиторий.
Если вы переписываете историю ветки, которая уже была запушена и на которую опираются другие разработчики, они могут столкнуться с конфликтами при git pull и будут вынуждены вручную разруливать ситуацию.
Поэтому хорошая практика:
- Свободно использовать
git rebase -iв локальных feature-ветках до первогоgit push. - Быть осторожным и договариваться с командой, если нужно переписать историю уже опубликованной ветки.
Что делать, если вы уже переписали историю и запушили
Если вам все-таки пришлось сделать интерактивный rebase в ветке, которая уже существует на удаленном репозитории, то для отправки изменений потребуется «форсированный» push:
git push --force-with-lease
# Здесь вы перезаписываете удаленную ветку своей локальной версией
# с переписанной историей
Почему именно --force-with-lease, а не --force:
--force-with-leaseпроверяет, не обновилась ли удаленная ветка с момента вашего последнего pull.- Если кто-то успел запушить свои коммиты, команда не пройдет и вы не затрете чужую работу.
Конечно, такие операции лучше делать осознанно и по договоренности с командой.
Практические сценарии использования git rebase -i
Сценарий 1 — «Почистить» историю перед merge в основную ветку
Представьте, что вы работали в ветке feature/auth-improvements и сделали следующие коммиты:
- «Промежуточные правки»
- «Еще правки»
- «Поправил логин»
- «Поправил logout»
- «Убрал отладочный вывод»
Вы хотите, чтобы в main попали:
- Один коммит «Улучшена авторизация»
- И отдельно один коммит «Убран отладочный вывод»
- Запускаем интерактивный rebase относительно ветки main:
git rebase -i main
# Здесь будут показаны все ваши коммиты отличающиеся от main
- В открывшемся списке группируем коммиты:
pick a1b2c3d Промежуточные правки
pick e4f5g6h Еще правки
pick 123abcd Поправил логин
pick 987zyxw Поправил logout
pick 55aa66bb Убрал отладочный вывод
Превращаем в:
pick a1b2c3d Промежуточные правки
fixup e4f5g6h Еще правки
fixup 123abcd Поправил логин
fixup 987zyxw Поправил logout
pick 55aa66bb Убрал отладочный вывод
- Git объединит первые четыре коммита в один, а пятый оставит отдельным. После rebase у вас будет:
- Коммит 1 — «Промежуточные правки» (вы можете предварительно переписать сообщение через reword на что-то вроде «Улучшена авторизация»)
- Коммит 2 — «Убрал отладочный вывод»
Сценарий 2 — Исправить «некрасивое» сообщение к старому коммиту
В истории есть старый коммит:
- «zzzz» — очевидно, случайное, нечитаемое сообщение.
Хочется оставить изменения, но исправить сообщение.
- Определяем, насколько далеко коммит от HEAD, например:
git log --oneline
# Смотрите в списке коммитов и считаете сколько шагов назад нужный коммит
- Запускаем интерактивный rebase:
git rebase -i HEAD~5
# Допустим коммит находится в пределах последних пяти
- В редакторе меняем:
pick 11aa22bb zzzz
на:
reword 11aa22bb zzzz
- При проигрывании rebase Git остановится на этом коммите и откроет редактор с текущим сообщением. Меняем на:
Исправлен расчет скидки для VIP-клиентов
- Сохраняем — и все, содержимое осталось тем же, а сообщение теперь говорит за себя.
Сценарий 3 — Разделить один большой коммит на логичные части
Мы уже частично проходили этот сценарий в разделе про split, но здесь еще раз коротко, как последовательность действий.
- Помечаем коммит для разделения через
editв интерактивном списке. - Когда Git остановится на нем, делаем:
git reset HEAD^
# Возвращаемся к состоянию до этого коммита оставляя его изменения незафиксированными
- Добавляем файлы партиями:
git add src/models/*
git commit -m "Обновлены модели"
git add src/services/*
git commit -m "Обновлена бизнес-логика"
git add tests/*
git commit -m "Обновлены тесты"
- Продолжаем rebase:
git rebase --continue
Теперь вы увидите в истории три новых коммита вместо одного крупного.
Заключение
Интерактивное перебазирование git rebase -i — это инструмент, который позволяет вам не просто «записывать» историю, но и сознательно формировать ее. Вы можете:
- Менять порядок коммитов
- Объединять мелкие правки в логичные блоки
- Удалять ненужные коммиты
- Исправлять содержимое и сообщения уже существующих коммитов
- Разделять один крупный коммит на несколько более понятных
Главное — помнить, что интерактивное rebase переписывает историю. Для локальных веток это отличная возможность сделать историю чище и понятнее. Для общих веток стоит быть аккуратным и договариваться с командой.
Если вы будете постепенно применять git rebase -i к своим feature-веткам перед тем, как открывать pull request, ваш git log станет гораздо более структурированным и читабельным. А коллегам будет проще понимать, что именно и в каком порядке вы меняли.
Частозадаваемые технические вопросы
Как задать редактор по умолчанию для git rebase -i
Если Git открывает неудобный вам редактор, можно настроить другой:
git config --global core.editor "nano"
# Здесь вместо nano вы можете указать vim code --wait и т.д.
После этого интерактивный rebase будет открываться в указанном редакторе.
Как «откатить» интерактивный rebase после его завершения
Если вы уже завершили rebase и поняли, что все испортили, можно вернуться к состоянию до rebase с помощью reflog:
git reflog
# Находим запись до начала rebase например HEAD@{3}
git reset --hard HEAD@{3}
# Возвращаемся к состоянию репозитория до rebase
Важно понимать что вы потеряете все изменения сделанные после этого состояния.
Можно ли использовать git rebase -i только для изменения сообщений без переписывания содержимого
Да. Если вы используете только команду reword для нужных коммитов, содержимое файлов не меняется. Git все равно создаст новые коммиты с новыми хешами, но изменения в коде останутся теми же. Это удобно для правки стиля сообщений.
Как сделать автоматический fixup без ручного редактирования списка rebase
Вы можете заранее пометить коммиты как fixup к другому коммиту:
git commit --fixup=<hash_или_реф>
# Создает коммит с особым сообщением начинающимся с fixup
А затем запустить:
git rebase -i --autosquash <base>
# Git автоматически переставит fixup-коммиты и применит их как fixup
Это ускоряет работу когда вы заранее знаете что коммит является доработкой к предыдущему.
Что делать если во время rebase я случайно закрыл редактор не сохранив изменения в сценарии
Если вы закрыли редактор без сохранения файла плана rebase, Git просто отменит запуск интерактивного rebase и оставит все как есть. В этом случае достаточно запустить команду git rebase -i еще раз и аккуратнее отредактировать файл сценария. Если же вы сохранили некорректный сценарий и rebase зашел в странное состояние — можете использовать git rebase --abort, чтобы вернуться к исходной точке и начать заново.
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

Основы Git
Антон Ларичев
TypeScript с нуля
Антон Ларичев