$ sudo teach IT
Модуль 7 · Чистая история · Урок 7.2

Интерактивный rebase

Вы склеите и переименуете коммиты ветки командой git rebase -i, чтобы pull request читался как аккуратная история.

Теория + практика~20 минутНовичокrebase -isquash

Зачем причёсывать

Интерактивный rebase (git rebase -i) — это способ переписать коммиты своей ветки перед pull request: объединить несколько коммитов в один, переименовать, поменять местами или выбросить лишний. Вы превратите коммиты «wip» ветки feature/dark-theme в два понятных и сольёте тёмную тему в main.

Ветка уже стоит на свежем main. Допишем в неё ещё светлый цвет ссылок для тёмной темы (коммит «wip ссылки», он будет в практике), и вот что увидит ревьюер:

Терминал
$ git log --oneline main..feature/dark-theme
847ae4c wip ссылки
852019c Исправить ссылку на проекты
02bf7d8 поправил
1353688 wip ещё цвета
1114e95 wip тёмная тема

Пять коммитов, из которых понятен один. «Поправил» что? Почему тема размазана на три части? А исправление ссылки уже лежит в main — вы переносили его через cherry-pick. Читать такое больно, а через полгода и вам самим будет непонятно, что тут происходило.

Простыми словами: перед сдачей работы вы переписываете черновик набело. Мысли те же, но без зачёркиваний и «ой, забыл». История коммитов ветки — такой же черновик, пока он ваш и никто на него не опирается. Это всё то же золотое правило rebase: причёсываем только свою ветку.

git rebase -i

Команда git rebase -i main делает тот же rebase на main, но сначала показывает план: список коммитов, которые будут перенесены, и даёт его отредактировать. Флаг -i — от interactive, «интерактивный». Вместо main можно указать, сколько последних коммитов взять: git rebase -i HEAD~4.

Терминал
$ git rebase -i main
warning: skipped previously applied commit 852019c
hint: use --reapply-cherry-picks to include skipped commits
hint: Disable this message with "git config advice.skippedCherryPicks false"

Разбор по строкам

  • warning: skipped previously applied commit 852019c — «пропущен уже применённый коммит». Git заметил, что точно такое же изменение уже есть в main (это ваш cherry-pick), и сам убрал копию из плана.
  • hint: use --reapply-cherry-picks ... — как оставить такие коммиты, если они всё-таки нужны; нам не нужны.

Затем откроется редактор (тот, что вы настроили в core.editor) с планом. Ниже — его начало; дальше идёт справка, строки с # Git не выполняет:

Конфиг · план rebase в редакторе (git-rebase-todo)
pick 1114e95 wip тёмная тема
pick 1353688 wip ещё цвета
pick 02bf7d8 поправил
pick 847ae4c wip ссылки

# Rebase 7ab0847..847ae4c onto 7ab0847 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
...
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.

Разбор по строкам

  • pick 1114e95 wip тёмная тема — одна строка — один коммит: команда, хеш, сообщение. pick — «взять как есть».
  • # Rebase 7ab0847..847ae4c onto 7ab0847 (4 commands) — переносим коммиты после 7ab0847 (конец main) на него же: четыре команды.
  • These lines can be re-ordered; they are executed from top to bottom. — строки можно переставлять, выполняются сверху вниз.
  • If you remove a line here THAT COMMIT WILL BE LOST. — удалите строку — коммит пропадёт из ветки.

Частая путаница: сверху — самый старый коммит

git log показывает свежие коммиты сверху, а план rebase — наоборот, в порядке выполнения: первая строка — самый ранний коммит ветки.

Команды плана

Меняете слово pick в начале строки на другое — меняете судьбу коммита. Можно писать и одной буквой.

КомандаЧто делает с коммитом
pick, pОставить как есть
reword, rОставить изменения, но переписать сообщение
squash, sВлить в коммит строкой выше; сообщения обоих попадут в редактор, вы напишете общее
fixup, fВлить в коммит строкой выше, а своё сообщение выбросить
drop, dВыбросить коммит вместе с изменениями (то же, что удалить строку)
edit, eОстановиться на коммите, чтобы поправить его через commit --amend; новичку редко нужно

Склеиваем и переименовываем

План для нашей ветки: три коммита темы склеить в один «Добавить тёмную тему», коммит про ссылки переименовать. Исправьте первые слова строк и сохраните файл, затем закройте окно редактора:

