Открой свою фича-ветку и посмотри на лог честно. Скорее всего там лежит что-то вроде "добавил эндпоинт", потом "fix", потом "ещё fix", "опечатка", "ну точно теперь работает", "ребейз" и "WIP не смотреть". Это нормальный рабочий мусор - так выглядит реальная разработка. Но если ты выльешь эти восемь коммитов в pull request, ревьюер увидит не логику изменения, а археологию твоих мучений. История коммитов - это не дневник, а документация. И вот тут на сцену выходит интерактивный rebase - самый мощный инструмент чистки в Git.
git rebase interactive (он же git rebase i, если набирать на скорость) позволяет взять серию коммитов и переиграть её как угодно: объединить, переименовать, переупорядочить, разбить, выкинуть, остановиться на любом и поправить содержимое. На входе - грязная серия из десяти коммитов, на выходе - три чистых, каждый из которых решает одну понятную задачу и собирается тестами. Это и есть переписать историю git осознанно, а не магией.
Важная оговорка с первой минуты. Rebase не меняет старые коммиты - он создаёт новые с новыми хешами. Старые версии никуда не пропадают сразу, они продолжают жить в reflog, пока их не съест сборка мусора. Поэтому rebase обратим, и ниже мы на этом построим всю страховку. И второе железное правило: переписывай только то, что ещё не ушло в общую ветку и что не тянут другие люди. Своя локальная фича-ветка перед PR - идеальная зона для rebase. Чужой main - запретная.

