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

Git rebase

Вы перенесёте свою ветку на свежий main командой git rebase, решите конфликт по ходу и перенесёте одно исправление в другую ветку через cherry-pick.

Теория + практика~25 минутНовичокrebasecherry-pick

Что делает rebase

Git rebase (по-русски — перебазирование) — это команда, которая переносит коммиты вашей ветки на новый конец другой ветки, обычно main, как будто вы начали работу позже. Вы начнёте тёмную тему сайта в ветке feature/dark-theme, перенесёте её на обновлённый main, решите конфликт по ходу и узнаете золотое правило rebase.

Когда вы работали вдвоём, отставшую ветку обновляли слиянием main, и каждое обновление добавляло merge-коммит. Rebase даёт второй способ догнать main: история остаётся прямой линией.

Простыми словами

Вы пишете главу общей книги по черновику недельной давности, а соавтор за неделю дописал перед ней ещё одну. Можно приложить свою главу с пометкой «объединено» — это merge. А можно переписать её поверх свежего черновика, будто начали сегодня, — это rebase.

Схема · до rebase
          D---E---F  feature/dark-theme
         /
    A---B---G  main

Что на схеме: ветка отошла от B, в ней три коммита D, E, F, а в main тем временем появился коммит коллеги G.

Схема · после git rebase main
              D'--E'--F'  feature/dark-theme
             /
    A---B---G  main

Что на схеме: ветка начинается от G. D', E', F' — копии ваших коммитов: изменения те же, хеши новые (сменился родитель).

Официальный термин

База ветки (base) — коммит, от которого она отходит; re-base — «сменить базу». git rebase main откладывает коммиты вашей ветки, ставит её на последний коммит main и применяет изменения заново по одному. Сам main не меняется.

Частая путаница: rebase не «склеивает» ветки

После rebase ваши коммиты ещё не в main: ветка лишь стала его продолжением. В main они попадут, как и раньше, через pull request.

Коммиты только своей ветки показывает запись с двумя точками: git log --oneline main..feature/dark-theme — «что есть в feature/dark-theme, но нет в main».

Rebase на практике

