Содержание
В каждой команде есть холивар: rebase или merge. Правда такая — у обоих свои сценарии, и выбор зависит не от вкуса, а от где находится ветка: личная или общая.
Короткий ответ — таблица
| Ситуация | Подход | Почему |
|---|---|---|
| Подтянуть свежий main в свою feature-ветку | git rebase main |
Линейная история, нет «merge bubble» |
| Слить готовую feature в main через PR | merge --no-ff |
Видно границы фичи в git log --graph |
| Feature-ветку разделяют двое | git merge main |
Rebase сломает чужие коммиты |
| Подготовить PR — много мелких WIP-коммитов | rebase -i (squash) |
Читаемая история в main |
| Public-ветка (release, master) отстала от хотфикса | merge |
Никогда не переписываем published-историю |
| Локальная ветка сломалась после rebase | git reflog + reset |
Reflog хранит всё на 90 дней |
Всё остальное — детали. Дальше разбираем каждое правило на примере.
Что вообще происходит под капотом
Merge создаёт новый «merge commit» с двумя родителями. История остаётся такой как была + узел сверху:
* abc1234 Merge branch 'feature' into main
|\
| * def5678 Add cart endpoint
| * 9876fed Refactor auth
* | 1111aaa Fix typo on main
|/
* 2222bbb Initial
Rebase «переносит» твои коммиты на новую базу — переписывает их с новыми хешами. История линейна:
* def5678' Add cart endpoint ← новый хеш!
* 9876fed' Refactor auth ← новый хеш!
* 1111aaa Fix typo on main
* 2222bbb Initial
Ключевое слово — переписывает. Старые коммиты def5678, 9876fed исчезают (они ещё в reflog 90 дней, но через log уже не видны). Если кто-то делал из них ветки или цитировал в PR — у того теперь dangling references.
Кейс 1: моя feature-ветка отстала от main
Самый частый случай. Ты неделю работаешь над feature/cart, в main за это время прилетело 30 коммитов. Хочешь подтянуть свежее.
Вариант А — merge (безопасно, но грязно):
git checkout feature/cart
git merge main
# конфликты разрешил, готово
История получает merge-узел и расходящийся граф. После 5 таких подтягиваний история выглядит как лапша.
Вариант Б — rebase (чисто, рекомендую если ветка только твоя):
git checkout feature/cart
git fetch origin
git rebase origin/main
# конфликты разрешил → git rebase --continue
git push --force-with-lease
Твои коммиты «приклеиваются» поверх свежего main. История линейная, как будто ты только что начал работу.
После rebase нужен force-push, потому что хеши коммитов поменялись. Используй --force-with-lease, а не --force — он откажется пушить если кто-то залил в твою ветку коммит, пока ты ребейзил.
Кейс 2: над веткой работает не один человек
feature/payments — общая, ты и коллега коммитите параллельно. Ты делаешь:
git rebase main
git push --force-with-lease
Что произошло у коллеги:
- Утром у него локальные коммиты
A,Bвfeature/payments - Ты переписал хеши, сделал force-push
- Коллега делает
git pull→ конфликт «refusing to merge unrelated histories» - Если он сделает
git pull --rebase, его коммитыAиBдублируются (теперь и в твоей переписанной истории, и в его локальной)
В итоге: либо коллега вручную чинит дубликаты, либо команда тратит час на «как нам это смержить».
Правило: на shared ветках только merge. Кто-то один может в конце перед мерджем в main сделать rebase -i для squash, но не во время разработки.
Кейс 3: подготовка PR через interactive rebase
У тебя ветка с 12 коммитами вида wip, fix typo, oops, actually fix it. Перед PR-review хочется привести в порядок.
git rebase -i origin/main
Откроется редактор:
pick a1b2c3d Add cart route
pick e4f5g6h wip
pick h7i8j9k fix typo
pick l1m2n3o Add cart tests
pick p4q5r6s oops
pick t7u8v9w actually fix tests
Заменяешь команды:
pick a1b2c3d Add cart route
fixup e4f5g6h wip # вмёрджит в предыдущий, выбросит сообщение
fixup h7i8j9k fix typo # тоже
pick l1m2n3o Add cart tests
fixup p4q5r6s oops
fixup t7u8v9w actually fix tests
Сохраняешь — Git склеивает. Получаешь 2 чистых коммита: «Add cart route» + «Add cart tests». PR-reviewer счастлив.
Ключевые команды:
| Команда | Что делает |
|---|---|
pick |
Оставить коммит как есть |
reword |
Оставить, но изменить commit message |
squash |
Слить с предыдущим, объединить сообщения |
fixup |
Слить с предыдущим, выбросить сообщение |
drop |
Удалить коммит совсем |
edit |
Остановиться на нём — можно изменить файлы и git commit --amend |
| Перетащить строки | Изменить порядок коммитов |
Кейс 4: запорол rebase, паника
Ты сделал git rebase -i, что-то пошло не так — оказалось 3 коммита просто исчезли. Что делать?
git reflog
Вывод:
abc1234 (HEAD) HEAD@{0}: rebase finished
def5678 HEAD@{1}: rebase: pick X
9876fed HEAD@{2}: rebase: pick Y
1111aaa HEAD@{3}: checkout: moving from feature to main
2222bbb HEAD@{4}: commit: Add Y
3333ccc HEAD@{5}: commit: Add X ← вот они, мои потерянные коммиты
4444ddd HEAD@{6}: commit: Add W
Откатываешься на момент до rebase:
git reset --hard 4444ddd
Всё на месте. Reflog хранит все HEAD-движения 90 дней по дефолту, потерять что-то почти невозможно.
Перед сложным rebase делай страховку: git tag backup-$(date +%s). Если ребейз всё сломает — git reset --hard backup-1715200000 вернёт всё ровно как было.
--force-with-lease vs --force
--force — слепо перезаписывает remote ветку. Если кто-то успел запушить туда — его коммиты пропадают.
--force-with-lease — пушит только если remote-ветка указывает на тот коммит, который ты ожидаешь (последний fetch). Если кто-то залил коммит после твоего fetch — push откажется. Безопасный force.
Поставь алиас глобально:
git config --global alias.fpush "push --force-with-lease"
Теперь привычка git fpush вместо git push -f спасает от подстав.
Git Flow vs trunk-based — где меньше rebase-конфликтов
В Git Flow (long-lived develop/feature ветки) feature живёт неделями, отстаёт от main на десятки коммитов. Rebase каждые 3 дня — норма.
В trunk-based (короткие feature, мерджишь в main за 1-2 дня) проблема почти не возникает: ветка живёт 1-2 дня, отставание минимальное, можно без rebase.
Если команда страдает от rebase-конфликтов — может, проблема не в Git, а в слишком длинных feature-branches. Сократи цикл — половина боли уйдёт.
Итого
- Чужая ветка → только merge. Rebase только в твоей личной.
--force-with-lease, никогда--force. Сделай алиас.rebase -iперед PR — ёмкий инструмент: squash WIP-коммитов, fixup опечаток, reorder.git reflogспасает почти всегда — 90 дней истории движений HEAD.- Если конфликты постоянные — ветки слишком долго живут, не Git виноват. Про базовую организацию веток — в статье о Git flow, а что не должно попадать в коммиты — в гайде по .gitignore.
Обсуждение
Пока нет комментариев. Будьте первым.