Новое в журнале. Маленькие pull request: почему их быстрее ревьюят и как резать задачу
Инструменты

Интерактивный rebase в Git: squash, fixup, reword и autosquash

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

26 мая 20269 мин чтенияПрактикам
Интерактивный rebase в Git: squash, fixup, reword и autosquash
6 командсловарь интерактивного rebase
90 днейтипичный срок жизни записи в reflog

История коммитов, которая получается в процессе работы над задачей, почти никогда не годится для показа в основной ветке напрямую: там есть промежуточные «fix typo», забытые отладочные console.log и коммиты, которые логично объединить в один смысловой шаг. Интерактивный rebase в Git позволяет привести эту историю в порядок перед тем, как её увидят остальные, не теряя при этом ни одной строки кода. Разберём основные команды и то, как подстраховаться reflog'ом, если перестановка пошла не так, как задумано.

Запуск интерактивного rebase и структура редактора

Команда git rebase -i HEAD~5 открывает в редакторе список последних пяти коммитов в порядке от старого к новому, и каждая строка начинается со слова pick. Это список инструкций, который Git выполнит после сохранения файла: для каждого коммита будет применена указанная команда. Замена слова pick на другую команду в начале строки и есть весь механизм интерактивного rebase, никакой магии за этим не стоит.

КомандаЧто делает
pickоставить коммит как есть
rewordоставить изменения, но переписать сообщение коммита
squashобъединить коммит с предыдущим, сообщения объединяются
fixupобъединить коммит с предыдущим, сообщение отбрасывается
dropудалить коммит вместе с его изменениями
editостановиться на этом коммите для ручной правки
Главная мысльИнтерактивный rebase не удаляет работу навсегда, он переставляет коммиты, а reflog помнит, где они были, даже после самой неудачной перестановки.

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 add
  • git rebase --abort полностью отменяет rebase и возвращает исходное состояние
  • git rebase --skip пропускает проблемный коммит целиком, редкий и рискованный случай

Главное правило: не делайте rebase для уже опубликованных коммитов

Edit: остановиться внутри rebase для ручной правки

Команда edit останавливает процесс на нужном коммите так же, как это происходит при конфликте, но без самого конфликта: разработчик получает возможность изменить содержимое коммита, разбить его на несколько более мелких через git reset HEAD~1 с последующими отдельными коммитами, либо добавить забытый файл. После нужных правок процесс продолжается привычной командой git rebase --continue. Это удобный способ разбить один слишком большой коммит на несколько логических шагов задним числом, если изначально не хватило дисциплины коммитить мелкими порциями.

Типичный рабочий цикл перед код-ревью

На практике интерактивный rebase чаще всего применяют не как разовую операцию, а как последний шаг перед тем, как ветка отправляется на ревью. Разработчик свободно коммитит в процессе работы, не задумываясь о чистоте истории, а в конце дня или перед созданием пул-реквеста запускает git rebase -i к точке, от которой ветка отошла от основной линии разработки, и приводит список коммитов к финальному виду.

  1. посмотреть список коммитов в ветке относительно основной: git log main..HEAD
  2. запустить git rebase -i с нужной точкой отсчёта
  3. объединить технические правки через fixup, оставить логические шаги через pick
  4. проверить итоговую историю через 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 начнётся с нуля, поэтому восстановить коммиты, которые существовали только в старой локальной копии и никогда не публиковались, уже не получится.

Код, который не стыдно передать

Начните с принципов, а дальше двигайтесь по рубрикам. Каждый разбор заканчивается чек-листом.

Случайный разбор