Интерактивный rebase: переписать серию коммитов

Рейтинг: 67.6% · 8 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

Интерактивный rebase: переписать серию коммитов

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов (вы здесь)
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Зачем переписывать историю перед PR

Открой свою фича-ветку и посмотри на лог честно. Скорее всего там лежит что-то вроде "добавил эндпоинт", потом "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
Это значит "дай мне переиграть последние 5 коммитов". Можно указать не количество, а базу: git rebase -i main переиграет всё, что есть в твоей ветке поверх main. Git собирает список этих коммитов и открывает редактор с планом - это и есть сердце интерактивного rebase:

Код: Выделить всё

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
Обрати внимание: коммиты идут сверху вниз от старого к новому - наоборот по сравнению с git log. Верхняя строка - самый старый коммит серии. Ты редактируешь этот текстовый файл: меняешь слово pick на нужную команду, переставляешь строки, удаляешь строки. Сохранил, закрыл редактор - Git проигрывает план сверху вниз. Разберём команды по делу.
  • pick - взять коммит как есть. Дефолт для каждой строки.
  • reword - взять коммит, но дать переписать его сообщение. Git остановится и откроет редактор сообщения.
  • edit - остановиться на этом коммите с уже применёнными изменениями, дать тебе поковыряться (доправить файлы, разбить, переразложить) и продолжить.
  • squash - влить этот коммит в предыдущий, объединив оба сообщения. Так делают squash коммиты git.
  • fixup - то же, что squash, но сообщение этого коммита выбрасывается, остаётся только сообщение предыдущего. Это git fixup в чистом виде.
  • drop - выкинуть коммит совсем. То же самое, что просто стереть строку.
  • break - искусственная остановка в середине плана, чтобы осмотреться и продолжить через git rebase --continue.
  • exec - выполнить произвольную shell-команду после применения коммита (например, прогнать тесты).
Объединить коммиты git: squash и fixup на практике

Самый частый сценарий - объединить коммиты git. Возьмём наш план и схлопнем три уборочных коммита в первый осмысленный:

Код: Выделить всё

pick a1b2c3d add user endpoint
fixup d4e5f6a fix validation
fixup 7890abc typo
pick fedcba9 add tests
pick 0011223 WIP review fixes
Здесь fix validation и typo вольются в add user endpoint, а их сообщения исчезнут. Если бы я хотел сохранить тексты и собрать из них одно объединённое сообщение - поставил бы squash вместо fixup, и Git открыл бы редактор, где лежат все сообщения подряд, чтобы я слепил из них одно. Правило простое: squash - когда сообщения вливаемых коммитов несут смысл, fixup - когда это мусор вроде "typo" и "fix".

После сохранения 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.
Было пять коммитов - стало три, и первый теперь несёт в себе все три уборочных правки под одним чистым сообщением. Проверь git log --oneline: история стала читаемой.

reword решает отдельную боль - переименовать коммит без касания его содержимого. Ставишь reword напротив строки, Git дойдёт до неё, применит изменения и откроет редактор сообщения:

Код: Выделить всё

pick a1b2c3d add user endpoint
reword fedcba9 add tests
Дошёл, переписал "add tests" в "test: cover user endpoint edge cases", сохранил - и хеш этого и всех последующих коммитов поменялся, а diff остался прежним. Кстати, в Git 2.54 (апрель 2026) появилась экспериментальная команда git history reword <commit>, которая делает то же самое точечно и сама ребейзит все локальные ветки, где этот коммит встречается. Но это пока экспериментальный путь - базовый reword внутри rebase -i остаётся рабочей лошадкой.

Разбить, переупорядочить и удалить коммит

Разбить один коммит на два - приём, который отличает новичка от инженера. Допустим, в один коммит ты по ошибке свалил и логику, и тесты, а хочешь развести их. Ставишь edit:

Код: Выделить всё

edit a1b2c3d add endpoint and tests
Git применяет коммит и останавливается. Теперь возвращаем изменения в рабочую копию, оставляя их вне индекса, и коммитим частями:

Код: Выделить всё

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 reset HEAD^ откатывает последний коммит, но оставляет его изменения в рабочем каталоге неиндексированными - ты как будто вернулся к моменту перед коммитом. Дальше добавляешь файлы по группам и делаешь два отдельных коммита. git rebase --continue доигрывает остаток плана. Был один коммит - стало два, и каждый про своё. Если файлы перемешаны внутри одного, спасает git add -p (поэтапное добавление кусков), но это уже тема отдельного разговора.

Переупорядочить - просто поменяй строки местами в плане. Хочешь, чтобы тесты шли до эндпоинта - переставь строки, Git проиграет в новом порядке. Осторожно: если коммиты трогают одни и те же строки, перестановка даст конфликт - это нормально, разрулишь по ходу. Удалить коммит - поставь drop или сотри строку целиком. Удалил случайный "WIP debug print" - и его как не было.

autosquash и git fixup: цикл ответа на ревью

Теперь самое вкусное - workflow, который превращает ответы на ревью в удовольствие. Вспомни урок 19: ревьюер оставил три замечания, ты их правишь. Плохой путь - три коммита "review fix 1/2/3" поверх. Хороший путь - autosquash с --fixup.

