Git rebase переписывает историю коммитов вашей ветки так, будто вы изначально начали работу от свежей вершины main. Это даёт линейную и читаемую историю без шумных merge-коммитов, но требует понимания, когда rebase безопасен, а когда сломает рабочий процесс команде. Разберём интерактивный режим, склейку коммитов, разрешение конфликтов и аварийный выход.

rebase vs merge: в чём принципиальная разница
git merge объединяет две ветки и создаёт merge-коммит с двумя родителями. История остаётся честной: видно, где начали ветку, что в ней делали и когда влили. Минус — на крупном проекте дерево превращается в спагетти.
git rebase снимает ваши коммиты с ветки, переключается на новую базу (например, обновлённый main) и поочерёдно применяет каждый коммит заново. SHA-хеши при этом меняются — это уже другие коммиты, пусть и с тем же содержимым.
# Сценарий: вы работаете в feature, в main за это время появились новые коммиты
git checkout feature
git fetch origin
# Вариант 1: merge — появится merge-коммит
git merge origin/main
# Вариант 2: rebase — линейная история
git rebase origin/main
Практика большинства команд такая: feature-ветки rebase’им поверх main перед пушем, а саму main никогда не rebase’им — иначе у коллег сломается локальная история.
Интерактивный rebase: pick, squash, fixup, reword, edit
Флаг -i открывает редактор со списком коммитов, которые будут применены. Каждую строку можно изменить — это и есть «переписывание истории».
# Перебрать последние 5 коммитов
git rebase -i HEAD~5
# Перебрать всё, что отличается от main
git rebase -i main
В редакторе вы увидите примерно следующее:
pick a1b2c3d Добавил модель User
pick e4f5a6b Опечатка в комментарии
pick c7d8e9f Тесты для User
pick 0a1b2c3 fix
pick 4d5e6f7 ещё один fix
# Команды:
# p, pick = использовать коммит как есть
# r, reword = использовать коммит, но изменить сообщение
# e, edit = остановиться и дать поправить
# s, squash = склеить с предыдущим, спросить новое сообщение
# f, fixup = склеить с предыдущим, выбросить сообщение
# d, drop = выбросить коммит
Типичная задача — собрать в один коммит работу над фичей и три «fix» коммита поверх. Меняем pick на squash или fixup:
pick a1b2c3d Добавил модель User
fixup e4f5a6b Опечатка в комментарии
pick c7d8e9f Тесты для User
fixup 0a1b2c3 fix
fixup 4d5e6f7 ещё один fix
После сохранения файла получится два чистых коммита: «Добавил модель User» и «Тесты для User». Разница между squash и fixup — первый спросит новое сообщение коммита, второй молча выбросит сообщение склеиваемого.
reword и edit: чинить сообщения и содержимое
reword нужен, когда содержимое коммита нормальное, а сообщение хочется переписать (опечатка, неверный тикет, нет глагола):
reword a1b2c3d Добавил модель User
pick c7d8e9f Тесты для User
После сохранения git откроет редактор с сообщением старого коммита — правите, сохраняете, rebase идёт дальше.
edit мощнее: rebase остановится перед применением коммита и даст вам что-то изменить. Например, забыли добавить файл в коммит:
# После остановки rebase
git add forgotten_file.php
# Доклеиваем к текущему коммиту, не создавая нового
git commit --amend --no-edit
# Продолжаем rebase
git rebase --continue
Если же нужно полностью разобрать один большой коммит на несколько маленьких:
# На остановке "edit" откатываем изменения в индекс, оставляя файлы
git reset HEAD^
# Теперь добавляем по частям и коммитим отдельно
git add app/Models/User.php
git commit -m "Добавил модель User"
git add tests/UserTest.php
git commit -m "Тесты для User"
git rebase --continue
autosquash: ускоряем работу с fixup-коммитами
Готовить fixup-коммиты вручную, а потом скрупулёзно расставлять их в редакторе — утомительно. Git умеет это автоматически. Делаете правку и коммитите её с флагом --fixup, указывая SHA коммита, к которому относится правка:
# Нашли баг в коммите a1b2c3d, исправили
git add app/Models/User.php
git commit --fixup=a1b2c3d
# Создастся коммит с сообщением "fixup! Добавил модель User"
Когда наберётся пачка таких коммитов — запускаем rebase с --autosquash:
git rebase -i --autosquash main
Git сам переставит fixup-коммиты после их «родителей» и пометит как fixup. Останется только сохранить файл. Чтобы не писать --autosquash каждый раз, включите глобально:
git config --global rebase.autoSquash true
Решение конфликтов в процессе rebase
При перебазировании каждый коммит применяется отдельно — а значит, конфликты тоже могут прилетать по одному. Git остановится и покажет файлы с конфликтами:
$ git rebase -i main
Auto-merging app/Service/Order.php
CONFLICT (content): Merge conflict in app/Service/Order.php
error: could not apply c7d8e9f... Поддержка скидок в заказе
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
Алгоритм действий:
git status— смотрим, какие файлы в конфликте- Открываем файл, ищем маркеры
<<<<<<<,=======,>>>>>>>, оставляем нужный вариант git add <файл>— помечаем как разрешённыйgit rebase --continue— продолжаем
# Удобная команда: открыть в редакторе все файлы с конфликтами
git diff --name-only --diff-filter=U | xargs $EDITOR
# Использовать графический mergetool (например, meld, kdiff3, vscode)
git mergetool
# После правок
git add .
git rebase --continue
Если в череде из 10 коммитов один и тот же кусок кода правится несколько раз, придётся разрешать один и тот же конфликт несколько раз. Чтобы git запомнил решения и применил их в следующих коммитах, включите rerere (reuse recorded resolution):
git config --global rerere.enabled true
После включения git сохраняет ваши решения конфликтов и автоматически применяет их при повторных столкновениях — экономит часы на длинных rebase’ах.
Отмена rebase: –abort, –skip и git reflog
Если в процессе rebase что-то пошло не так, у вас есть три уровня отката.
1. Отменить весь rebase и вернуться к состоянию до запуска:
git rebase --abort
Эта команда работает только пока rebase в процессе — после --continue или завершения она недоступна.
2. Пропустить проблемный коммит (если он, например, стал не нужен):
git rebase --skip
3. Откатить уже завершённый rebase через reflog. Это спасательный круг, когда вы поняли, что зря склеили коммиты или потеряли важную правку:
# Смотрим, где была ветка до rebase
git reflog
# Пример вывода:
# 4d5e6f7 HEAD@{0}: rebase (finish): returning to refs/heads/feature
# 0a1b2c3 HEAD@{1}: rebase (pick): Тесты для User
# c7d8e9f HEAD@{5}: rebase (start): checkout main
# a1b2c3d HEAD@{6}: commit: ещё один fix <-- сюда хотим вернуться
# Откатываемся
git reset --hard HEAD@{6}
Reflog хранит все перемещения HEAD за последние 90 дней по умолчанию — потеряться по-настоящему в git довольно сложно.
Когда rebase опасен: золотое правило публичных веток
Главное правило, которое стоит выгравировать на клавиатуре: никогда не делайте rebase ветки, которая уже запушена и используется кем-то ещё. После rebase коммиты получают новые SHA, и для остальных это выглядит как «появилась параллельная история, старая исчезла». Когда коллега попытается запушить свои изменения поверх старой версии, git будет уговаривать его сделать merge — и в итоге история превратится в кошмар с дубликатами коммитов.
Безопасные сценарии rebase:
- Локальная feature-ветка, которую вы ещё не пушили
- Ваша личная ветка на удалёнке, с которой больше никто не работает (тогда после rebase нужен
git push --force-with-lease) - PR-ветка перед мержем в main — для красивой истории
Опасные сценарии:
- Rebase
main,develop,release/*— это коллективные ветки - Rebase ветки, по которой уже открыт PR и ревьюеры оставили комментарии (потеряются якоря на конкретные строки)
- Rebase в общей ветке, в которую коммитят несколько разработчиков
Для push после rebase используйте --force-with-lease, а не --force. Первый сначала проверит, что на удалённой ветке нет чужих коммитов поверх вашей старой версии, и только потом перезапишет. Это спасает от случайного затирания чужой работы:
# Безопасный force-push: откажется, если кто-то успел запушить раньше
git push --force-with-lease origin feature
# Опасный force-push: молча перезатрёт чужие коммиты
git push --force origin feature
Чеклист по работе с rebase
- Перед rebase сделайте
git status— рабочая директория должна быть чистой - Запомните или запишите текущий SHA — пригодится для
git reset --hard, если что-то сломается - Не делайте rebase публичных веток — только своих локальных или личных feature-веток
- Используйте
fixup+--autosquashвместо ручного редактирования todo-листа - Включите
rerere, чтобы не разрешать одни и те же конфликты дважды - Пушьте после rebase только с
--force-with-lease, никогда с--force - Если зашли в тупик —
git rebase --abort, выдохните, попробуйте снова
Освоив интерактивный rebase, вы перестанете бояться «переписывания истории» и начнёте сдавать PR с чистой линейной серией коммитов, по которой ревьюер пробежится за минуту вместо получаса.