Конфиг · план rebase после правки
pick 1114e95 wip тёмная тема
squash 1353688 wip ещё цвета
fixup 02bf7d8 поправил
reword 847ae4c wip ссылки

Разбор по строкам

  • pick 1114e95 — первый коммит остаётся основой.
  • squash 1353688 — вливается в него; его сообщение покажут в редакторе.
  • fixup 02bf7d8 — тоже вливается, сообщение «поправил» выбрасывается.
  • reword 847ae4c — остаётся отдельным коммитом с новым сообщением.

Git откроет редактор ещё раз — для общего сообщения склеенного коммита:

Конфиг · сообщение склеенного коммита в редакторе
# This is a combination of 3 commits.
# This is the 1st commit message:

wip тёмная тема

# This is the commit message #2:

wip ещё цвета

# The commit message #3 will be skipped:

# поправил
...

This is a combination of 3 commits — «это объединение трёх коммитов»; третье сообщение уже закомментировано (will be skipped), это fixup. Удалите все строки без # и напишите одну: Добавить тёмную тему. Сохраните и закройте. Третий раз редактор откроется для reword: замените wip ссылки на Сделать ссылки светлее в тёмной теме, сохраните, закройте.

Терминал
[detached HEAD 08ddbf0] Добавить тёмную тему
 Date: Mon Nov 16 10:00:00 2026 +0300
 1 file changed, 9 insertions(+)
[detached HEAD ebb2e91] Сделать ссылки светлее в тёмной теме
 Date: Tue Nov 17 10:00:00 2026 +0300
 1 file changed, 3 insertions(+)
Successfully rebased and updated refs/heads/feature/dark-theme.
$ git log --oneline main..feature/dark-theme
ebb2e91 Сделать ссылки светлее в тёмной теме
08ddbf0 Добавить тёмную тему

Разбор по строкам

  • [detached HEAD 08ddbf0] Добавить тёмную тему — склеенный коммит: 9 добавленных строк — вся тема целиком, опечатки в ней уже нет.
  • Date: Mon Nov 16 10:00:00 2026 +0300 — дата взята у первого коммита — когда работа началась.
  • git log --oneline main..feature/dark-theme — в ветке два понятных коммита вместо пяти.

Изменения в файлах не поменялись ни на символ: style.css такой же, как до rebase. Переписана только история. Подробно — в документации: git rebase (раздел про interactive mode).

commit --fixup и --autosquash

Заметили ошибку в своём старом коммите ветки? Исправьте файл и сделайте git commit --fixup <хеш> — Git создаст коммит с сообщением fixup! <сообщение того коммита>. Потом git rebase -i --autosquash main сам поставит его под нужный коммит с командой fixup, останется сохранить план.

Терминал
$ git commit --fixup 40f41b1
[feature/title-color f0a0b33] fixup! Добавить цвет заголовка
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git rebase -i --autosquash main
pick 40f41b1 Добавить цвет заголовка
fixup f0a0b33 fixup! Добавить цвет заголовка

Пример на отдельной учебной ветке: в коммите 40f41b1 задали цвет заголовка, потом поменяли оттенок. Две нижние строки — начало плана в редакторе: Git расставил всё сам.

Как пишут сообщения в англоязычных командах

Во многих открытых проектах приняты Conventional Commits: тип в начале сообщения — feat: add dark theme (новая возможность), fix: correct projects link (исправление), docs: update README (документация). По таким сообщениям программы сами собирают список изменений. В нашем проекте остаёмся с русским инфинитивом, но если проект, куда вы пишете, принял это соглашение, — следуйте ему.

Практика у себя

Начинаем там, где закончили: ветка feature/dark-theme перебазирована и отправлена, исправление ссылки уже в main.

Шаг 1. Обновите main и вернитесь в ветку:

Терминал
$ git switch main
$ git pull
$ git switch feature/dark-theme

Шаг 2. В блоке @media в конце style.css после правила .lead добавьте светлый цвет ссылок и закоммитьте как wip ссылки:

CSS · style.css (внутрь @media, после .lead)
  a {
    color: #60a5fa;
  }

Шаг 3. git log --oneline main..feature/dark-theme покажет пять коммитов, как в начале урока.

Шаг 4. Запустите git rebase -i main. В плане четыре строки (исправление ссылки Git пропустил сам). Поставьте pick, squash, fixup, reword, как в разборе, сохраните и закройте. В следующих двух окнах напишите сообщения Добавить тёмную тему и Сделать ссылки светлее в тёмной теме.

Как сохранить и закрыть

VS Code: сохраните файл (Ctrl+S, на Mac — Cmd+S) и закройте вкладку — Git продолжит. nano: Ctrl+O, Enter, затем Ctrl+X. Если открылся Vim: нажмите Esc, наберите :wq и Enter.

Шаг 5. Проверьте результат. Конец style.css должен быть таким:

CSS · style.css (конец файла)
a {
  color: #2563eb;
}

@media (prefers-color-scheme: dark) {
  body {
    color: #e5e5e5;
    background: #18181b;
  }
  .lead {
    color: #a1a1aa;
  }
  a {
    color: #60a5fa;
  }
}

Шаг 6. Отправьте переписанную ветку:

Терминал
$ git push --force-with-lease
To https://github.com/alex-ivanov/portfolio.git
 + 852019c...ebb2e91 feature/dark-theme -> feature/dark-theme (forced update)

Шаг 7. На GitHub создайте pull request из feature/dark-theme в main с названием «Тёмная тема» (у нас это #9). На вкладке Commits — два аккуратных коммита. Слейте кнопкой Merge pull request, подтвердите Confirm merge и нажмите Delete branch.

Шаг 8. Заберите результат и удалите ветку у себя:

Терминал
$ git switch main
$ git pull
$ git branch -d feature/dark-theme
Deleted branch feature/dark-theme (was ebb2e91).

Шаг 9. Откройте сайт и переключите оформление системы на тёмное: фон станет почти чёрным, текст светлым, ссылки голубыми.

Проверьте себя

  • git log --oneline -4 в main: Merge pull request #9 from alex-ivanov/feature/dark-theme, Сделать ссылки светлее в тёмной теме, Добавить тёмную тему, Merge pull request #8 from alex-ivanov/fix/projects-link.
  • Ни одного коммита «wip» и «поправил» в git log --oneline не осталось.
  • git branch показывает только main.
  • В style.css слово background в тёмной теме написано без опечатки.

Частые ошибки

Отправили переписанную ветку обычным git push. Получили ! [rejected] ... (non-fast-forward) — это нормально после любого rebase. Нужен git push --force-with-lease, а не git pull и не --force.

Удалили строку из плана — пропал коммит. Удалённая строка работает как drop. Коммит не исчез насовсем, его помнит reflog. Вот он сразу после такого rebase:

Терминал
$ git reflog -8
ebb2e91 HEAD@{0}: rebase (finish): returning to refs/heads/feature/dark-theme
ebb2e91 HEAD@{1}: rebase (reword): Сделать ссылки светлее в тёмной теме
3bac671 HEAD@{2}: rebase (reword): wip ссылки
08ddbf0 HEAD@{3}: rebase (fixup): Добавить тёмную тему
1b182ea HEAD@{4}: rebase (squash): # This is a combination of 2 commits.
0ff1a05 HEAD@{5}: rebase (pick): wip тёмная тема
7ab0847 HEAD@{6}: rebase (start): checkout main
847ae4c HEAD@{7}: commit: wip ссылки

Строка сразу под rebase (start) — ветка до rebase (847ae4c). git reset --hard 847ae4c вернёт её, и можно начать заново. Перед этим проверьте, что git status чист: reset --hard стирает незакоммиченные правки.

Перепутали порядок: squash в первой строке. Git ответит error: cannot 'squash' without a previous commit — вливать не во что, ведь сверху самый старый коммит. Выполните git rebase --edit-todo, исправьте план и затем git rebase --continue. Или начните заново: git rebase --abort.

Причесали ветку, которую уже взял коллега. Это нарушение золотого правила: у него останутся старые коммиты. Причёсывайте до того, как другие начнут работать поверх вашей ветки.

Что важно запомнить

  • git rebase -i main открывает план: одна строка — один коммит, сверху самый старый.
  • pick — оставить, reword — переименовать, squash — влить с сообщением, fixup — влить без сообщения, drop или удалённая строка — выбросить.
  • Переписывается только история: файлы после rebase те же.
  • После переписывания отправленной ветки — git push --force-with-lease.
  • Потерянное после rebase ищите в git reflog, строку под rebase (start).

Тёмная тема в main, история читается. Чистая история нужна не для красоты: однажды что-то сломается, и по ней придётся искать, где и когда. Например, кто всё-таки сломал ссылку на проекты. Этим займёмся в следующем уроке.

На следующий урок