Как устроен git rebase i и его команды
Запускаешь так:
Код: Выделить всё
git rebase -i HEAD~5
Код: Выделить всё
pick a1b2c3d add user endpoint
pick d4e5f6a fix validation
pick 7890abc typo
pick fedcba9 add tests
pick 0011223 WIP review fixes
# Rebase 9f8e7d6..0011223 onto 9f8e7d6 (5 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# d, drop = remove commit
# b, break = stop here (continue rebase later with 'git rebase --continue')
# x, exec = run command (the rest of the line) using shell
- pick - взять коммит как есть. Дефолт для каждой строки.
- reword - взять коммит, но дать переписать его сообщение. Git остановится и откроет редактор сообщения.
- edit - остановиться на этом коммите с уже применёнными изменениями, дать тебе поковыряться (доправить файлы, разбить, переразложить) и продолжить.
- squash - влить этот коммит в предыдущий, объединив оба сообщения. Так делают squash коммиты git.
- fixup - то же, что squash, но сообщение этого коммита выбрасывается, остаётся только сообщение предыдущего. Это git fixup в чистом виде.
- drop - выкинуть коммит совсем. То же самое, что просто стереть строку.
- break - искусственная остановка в середине плана, чтобы осмотреться и продолжить через git rebase --continue.
- exec - выполнить произвольную shell-команду после применения коммита (например, прогнать тесты).
Самый частый сценарий - объединить коммиты git. Возьмём наш план и схлопнем три уборочных коммита в первый осмысленный:
Код: Выделить всё
pick a1b2c3d add user endpoint
fixup d4e5f6a fix validation
fixup 7890abc typo
pick fedcba9 add tests
pick 0011223 WIP review fixes
После сохранения Git проигрывает план и печатает:
Код: Выделить всё
[detached HEAD 5e6f7a8] add user endpoint
Date: Mon Jun 15 11:02:31 2026 +0300
3 files changed, 47 insertions(+), 4 deletions(-)
Successfully rebased and updated refs/heads/feature/users.
reword решает отдельную боль - переименовать коммит без касания его содержимого. Ставишь reword напротив строки, Git дойдёт до неё, применит изменения и откроет редактор сообщения:
Код: Выделить всё
pick a1b2c3d add user endpoint
reword fedcba9 add tests
Разбить, переупорядочить и удалить коммит
Разбить один коммит на два - приём, который отличает новичка от инженера. Допустим, в один коммит ты по ошибке свалил и логику, и тесты, а хочешь развести их. Ставишь edit:
Код: Выделить всё
edit a1b2c3d add endpoint and tests
Код: Выделить всё
git reset HEAD^
git add src/users.php
git commit -m "feat: add user endpoint"
git add tests/UsersTest.php
git commit -m "test: cover user endpoint"
git rebase --continue
Переупорядочить - просто поменяй строки местами в плане. Хочешь, чтобы тесты шли до эндпоинта - переставь строки, Git проиграет в новом порядке. Осторожно: если коммиты трогают одни и те же строки, перестановка даст конфликт - это нормально, разрулишь по ходу. Удалить коммит - поставь drop или сотри строку целиком. Удалил случайный "WIP debug print" - и его как не было.
autosquash и git fixup: цикл ответа на ревью
Теперь самое вкусное - workflow, который превращает ответы на ревью в удовольствие. Вспомни урок 19: ревьюер оставил три замечания, ты их правишь. Плохой путь - три коммита "review fix 1/2/3" поверх. Хороший путь - autosquash с --fixup.
Ты правишь код по замечанию к коммиту a1b2c3d и коммитишь не обычно, а так:
Код: Выделить всё
git commit --fixup a1b2c3d
Код: Выделить всё
git rebase -i --autosquash main
Код: Выделить всё
pick a1b2c3d add user endpoint
fixup 9a8b7c6 fixup! add user endpoint
pick fedcba9 add tests
fixup 1d2e3f4 fixup! add tests
Код: Выделить всё
git config --global rebase.autosquash true
exec, autostash и тесты на каждом коммите
exec (x) гоняет команду после применения коммита. Сила в флаге --exec - он вставит команду после каждого pick в плане:
Код: Выделить всё
git rebase -i --exec "php vendor/bin/phpunit" main
Код: Выделить всё
pick a1b2c3d add user endpoint
exec php vendor/bin/phpunit
pick fedcba9 add tests
exec php vendor/bin/phpunit
Грабля, на которой все спотыкаются: rebase не запустится с грязным рабочим каталогом. Решение - autostash. Включи раз и навсегда:
Код: Выделить всё
git config --global rebase.autostash true
Грабли, прерывание и reflog-спасение
Главная боль интерактивного rebase - конфликты на каждом шаге. Когда ты схлопываешь и переставляешь коммиты, которые трогают соседние строки, конфликт возможен на любом шаге. Git остановится:
Код: Выделить всё
CONFLICT (content): Merge conflict in src/users.php
error: could not apply 7890abc... typo
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted-files>", then run "git rebase --continue".
А если уже закончил rebase, а потом понял, что испортил историю или "потерял" коммит? Старые коммиты живы в reflog. Перед началом rebase Git кладёт исходный HEAD в ORIG_HEAD - грубый откат назад одной командой:
Код: Выделить всё
git reset --hard ORIG_HEAD
Код: Выделить всё
git reflog
0011223 HEAD@{0}: rebase (finish): returning to refs/heads/feature
5e6f7a8 HEAD@{1}: rebase (squash): add user endpoint
a1b2c3d HEAD@{5}: commit: add user endpoint
9f8e7d6 HEAD@{6}: checkout: moving from main to feature
Мини-лаба: почисти ветку руками
Повтори по шагам в тестовом репозитории, держа reflog как страховку.
- Создай репозиторий и серию мусорных коммитов: git init lab, внутри сделай 4 коммита, где второй и третий - правки первого ("fix", "typo"), а четвёртый - нормальный.
- Запусти git rebase -i HEAD~4. Поставь fixup напротив второго и третьего коммита. Сохрани, закрой. Проверь git log --oneline - должно остаться два коммита.
- Поломай первый коммит специально: смешай в нём два файла. Запусти rebase снова, поставь edit, разбей через git reset HEAD^ и два отдельных git commit, заверши git rebase --continue.
- Сделай правку и закоммить через git commit --fixup <sha-первого>. Запусти git rebase -i --autosquash HEAD~3 и убедись, что fixup уже разложен в плане.
- Теперь сломай нарочно: в плане поставь drop напротив нужного коммита, заверши rebase. Посмотри git reflog, найди состояние до drop и верни всё через git reset --hard HEAD@{N}.
- Добавь git config rebase.autostash true, оставь незакоммиченную правку в файле и запусти любой rebase - убедись, что он прошёл, а правка вернулась.
- В чём разница между squash и fixup при объединении коммитов, и когда выбирать каждый?
- Ты сделал три правки по ревью к разным коммитам. Какие команды дадут схлопнуть их в нужные цели одним движением?
- Rebase завершился, история выглядит сломанной. Два разных способа вернуть исходное состояние - какие и чем отличаются?
- Зачем нужен --exec при rebase и какое практическое требование к истории он помогает выдержать?
Интерактивный rebase - это редактор твоей истории. pick/reword/edit/squash/fixup/drop/break/exec покрывают всё: объединить, переименовать, разбить, переставить, удалить, остановиться, прогнать тесты. Связка git commit --fixup плюс git rebase --autosquash превращает ответы на ревью в чистую историю без ручной возни. Включи rebase.autosquash и rebase.autostash один раз - и забудь про грабли. А reflog с ORIG_HEAD делают весь процесс обратимым, поэтому не бойся переписывать - бойся только пушить переписанное в общую ветку без --force-with-lease.