Интерактивный rebase в Git: squash, fixup, reword и autosquash
Разбираем интерактивный rebase в Git: squash и fixup для объединения коммитов, reword для сообщений, autosquash для автоматизации и reflog как страховка, если перестановка пошла не так.

История коммитов, которая получается в процессе работы над задачей, почти никогда не годится для показа в основной ветке напрямую: там есть промежуточные «fix typo», забытые отладочные console.log и коммиты, которые логично объединить в один смысловой шаг. Интерактивный rebase в Git позволяет привести эту историю в порядок перед тем, как её увидят остальные, не теряя при этом ни одной строки кода. Разберём основные команды и то, как подстраховаться reflog'ом, если перестановка пошла не так, как задумано.
Запуск интерактивного rebase и структура редактора
Команда git rebase -i HEAD~5 открывает в редакторе список последних пяти коммитов в порядке от старого к новому, и каждая строка начинается со слова pick. Это список инструкций, который Git выполнит после сохранения файла: для каждого коммита будет применена указанная команда. Замена слова pick на другую команду в начале строки и есть весь механизм интерактивного rebase, никакой магии за этим не стоит.
| Команда | Что делает |
|---|---|
| pick | оставить коммит как есть |
| reword | оставить изменения, но переписать сообщение коммита |
| squash | объединить коммит с предыдущим, сообщения объединяются |
| fixup | объединить коммит с предыдущим, сообщение отбрасывается |
| drop | удалить коммит вместе с его изменениями |
| edit | остановиться на этом коммите для ручной правки |
Squash и fixup: разница на практике
Обе команды объединяют текущий коммит с предыдущим в списке, разница только в сообщении итогового коммита. squash открывает редактор и предлагает объединить оба сообщения, оставляя разработчику решить, что из этого важно сохранить. fixup делает то же самое молча, забирая только изменения кода и полностью отбрасывая сообщение второго коммита. На практике fixup удобнее для технических правок вроде «исправил опечатку» или «добавил забытый импорт», где само сообщение не несёт смысла, который стоит сохранять в истории.
pick a1b2c3d добавить форму регистрации
fixup e4f5g6h исправить опечатку в лейбле
fixup h7i8j9k забытый console.log
pick k0l1m2n добавить валидацию email
После такого списка в истории останутся два осмысленных коммита: «добавить форму регистрации» и «добавить валидацию email», а две технические правки растворятся внутри первого без следа в сообщениях.
Autosquash: не искать коммит для fixup вручную
Когда исправление относится к коммиту, сделанному давно, а не к последнему, искать его номер в списке и переставлять строки руками неудобно. Git умеет делать это автоматически через связку флага --fixup при коммите и опции --autosquash при rebase.
git commit --fixup a1b2c3d
git rebase -i --autosquash HEAD~8
Первая команда создаёт коммит с особым сообщением вида fixup! добавить форму регистрации, который Git связывает с коммитом a1b2c3d по содержимому исходного сообщения. Вторая команда при открытии списка сама находит такие помеченные коммиты, переставляет их сразу после целевого и подставляет команду fixup вместо pick. Разработчику остаётся только сохранить список без ручных правок, если он согласен с автоматической расстановкой.
git config --global rebase.autosquash true один раз, и флаг --autosquash при каждом rebase указывать не придётся, он станет поведением по умолчанию.Reword: исправить сообщение без изменения кода
Если сам код коммита в порядке, а сообщение неточное или с опечаткой, команда reword останавливает процесс именно на этом коммите и открывает редактор сообщения, не трогая содержимое изменений. Это отличается от git commit --amend, который работает только с самым последним коммитом: reword через интерактивный rebase добирается до любого коммита в выбранном диапазоне.
Если что-то пошло не так: reflog как страховка
Главный страх перед интерактивным rebase, особенно у тех, кто делает это впервые, - ощущение, что неудачная перестановка коммитов навсегда теряет работу. На практике Git почти никогда не удаляет данные сразу: каждое перемещение указателя HEAD, включая промежуточные шаги rebase, записывается в reflog, локальный журнал перемещений ссылок.
git reflog
# ... HEAD@{3}: rebase (start): checkout HEAD~5
git reset --hard HEAD@{3}
Команда git reflog показывает список последних состояний HEAD с метками времени и действий, включая момент непосредственно перед началом rebase. Найдя нужную строку, можно вернуть ветку в это состояние командой git reset --hard с соответствующим указателем, и вся история до rebase восстановится как будто перестановки не было. Это работает даже после прерванного или неудачно завершённого rebase, пока локальный репозиторий не был удалён.
Конфликты во время rebase: продолжить, не бросая работу
Если во время применения одного из коммитов возникает конфликт, Git останавливается на этом шаге и ждёт ручного разрешения, в точности как при обычном merge. После правки конфликтующих файлов нужно добавить их в индекс через git add и продолжить процесс командой git rebase --continue, а не создавать новый коммит самостоятельно. Если конфликт оказался слишком сложным или возник по ошибке, весь rebase можно прервать командой git rebase --abort, которая возвращает ветку в состояние до начала перестановки, без необходимости искать нужную запись в reflog.
git rebase --continueпосле разрешения конфликта и git addgit rebase --abortполностью отменяет rebase и возвращает исходное состояниеgit rebase --skipпропускает проблемный коммит целиком, редкий и рискованный случай
Главное правило: не делайте rebase для уже опубликованных коммитов
Edit: остановиться внутри rebase для ручной правки
Команда edit останавливает процесс на нужном коммите так же, как это происходит при конфликте, но без самого конфликта: разработчик получает возможность изменить содержимое коммита, разбить его на несколько более мелких через git reset HEAD~1 с последующими отдельными коммитами, либо добавить забытый файл. После нужных правок процесс продолжается привычной командой git rebase --continue. Это удобный способ разбить один слишком большой коммит на несколько логических шагов задним числом, если изначально не хватило дисциплины коммитить мелкими порциями.
Типичный рабочий цикл перед код-ревью
На практике интерактивный rebase чаще всего применяют не как разовую операцию, а как последний шаг перед тем, как ветка отправляется на ревью. Разработчик свободно коммитит в процессе работы, не задумываясь о чистоте истории, а в конце дня или перед созданием пул-реквеста запускает git rebase -i к точке, от которой ветка отошла от основной линии разработки, и приводит список коммитов к финальному виду.
- посмотреть список коммитов в ветке относительно основной: git log main..HEAD
- запустить git rebase -i с нужной точкой отсчёта
- объединить технические правки через fixup, оставить логические шаги через pick
- проверить итоговую историю через git log перед push
Такой цикл занимает пару минут, но делает ревью заметно быстрее: проверяющему не нужно продираться через десяток промежуточных коммитов, чтобы понять, что на самом деле изменилось в задаче.
Отдельно стоит упомянуть флаг --rebase-merges, который сохраняет структуру слияний внутри диапазона коммитов вместо того, чтобы разворачивать их в линейную последовательность. Это нужно редко, в основном когда ветка сама содержала промежуточные merge от другой ветки, и эту структуру важно сохранить при перестройке истории, а не свести всё к одной прямой линии коммитов.
Полезно также помнить, что интерактивный rebase работает не только с последними коммитами текущей ветки, но и с любым диапазоном, если указать его явно через хеш коммита вместо относительного HEAD~N. Это удобно, когда задача решалась не один день и нужно привести в порядок историю за всё время работы, а не только за последние несколько коммитов, которые помещаются в короткий относительный диапазон.
Интерактивный rebase переписывает хеши коммитов, даже если менялось только сообщение. Если ветка уже отправлена в общий репозиторий и кто-то другой от неё отвёл свою работу, переписанная история разойдётся с его локальной копией, и следующий push или pull потребует силового разрешения конфликта истории, которое легко сделать неправильно. Безопасная граница простая: приводить в порядок историю стоит до того, как ветка опубликована для совместной работы, либо явно договориться с командой о force push в ветку, которую кроме вас никто не трогает.
Частые вопросы
Можно ли изменить порядок коммитов, а не только объединить их?
Да, достаточно переставить строки в списке интерактивного rebase местами перед сохранением файла. Git применит коммиты в новом порядке строк. Стоит помнить, что при сильной перестановке возрастает риск конфликтов, если более поздний коммит логически зависел от изменений, которые теперь применяются после него.
Что безопаснее для публичной ветки: rebase или merge?
Для веток, которые уже видны другим участникам команды и от которых кто-то мог начать свою работу, безопаснее merge, он не переписывает существующие коммиты. Rebase лучше подходит для личной ветки с задачей до того, как она опубликована, именно там его применяют, чтобы привести историю в порядок перед код-ревью.
Что произойдёт с reflog, если удалить и заново клонировать репозиторий?
Reflog полностью локальный: он не передаётся через push, pull или clone и существует только в конкретной локальной копии репозитория на диске. После удаления папки .git или клонирования заново история reflog начнётся с нуля, поэтому восстановить коммиты, которые существовали только в старой локальной копии и никогда не публиковались, уже не получится.