Ты правишь код по замечанию к коммиту a1b2c3d и коммитишь не обычно, а так:

Код: Выделить всё

git commit --fixup a1b2c3d
Git создаёт коммит с особым сообщением "fixup! add user endpoint" - префикс fixup! и оригинальный заголовок. Это маркер: "я отношусь вот к тому коммиту". Делаешь так для каждого замечания, привязывая фиксы к нужным коммитам. А когда пора чистить - запускаешь:

Код: Выделить всё

git rebase -i --autosquash main
И магия: Git сам находит каждый fixup! коммит, переставляет его прямо под его цель и заранее помечает его командой fixup. План открывается уже разложенным:

Код: Выделить всё

pick a1b2c3d add user endpoint
fixup 9a8b7c6 fixup! add user endpoint
pick fedcba9 add tests
fixup 1d2e3f4 fixup! add tests
Тебе остаётся только сохранить и закрыть. Есть и --squash для случая, когда хочешь дополнить сообщение цели, а не выбросить. Чтобы не писать --autosquash каждый раз, включи это глобально:

Код: Выделить всё

git config --global rebase.autosquash true
Совсем без открытия редактора - git rebase --autosquash main (без -i) проиграет план автоматически. Это и есть полноценный git fixup цикл: правишь точечно, потом одним движением вшиваешь правки в правильные коммиты. Ревьюеру при этом удобно смотреть отдельные fixup-коммиты до финального схлопывания, а в main уезжает чистая история.

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
Если на каком-то коммите тесты падают (exec вернул ненулевой код), rebase останавливается ровно там. Ты чинишь, делаешь git commit --amend, потом git rebase --continue. Так ловят "сломанные посередине" коммиты - те, что собирают финально, но не бьются на промежуточных шагах. Для проекта, где важен bisect (урок про поиск регрессий), каждый коммит обязан быть зелёным - exec это гарантирует.

Грабля, на которой все спотыкаются: rebase не запустится с грязным рабочим каталогом. Решение - autostash. Включи раз и навсегда:

Код: Выделить всё

git config --global rebase.autostash true
Теперь перед rebase Git сам спрячет незакоммиченные изменения в stash, проиграет rebase и развернёт их обратно. Если при возврате случится конфликт - изменения останутся в stash, не потеряются, достанешь руками.

Грабли, прерывание и 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".
Алгоритм: правишь файл, git add <файл>, git rebase --continue. Важно - на этом шаге не делай git commit руками, rebase сам закоммитит по --continue. Если совсем запутался - git rebase --abort откатит всё в исходное состояние, как будто rebase не начинался. Это твой парашют: пока не закончил, abort вернёт точку старта целиком.

А если уже закончил rebase, а потом понял, что испортил историю или "потерял" коммит? Старые коммиты живы в reflog. Перед началом rebase Git кладёт исходный HEAD в ORIG_HEAD - грубый откат назад одной командой:

Код: Выделить всё

git reset --hard ORIG_HEAD
Если ORIG_HEAD уже перезатёрт другой операцией, идёшь в reflog - это журнал всех движений 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
Видишь HEAD@{6} - состояние до начала всей возни. Возвращаешься: git reset --hard HEAD@{6}, и ветка снова указывает на старую серию. Никакой коммит, у которого есть запись в reflog, не "потерян" - он просто временно без ярлыка-ветки. Reflog по умолчанию держит записи 90 дней. Поэтому повторю первое правило: rebase безопасен ровно потому, что обратим через reflog и ORIG_HEAD - но только в локальной ветке. После rebase публиковать в общий remote нужно через git push --force-with-lease (а не --force) - он не затрёт чужие изменения, если кто-то успел запушить, в отличие от тупого --force.

Мини-лаба: почисти ветку руками

Повтори по шагам в тестовом репозитории, держа 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.
👍2 ❤️3 🔥 😄 🤔
Аватара пользователя
ZigMaster
Сообщения: 1
Зарегистрирован: 12 май 2026, 09:10

Re: Интерактивный rebase: переписать серию коммитов

Сообщение ZigMaster »

До этого урока я тупо коммитил review fix 1, fix 2, fix 3 поверх. --fixup плюс autosquash это просто другой уровень, спасибо. Включил rebase.autosquash глобально сразу.
👍 ❤️ 🔥 😄 🤔1
Аватара пользователя
arch_pro
Сообщения: 1
Зарегистрирован: 18 май 2026, 05:04

Re: Интерактивный rebase: переписать серию коммитов

Сообщение arch_pro »

Вопрос: а если я уже запушил ветку в свой форк на гитхаб и потом сделал rebase, force-with-lease безопасен если я работаю один в этой ветке? или всё равно лучше не трогать?
👍1 ❤️ 🔥 😄 🤔1
Ответить
← Предыдущая глава
git rebase: пересадка истории и золотое правило
Следующая глава →
git cherry-pick: перенос отдельных коммитов

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git commit: фиксация измененийgit tag: теги и релизыgit blame: кто изменил строкуПодпись коммитов в Git (SSH, GPG)git cherry-pick: перенос коммитовgit merge или rebase: что выбрать

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость