Олег Марков
Интерактивное перебазирование - git rebase -i
Введение
Интерактивное перебазирование в Git — это инструмент, который позволяет вам аккуратно переписать историю коммитов. С его помощью вы можете:
- объединять несколько мелких коммитов в один;
- разбивать один большой коммит на несколько логичных частей;
- менять порядок коммитов;
- редактировать сообщения;
- удалять лишние или неудачные коммиты;
- исправлять старые коммиты задним числом.
Смотрите, ключевая идея проста: вы берете диапазон своих коммитов и говорите Git, что хотите их проиграть заново, но с возможностью «отредактировать сценарий» по ходу. Интерактивный режим rebase как раз и является этим редактором сценария.
В этой статье я покажу вам, как работает git rebase -i, на что обратить внимание, какие ошибки часто допускают разработчики и как их избегать.
Что такое rebase и чем интерактивный режим отличается от обычного
Прежде чем разбираться с интерактивным режимом, давайте коротко напомним, что делает обычный rebase.
Базовая идея rebase
Обычное перебазирование переписывает историю ветки так, будто ваши коммиты были сделаны поверх другой «базы».
Представьте ситуацию:
- у вас есть ветка main;
- вы создали ветку feature от main;
- в main кто-то продолжил работу и добавил новые коммиты;
- теперь вы хотите перенести свои коммиты из feature так, как будто они были сделаны после последних коммитов в main.
Для этого вы используете:
git checkout feature # Переключаемся на ветку feature
git rebase main # Перебазируем feature на верхушку main
Git берет ваши коммиты из feature и «проигрывает» их поверх последнего коммита main. История становится более линейной и понятной.
Что добавляет интерактивный режим
Интерактивный режим включается опцией -i:
git rebase -i main
Вместо того чтобы просто автоматически проиграть ваши коммиты, Git открывает редактор и показывает список коммитов, которые собирается перебазировать. Теперь вы можете:
- управлять этим списком;
- менять команды для каждого коммита;
- останавливать процесс на коммитах для их редактирования.
По сути, вы получаете возможность не просто перенести коммиты, а переписать их так, как удобно вам и команде.
Как запустить интерактивное перебазирование
Выбор диапазона коммитов
Чаще всего интерактивное перебазирование запускают относительно другой ветки или относительно HEAD~N.
Примеры:
git rebase -i main # Перебазировать все коммиты, которых нет в main
git rebase -i HEAD~5 # Работаем с последними 5 коммитами
git rebase -i <hash>^ # Список начнётся с коммита, следующего за <hash>
Обратите внимание на запись HEAD~5:
- HEAD — текущий коммит;
- HEAD~5 — это «5 коммитов назад» от текущего;
- когда вы пишете git rebase -i HEAD~5, в список попадут последние 5 коммитов, идущих после HEAD~5.
Что откроется при запуске rebase -i
Когда вы выполните команду, Git откроет редактор (обычно Vim, Nano или тот, который настроен в Git). Внутри вы увидите примерно такое:
pick a1b2c3d Первый коммит
pick b2c3d4e Второй коммит
pick c3d4e5f Третий коммит
# Rebase 1234567..c3d4e5f onto 1234567 (3 commands)
#
# Команды:
# p, pick = использовать коммит как есть
# r, reword = использовать коммит, но изменить сообщение
# e, edit = остановиться на этом коммите для правки
# s, squash = объединить с предыдущим коммитом, объединить сообщения
# f, fixup = объединить с предыдущим коммитом, отбросив сообщение
# x, exec = выполнить команду оболочки
# d, drop = удалить коммит
Здесь я намеренно показываю комментарии, которые Git добавляет сам. Они помогают вспомнить, что означает каждая команда.
Все, что вам нужно сделать — отредактировать строки с командами и сохранить файл. После сохранения rebase начнется.
Основные команды в интерактивном rebase
Теперь давайте по шагам разберем, что значат команды, которые вы можете использовать в этом списке.
Команда pick — оставить коммит как есть
Команда по умолчанию — pick:
pick a1b2c3d Первый коммит
pick b2c3d4e Второй коммит
Здесь Git «возьмет» коммиты и перепроиграет их без изменений. Вы можете использовать сокращение:
p a1b2c3d Первый коммит
Но, как правило, удобнее оставлять полное слово для читабельности.
Команда reword — изменить только сообщение коммита
Если вам нужно поправить только текст сообщения, но не содержимое файлов, используйте reword:
reword a1b2c3d Первый коммит
pick b2c3d4e Второй коммит
После сохранения файла rebase:
- дойдет до этого коммита;
- создаст новый коммит с теми же изменениями;
- откроет редактор сообщения, чтобы вы могли переписать текст.
Давайте разберемся на примере:
git rebase -i HEAD~3 # Берем последние 3 коммита
# В редакторе меняем pick на reword у нужного коммита
# Сохраняем файл
# Git откроет редактор для изменения сообщения
Такой подход удобен, если вы хотите привести сообщения к одному стилю перед тем, как отправлять изменения в общий репозиторий.
Команда edit — изменить содержимое коммита
Команда edit позволяет остановиться на конкретном коммите, чтобы изменить его содержимое (добавить или убрать файлы, подправить код и т.п.).
Пример списка:
pick a1b2c3d Первый коммит
edit b2c3d4e Второй коммит
pick c3d4e5f Третий коммит
Что произойдет:
- Git перепроиграет первый коммит;
дойдет до второго и остановится, выведя сообщение вроде:
Stopped at b2c3d4e... Второй коммит You can amend the commit nowТеперь вы можете менять файлы как обычно.
Пример действий:
# Меняем файлы, как обычно
vim file.go # // Открываем и правим файл
git add file.go # // Добавляем изменения к коммиту
# Переписываем текущий коммит
git commit --amend # // Обновляем коммит, не создавая новый
# Продолжаем rebase
git rebase --continue
Git двинется дальше по списку и продолжит применять оставшиеся коммиты.
Команды squash и fixup — объединение коммитов
Часто к концу работы над задачей у вас появляется несколько «шумных» коммитов:
- «Исправить опечатку»;
- «Правка форматирования»;
- «Еще исправление» и т.п.
Интерактивный rebase позволяет объединить их в один аккуратный коммит.
squash — объединить коммиты и их сообщения
Пример списка:
pick a1b2c3d Реализация фичи
squash b2c3d4e Исправление ошибки в фиче
squash c3d4e5f Рефакторинг после ревью
Как это работает:
- первый коммит (pick) становится «основным»;
- последующие с squash «приклеиваются» к нему;
- Git объединяет все изменения в один коммит;
- откроет редактор, чтобы вы объединили или отредактировали итоговое сообщение.
Вы увидите текст вроде:
# Это сообщение нового коммита
Реализация фичи
Исправление ошибки в фиче
Рефакторинг после ревью
Вы можете переписать это сообщение в одно логичное, например:
Реализация фичи X с исправлением ошибки и рефакторингом
fixup — объединить изменения, отбросив сообщение
fixup похож на squash, но не сохраняет текст сообщения «слипаемых» коммитов.
Пример:
pick a1b2c3d Реализация фичи
fixup b2c3d4e Исправление опечатки
После rebase у вас будет один коммит с сообщением:
Реализация фичи
и внутри уже будут изменения из обоих коммитов.
Часто используют в связке с опцией --autosquash, о ней поговорим позже.
Команда drop — удалить коммит
Если вы понимаете, что какой-то коммит был лишним, вы можете удалить его из истории.
Варианты:
drop a1b2c3d Лишний коммит
или просто удалить строку целиком:
# Было:
pick a1b2c3d Лишний коммит
pick b2c3d4e Полезный коммит
# Стало:
pick b2c3d4e Полезный коммит
Git просто не будет заново создавать этот коммит при перебазировании.
Используйте с осторожностью, если коммиты уже ушли в общий репозиторий — об этом мы поговорим отдельно.
Команда exec — выполнить команду по ходу rebase
Команда exec позволяет вам вставить выполнение произвольной команды оболочки во время rebase.
Например, вы хотите, чтобы после каждого коммита запускались тесты:
pick a1b2c3d Добавить функцию
exec go test ./... # // Запустить тесты после этого коммита
pick b2c3d4e Оптимизация
Git после применения первого коммита выполнит команду go test ./..., и только если она пройдет успешно, пойдет дальше.
Это уже более продвинутый сценарий, но он полезен, если вы хотите убедиться, что каждый коммит в истории «здоровый».
Изменение порядка коммитов
Одно из важных преимуществ интерактивного rebase — возможность менять порядок коммитов. Git трактует порядок строк в файле как порядок применения коммитов.
Давайте посмотрим на пример:
pick a1b2c3d Добавить модель пользователя
pick b2c3d4e Добавить API для пользователя
pick c3d4e5f Добавить тесты API
Логика здесь правильная: сначала модель, затем API, потом тесты. Но если вдруг вы случайно закоммитили тесты раньше API, вы можете исправить это.
Пример списка с неправильным порядком:
pick a1b2c3d Добавить модель пользователя
pick c3d4e5f Добавить тесты API
pick b2c3d4e Добавить API для пользователя
Вы просто переставляете строки:
pick a1b2c3d Добавить модель пользователя
pick b2c3d4e Добавить API для пользователя
pick c3d4e5f Добавить тесты API
Сохраняете, и Git применяет их в новом порядке.
Обратите внимание: если между коммитами есть зависимости по коду (например, тесты используют функции, которых еще нет), при неверном порядке вы можете получить конфликты или неработающую сборку. Тогда вам придется разбираться с ними во время rebase.
Объединение коммитов: практический сценарий
Давайте разберемся на практическом сценарии, который встречается почти у всех.
Представьте, что вы работали над задачей и сделали такую историю:
pick a1b2c3d Реализация фичи X
pick b2c3d4e Исправление опечатки
pick c3d4e5f Переименование переменных
pick d4e5f6a Исправление тестов
Вы понимаете, что все это по сути — одна фича, и хотите оставить один чистый коммит.
Шаг 1. Запустить rebase -i
git rebase -i HEAD~4
Git покажет:
pick a1b2c3d Реализация фичи X
pick b2c3d4e Исправление опечатки
pick c3d4e5f Переименование переменных
pick d4e5f6a Исправление тестов
Шаг 2. Пометить коммиты для squash
Теперь вы говорите Git, что первый коммит — основной, а остальные нужно прилепить к нему:
pick a1b2c3d Реализация фичи X
squash b2c3d4e Исправление опечатки
squash c3d4e5f Переименование переменных
squash d4e5f6a Исправление тестов
Сохраняете файл.
Шаг 3. Объединить сообщения
Git применит первый коммит, затем добавит к нему остальные, и откроет редактор сообщения с чем-то вроде:
Реализация фичи X
Исправление опечатки
Переименование переменных
Исправление тестов
Вы можете переписать все это в одно:
Реализация фичи X
- начальная реализация
- правки кода и переменных
- исправление тестов
Сохраняете, rebase завершается, и в истории у вас один аккуратный коммит.
Правка старого коммита через edit
Теперь давайте посмотрим, как исправить ошибку в среднем коммите, не трогая позже идущие.
Предположим, история такая:
commit c3d4e5f (HEAD) Добавить тесты
commit b2c3d4e Добавить API
commit a1b2c3d Добавить модель
Вы обнаружили, что в коммите «Добавить модель» забыли добавить поле в структуру. Но после него уже написан API и тесты. Вы хотите:
- поправить модель в том месте, где она была добавлена;
- при этом сохранить существующие коммиты API и тестов, но уже поверх исправленной модели.
Шаг 1. Запустить rebase для нужного диапазона
Нам нужно захватить все коммиты, начиная с того, который хотим править:
git rebase -i HEAD~3
Git покажет:
pick a1b2c3d Добавить модель
pick b2c3d4e Добавить API
pick c3d4e5f Добавить тесты
Шаг 2. Пометить коммит как edit
Меняем:
edit a1b2c3d Добавить модель
pick b2c3d4e Добавить API
pick c3d4e5f Добавить тесты
Сохраняем.
Шаг 3. Внести правки и amend
Git остановится на первом коммите и скажет, что вы можете его изменить.
Теперь вы делаете изменения:
vim user_model.go # // Добавляем недостающее поле
git add user_model.go # // Добавляем файл в индекс
git commit --amend # // Обновляем текущий коммит
Комментарий:
- commit --amend не создает новый логический коммит, а переписывает текущий, создавая новый объект коммита с обновленным содержимым.
Шаг 4. Продолжить rebase
git rebase --continue # // Продолжаем проигрывать коммиты
Git заново применит коммиты «Добавить API» и «Добавить тесты» уже поверх обновленной модели.
Разрешение конфликтов во время rebase -i
При перебазировании часто возникают конфликты — это нормально, особенно если вы меняете историю или базу ветки.
Как выглядит конфликт при rebase
Если Git не может автоматически применить коммит, он выводит что-то вроде:
CONFLICT (content): Merge conflict in file.go
error: could not apply b2c3d4e... Добавить API
И при этом rebase приостанавливается.
Ваша задача:
- Открыть конфликтующие файлы.
- Разрешить конфликты.
- Продолжить rebase.
Шаги по разрешению конфликтов
Пример:
# Смотрим статус
git status
Git покажет файлы в состоянии «both modified».
Вы открываете файл:
vim file.go
Внутри увидите маркеры конфликта:
// Ваша версия
<<<<<<< HEAD
func DoSomething() {
// Реализация А
}
=======
// Версия из коммита, который применяется
func DoSomething() {
// Реализация Б
}
>>>>>>> b2c3d4e (Добавить API)
Вы руками оставляете нужный вариант (или объединяете их):
// Здесь мы объединяем нужные части реализаций
func DoSomething() {
// Итоговая реализация
}
Далее:
git add file.go # // Отмечаем конфликт как решенный
git rebase --continue # // Продолжаем rebase
Если вы понимаете, что зашли в тупик и не хотите продолжать rebase, используйте:
git rebase --abort # // Отменить rebase и вернуться к исходному состоянию
Важное правило безопасности: переписывание «общей» истории
Интерактивный rebase переписывает историю, а это значит:
- хэши коммитов меняются;
- Git по сути создает новые коммиты взамен старых.
Здесь важно отличать два случая:
1. Локальная ветка, которую вы еще не пушили
Тут вы можете переписывать все что угодно:
- объединять;
- удалять;
- править;
- менять порядок;
и это безопасно, потому что никто, кроме вас, не опирается на эти коммиты.
2. Ветка, которую вы уже отправили на удаленный репозиторий
Если вы сделали push, а после этого делаете интерактивный rebase, история на сервере и у вас начнет отличаться.
Чтобы запушить такую ветку, вам придется использовать:
git push --force-with-lease
Эта команда перезапишет историю на сервере.
Риски:
- коллеги, которые уже успели выкачать старые коммиты, получат несостыковки и будут вынуждены вручную разруливать ситуацию;
- если кто-то от вашей ветки создал другую, его история тоже окажется поврежденной.
Поэтому общий практический совет:
- интерактивный rebase безопасен для локальных веток до первого push;
- для веток, которыми пользуются другие, лучше обсуждать такие изменения в команде и использовать их аккуратно.
Быстрая очистка истории с autosquash
Git умеет автоматически подготавливать rebase -i для фиксации заранее помеченных коммитов. Смотрите, как это работает.
Подготовка fixup-коммитов
Представьте, что вы уже сделали основной коммит:
git commit -m "Реализация фичи X"
Потом обнаружили ошибку и решили сделать «фиксирующий» коммит:
git commit --fixup=<hash_основного_коммита>
или, если используете Git 2.35+:
git commit --fixup=amend:HEAD # // Создать коммит, который будет склеен с HEAD
Git сам создаст сообщение вида:
fixup! Реализация фичи X
Автоматическое расположение с autosquash
Теперь вы запускаете:
git rebase -i --autosquash main
Git:
- сам отыщет коммиты с префиксом fixup! или squash!;
- переставит их сразу за соответствующие базовые коммиты;
- автоматически проставит для них команды fixup или squash в файле rebase.
В редакторе вы увидите уже подготовленный порядок и команды, останется только сохранить.
Это очень удобно, когда вы сначала делаете основной коммит, а затем несколько исправлений, которые хотите в итоге слить в один.
Полезные практики при работе с rebase -i
Работайте с небольшими диапазонами
Старайтесь не брать сразу 30–40 коммитов. Лучше:
- работать с последними 3–10;
- чистить историю по мере работы над задачей.
Так ниже риск запутаться и столкнуться с масштабными конфликтами.
Используйте осмысленные сообщения коммитов
Интерактивное перебазирование особенно полезно, если вы:
- в процессе работы пишете любые «черновые» сообщения;
- а в конце приводите их к единому аккуратному виду через reword, squash и fixup.
Хорошая практика:
- пока вы экспериментируете — «грязные» коммиты;
- перед пушем — один или несколько чистых логичных коммитов.
Перед rebase делайте бэкап ветки
Если вы только осваиваете rebase -i, полезно подстраховаться:
git branch backup/feature-X # // Создаем запасную ветку от текущего состояния
Если что-то пошло не так, вы всегда можете:
git checkout feature-X
git reset --hard backup/feature-X # // Вернуться к исходному состоянию
Заключение
Интерактивное перебазирование в Git — это мощный инструмент для управления историей коммитов. С его помощью вы можете:
- вычищать историю перед отправкой в общий репозиторий;
- объединять мелкие исправления в крупные логичные коммиты;
- исправлять старые ошибки без создания лишних коммитов;
- поддерживать в репозитории аккуратную, линейную историю.
Основная идея проста: git rebase -i дает вам «черновик сценария», в котором вы описываете, что нужно сделать с каждым коммитом, а Git затем аккуратно исполняет этот сценарий.
По мере того как вы будете чаще пользоваться интерактивным rebase, он станет естественной частью вашего рабочего процесса, особенно при работе с фиче-ветками и перед созданием pull request.
Частозадаваемые технические вопросы по теме и ответы
1. Как изменить только один конкретный старый коммит, не трогая остальные?
- Найдите хэш коммита:
bash git log --oneline - Запустите rebase от родителя этого коммита:
bash git rebase -i <hash>^ - В списке коммитов замените pick на edit у нужного коммита.
- После остановки:
bash # Вносите изменения в файлы git add <files> # // Добавляете изменения git commit --amend # // Переписываете коммит git rebase --continue # // Продолжаете rebase
2. Как отменить уже выполненный интерактивный rebase, если результат не устроил?
Если вы еще не закрыли терминал и rebase только что был выполнен:
- Смотрите reflog:
bash git reflog - Найдите запись до начала rebase, что-то вроде "rebase: starting".
- Перекатитесь туда:
bash git reset --hard <hash_до_rebase>
3. Что делать, если во время rebase постоянно возникают конфликты в одних и тех же файлах?
- Старайтесь уменьшить диапазон rebase, работая с меньшим количеством коммитов.
- Локализуйте конфликтующие изменения:
- посмотрите, какие коммиты затрагивают проблемный файл:
bash git log --oneline -- <file> - возможно, имеет смысл сначала объединить часть коммитов в один, чтобы упростить конфликты.
- посмотрите, какие коммиты затрагивают проблемный файл:
- При необходимости временно отмените rebase:
bash git rebase --abortи сначала упростите историю (например, через локальный merge), затем снова запустите rebase -i.
4. Как аккуратно форсировать push после интерактивного rebase?
Используйте именно --force-with-lease, а не просто --force:
git push --force-with-lease origin feature-X
Этот вариант проверяет, не изменился ли удаленный бранч по сравнению с тем, что вы видели. Если кто-то успел запушить новые коммиты, push будет отклонен, и вы не затрете чужую работу.
5. Можно ли делать rebase -i поверх другой фиче-ветки, а не main?
Да. Просто указываете базой нужную ветку:
git rebase -i feature-Y
Git возьмет все коммиты, которые есть в вашей ветке и отсутствуют в feature-Y, и откроет интерактивный список. Дальше вы работаете так же, как при перебазировании на main. Это удобно, если ваша фича логически продолжает другую фичу.
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

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