В ветке feature/dark-theme три коммита с тёмной темой в style.css. Тем временем коллега влил в main pull request «Оформить ссылки»: он переписал правило a { color: #3b82f6; } в конце style.css, после которого вы дописали тему. Обновляем main и переносим ветку:

Терминал
$ git switch main
$ git pull
$ git switch feature/dark-theme
$ git rebase main
Auto-merging style.css
CONFLICT (content): Merge conflict in style.css
error: could not apply 778017a... wip тёмная тема
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config advice.mergeConflict false"
Could not apply 778017a... wip тёмная тема

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

  • CONFLICT (content): Merge conflict in style.css — обе стороны поменяли конец файла: коллега — правило ссылок, вы — дописали тему сразу после него.
  • error: could not apply 778017a... wip тёмная тема — «не могу применить коммит 778017a»: rebase остановился на первом из трёх коммитов.
  • hint: ... git rebase --continue — решите конфликт, отметьте файл через git add и продолжите.
  • hint: ... git rebase --skip — пропустить коммит (его изменения пропадут).
  • hint: ... git rebase --abort — отменить всё и вернуться к состоянию до rebase.

Решается такой конфликт так же, как конфликт слияния. В конце style.css маркеры:

CSS · style.css (конец файла с конфликтом)
<<<<<<< HEAD
a {
  color: #2563eb;
=======
a { color: #3b82f6; }
@media (prefers-color-scheme: dark) {
  body {
    color: #e5e5e5;
    backround: #18181b;
  }
>>>>>>> 778017a (wip тёмная тема)
}

Частая путаница: при rebase HEAD — это не ваша ветка

При слиянии сверху была ваша ветка. При rebase наоборот: сверху (HEAD) — main с новым правилом коллеги, снизу — ваш переносимый коммит.

Сверху берём новое правило ссылок, снизу — только блок @media: старую строку a { color: #3b82f6; } коллега уже заменил. Уберите её и маркеры (опечатку backround исправит ваш третий коммит):

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

@media (prefers-color-scheme: dark) {
  body {
    color: #e5e5e5;
    backround: #18181b;
  }
}

Отметьте конфликт решённым и продолжите. Git откроет редактор с сообщением коммита — сохраните его без изменений и закройте:

Терминал
$ git add style.css
$ git rebase --continue
[detached HEAD 1114e95] wip тёмная тема
 1 file changed, 6 insertions(+)
Successfully rebased and updated refs/heads/feature/dark-theme.

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

  • [detached HEAD 1114e95] wip тёмная тема — копия первого коммита с новым хешем; detached HEAD на время rebase — нормально.
  • Successfully rebased and updated refs/heads/feature/dark-theme. — остальные два коммита применились сами.
Терминал
$ git log --oneline -5
02bf7d8 поправил
1353688 wip ещё цвета
1114e95 wip тёмная тема
816852b Merge pull request #7 from alex-ivanov/feature/colleague-links
60a9824 Оформить ссылки

Ваши коммиты стоят над слиянием коллеги, без развилки и merge-коммита. Если конфликтуют несколько коммитов, цикл «решить — git add — git rebase --continue» повторяется.

Merge или rebase

Оба способа соединяют вашу работу со свежим main, по-разному обращаясь с историей.

mergerebase
ИсторияРазвилки и merge-коммиты, видно, как шла работаПрямая линия, будто работали по очереди
Ваши коммитыОстаются как были, хеши те жеЗаменяются копиями с новыми хешами
КонфликтыРешаются один раз, в merge-коммитеМогут встретиться на каждом переносимом коммите
Для веток, которые взяли другиеБезопасноОпасно: у других останутся старые коммиты
Когда братьВлить готовую ветку в main, обновить общую веткуДогнать main в своей ветке перед pull request

Золотое правило rebase

Не перебазируйте коммиты, которые уже забрали другие. Rebase заменяет коммиты копиями: если коллега скачал вашу ветку и закоммитил поверх, у него останутся старые оригиналы, и при следующем git pull появятся дубли и конфликты на ровном месте.

Проще говоря: перебазируйте только свою ветку, в которую никто, кроме вас, не коммитит. feature/dark-theme можно: другие её только читают в pull request. main — никогда; её бережёт и ruleset (пункт Block force pushes включён по умолчанию), а на ваши ветки он не действует.

Push после rebase

Ветка feature/dark-theme уже была на GitHub — вы отправляли её до rebase:

Терминал
$ git status
On branch feature/dark-theme
Your branch and 'origin/feature/dark-theme' have diverged,
and have 5 and 3 different commits each, respectively.
  (use "git pull" if you want to integrate the remote branch with yours)

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

  • have diverged — ваша ветка и её копия на GitHub разошлись.
  • have 5 and 3 different commits each — у вас 5 коммитов, которых нет на GitHub (три копии и два коммита коллеги из main), там — 3 старых оригинала.
  • (use "git pull" ...) — после rebase этому совету не следуйте: pull вольёт старые оригиналы обратно.

Обычный push не пройдёт:

Терминал
$ git push
To https://github.com/alex-ivanov/portfolio.git
 ! [rejected]        feature/dark-theme -> feature/dark-theme (non-fast-forward)
error: failed to push some refs to 'https://github.com/alex-ivanov/portfolio.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
...

GitHub отказывает: отправка выкинула бы три его коммита. А мы как раз хотим заменить их копиями — для этого есть принудительная отправка с проверкой:

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

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

  • + 011e648...02bf7d8 ... (forced update) — ветка на GitHub принудительно переставлена со старого коммита на новый.

--force-with-lease — «силой, но с проверкой»: Git заменит ветку на GitHub, только если она там такая же, какой вы видели её в последний раз. Если туда успел прийти чужой коммит, Git откажет:

Вывод
 ! [rejected]        feature/dark-theme -> feature/dark-theme (stale info)

stale info — «устаревшие сведения». Выполните git fetch, заберите чужой коммит к себе и только потом отправляйте.

git push --force без lease стирает чужую работу

В старых ответах советуют git push --force (-f). Он ничего не проверяет, и чужой коммит молча пропадёт. Пользуйтесь --force-with-lease и только для своей ветки pull request, никогда для main.

Подробно — в документации: git rebase, git push.

Cherry-pick — перенести один коммит

Проверяя тему в браузере, вы щёлкаете «Все мои проекты» и видите «страница не найдена»: в адресе project.html вместо projects.html. Вы чините это в своей ветке, но тема будет готова через пару дней, а в main исправление нужно сейчас.

Простыми словами: из черновиков переписать в чистовик одну готовую страницу. Официально — cherry-pick («собирать вишенки»): git cherry-pick <хеш> создаёт копию одного коммита поверх текущей ветки. Rebase, по сути, делает то же самое с каждым вашим коммитом по очереди.

Терминал
$ git log --oneline -1
852019c Исправить ссылку на проекты
$ git switch main
$ git switch -c fix/projects-link
$ git cherry-pick 852019c
[fix/projects-link 1bf90b5] Исправить ссылку на проекты
 Date: Mon Nov 16 15:00:00 2026 +0300
 1 file changed, 1 insertion(+), 1 deletion(-)

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

  • [fix/projects-link 1bf90b5] ... — копия в ветке fix/projects-link: хеш новый, сообщение прежнее.
  • Date: Mon Nov 16 15:00:00 2026 +0300 — дата автора взята из оригинала.

Дальше push, pull request, слияние — и исправление на сайте раньше темы. Кто сломал ссылку, выясним, когда научимся искать по истории.

git pull --rebase

git pull --rebase скачивает новые коммиты и ставит ваши неотправленные поверх них вместо merge-коммита. У вас в настройках pull.rebase false — для начала хороший выбор, а --rebase указывайте явно, когда нужно.

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

Работаем в ~/projects/portfolio. Сообщения первых коммитов плохие нарочно — причешем их в следующем уроке.

Шаг 1. Обновите main и создайте ветку:

Терминал
$ git switch main
$ git pull
$ git switch -c feature/dark-theme
Switched to a new branch 'feature/dark-theme'

Шаг 2. Допишите в конец style.css блок, который включается при тёмной теме в системе. Опечатку backround оставьте:

CSS · style.css (добавить в конец)
@media (prefers-color-scheme: dark) {
  body {
    color: #e5e5e5;
    backround: #18181b;
  }
}
Терминал
$ git add style.css
$ git commit -m "wip тёмная тема"

Шаг 3. Внутри @media после правила body добавьте цвет подзаголовка и закоммитьте как wip ещё цвета:

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

Шаг 4. Исправьте backround на background, закоммитьте как поправил и отправьте ветку: git push -u origin feature/dark-theme. Проверка:

Терминал
$ git log --oneline main..feature/dark-theme
011e648 поправил
fed9cf6 wip ещё цвета
778017a wip тёмная тема

Шаг 5. За коллегу. Во втором клоне сделайте изменение, которое уйдёт в main раньше вашего. Опечатку в адресе повторите нарочно — это учебная поломка:

Терминал
$ cd ~/projects/portfolio-colleague
$ git switch main
$ git pull
$ git switch -c feature/colleague-links

В конце style.css замените строку a { color: #3b82f6; } правилом ниже (синий темнее, запись в несколько строк, как у остальных правил), а в index.html замените строку со ссылкой <p><a href="projects.html">Все проекты</a></p> на <p><a href="project.html">Все мои проекты</a></p>.

CSS · style.css (вместо a { color: #3b82f6; })
a {
  color: #2563eb;
}

Закоммитьте оба файла как Оформить ссылки, выполните git push -u origin feature/colleague-links, создайте pull request и слейте его.

Шаг 6. В своей папке обновите main и перебазируйте ветку:

Терминал
$ cd ~/projects/portfolio
$ git switch main
$ git pull
$ git switch feature/dark-theme
$ git rebase main
...
CONFLICT (content): Merge conflict in style.css
error: could not apply 778017a... wip тёмная тема

Решите конфликт, как в разборе выше, затем git add style.css и git rebase --continue до строки Successfully rebased.

Шаг 7. Выполните git push --force-with-lease — в выводе (forced update).

Шаг 8. Откройте index.html в браузере и щёлкните «Все мои проекты» — страница не найдена. Исправьте project.html на projects.html и закоммитьте:

Терминал
$ git add index.html
$ git commit -m "Исправить ссылку на проекты"
[feature/dark-theme 852019c] Исправить ссылку на проекты
 1 file changed, 1 insertion(+), 1 deletion(-)

Шаг 9. Перенесите исправление в отдельную ветку (хеш — свой из шага 8):

Терминал
$ git switch main
$ git switch -c fix/projects-link
$ git cherry-pick 852019c
$ git push -u origin fix/projects-link

Создайте pull request из fix/projects-link (у нас это #8), слейте его, затем у себя:

Терминал
$ git switch main
$ git pull
$ git branch -d fix/projects-link
Deleted branch fix/projects-link (was 1bf90b5).
$ git switch feature/dark-theme
$ git push

Последний push обычный: история не переписывалась.

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

  • git log --oneline main..feature/dark-theme показывает четыре коммита: Исправить ссылку на проекты, поправил, wip ещё цвета, wip тёмная тема.
  • В git log --oneline -3 main сверху — Merge pull request #8 from alex-ivanov/fix/projects-link.
  • В style.css ветки нет маркеров конфликта: правило a, затем блок @media.
  • git status в ветке пишет Your branch is up to date with 'origin/feature/dark-theme'.
  • На сайте GitHub Pages ссылка «Все мои проекты» снова работает.

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

Перебазировали общую ветку (main или ветку, где коммитит коллега). Не отправили — верните её через reflog (ниже); отправили — договаривайтесь с командой.

Rebase «потерял» коммиты: удалили при конфликте не ту часть или нажали --skip. git reflog помнит, где ветка была до rebase:

Терминал
$ git reflog -6
02bf7d8 HEAD@{0}: rebase (finish): returning to refs/heads/feature/dark-theme
02bf7d8 HEAD@{1}: rebase (pick): поправил
1353688 HEAD@{2}: rebase (pick): wip ещё цвета
1114e95 HEAD@{3}: rebase (continue): wip тёмная тема
816852b HEAD@{4}: rebase (start): checkout main
011e648 HEAD@{5}: checkout: moving from main to feature/dark-theme

Строка под rebase (start) — состояние до rebase (011e648); git reset --hard 011e648 вернёт ветку туда.

reset --hard стирает незакоммиченное

Перед git reset --hard убедитесь, что git status чист: незакоммиченные правки пропадут безвозвратно.

git rebase --continue отвечает You must edit all merge conflicts and then mark them as resolved using git add. Файл поправлен, но не добавлен: git add style.css.

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

  • git rebase main переносит коммиты вашей ветки на конец main: изменения те же, хеши новые, история прямая.
  • Конфликт при rebase: решить, git add, git rebase --continue; передумали — git rebase --abort.
  • Золотое правило: перебазируйте только свою ветку, которую не взяли другие.
  • После rebase отправленной ветки — git push --force-with-lease, не git pull и не --force.
  • git cherry-pick <хеш> копирует один коммит в текущую ветку.

Ветка догнала main, но её коммиты — «wip» и «поправил» — ревьюеру ничего не скажут. В следующем уроке интерактивный rebase склеит и переименует их перед pull request.

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