Git merge vs rebase: kdy použít který a jak si nerozbít historii
Srovnání git merge a git rebase na konkrétních příkladech. Fast-forward, merge commit, interaktivní rebase, zlaté pravidlo a nastavení pull.rebase.
Merge i rebase řeší stejný problém: jak dostat změny z jedné větve do druhé. Liší se tím, jak potom vypadá historie. Když pochopíte rozdíl, přestanete se rebase bát a zároveň budete vědět, kdy ho nepoužívat.
Výchozí situace
Máte větev feature odbočenou z main. Mezitím někdo do main přidal commity:
A---B---C feature
/
D---E---F---G main
Merge: spojení dvou linií
git switch feature
git merge main
Git vytvoří nový merge commit M, který má dva rodiče:
A---B---C---M feature
/ /
D---E---F-------G main
Výhody: historie je pravdivá – přesně vidíte, kdy se co spojilo. Nic se nepřepisuje, je to bezpečné i na sdílených větvích.
Nevýhody: u aktivních projektů vzniká spousta merge commitů a graf připomíná kolejiště.
Fast-forward
Pokud main mezitím nic nepřibylo, merge jen posune ukazatel – žádný merge commit nevznikne. Chcete-li merge commit vynutit (např. aby bylo vidět, že šlo o feature větev), použijte git merge --no-ff feature. Naopak git merge --ff-only selže, pokud fast-forward není možný – užitečné pro skripty.
Rebase: přesazení commitů
git switch feature
git rebase main
Git vezme commity A, B, C a znovu je aplikuje na konec main:
A'--B'--C' feature
/
D---E---F---G main
Vzniknou nové commity A’, B’, C’ s jiným hashem. Historie je lineární, jako by feature vznikla až teď.
Výhody: čistá, čitelná historie, git log a git bisect se používají snáz.
Nevýhody: přepisuje historii. Pokud už někdo na staré commity A, B, C navázal, způsobíte mu problémy.
Zlaté pravidlo rebase
Nerebasujte commity, které už jsou sdílené s ostatními (pushnuté do větve, na které pracuje i někdo jiný).
Rebase vlastní lokální větve před otevřením pull requestu je naprosto v pořádku. Po rebase už pushnuté větve musíte pushnout silou – používejte bezpečnější variantu:
git push --force-with-lease
Ta odmítne push, pokud mezitím na serveru přibyly cizí commity, které byste jinak smazali.
Interaktivní rebase: úklid před PR
Nejužitečnější podoba rebase je úklid vlastních commitů:
git rebase -i HEAD~4
Otevře se editor se seznamem:
pick 1a2b3c4 Přidat formulář
pick 5d6e7f8 oprava překlepu
pick 9a0b1c2 WIP
pick 3d4e5f6 Validace formuláře
Změnou slova na začátku řádku řeknete, co s commitem udělat:
squash/s– sloučit s předchozím a upravit zprávu,fixup/f– sloučit a zprávu zahodit,reword/r– jen přepsat zprávu,drop/d– commit vyhodit,- přeházením řádků změníte pořadí.
Šikovný trik: commit-opravu vytvořte rovnou jako fixup a nechte Git ji zařadit sám.
git commit --fixup 1a2b3c4
git rebase -i --autosquash HEAD~5
Pull: merge, nebo rebase?
git pull je fetch + merge. Pokud pracujete na stejné větvi s kolegou, vznikají zbytečné commity „Merge branch ‘main’ of github.com…“. Řešením je pull s rebase:
git pull --rebase
# nebo natrvalo
git config --global pull.rebase true
Novější verze Gitu (od 2.27) vás při nejednoznačném pullu samy upozorní, abyste si chování zvolili.
Konflikty v obou případech
Při merge řešíte konflikty jednou. Při rebase se commity aplikují postupně, takže konflikt může přijít u každého z nich:
# po vyřešení souborů
git add soubor.js
git rebase --continue
# nebo couvnout do stavu před rebase
git rebase --abort
Pokud stejný konflikt řešíte opakovaně, zapněte git config --global rerere.enabled true – Git si zapamatuje vaše řešení. Podrobně se konfliktům věnuje článek Řešení merge konfliktů.
Když se rebase pokazí
Nic se neztratí hned. git reflog ukáže, kde všude HEAD byl:
git reflog
git reset --hard HEAD@{5}
Doporučení pro praxi
| Situace | Volba |
|---|---|
Aktualizace vlastní lokální větve z main |
rebase |
| Úklid commitů před PR | rebase -i |
Spojení hotové feature do main |
merge (nebo squash merge v GitHubu) |
| Sdílená větev více lidí | jen merge |
| Pull na společné větvi | pull --rebase |
Na GitHubu si navíc v nastavení repozitáře můžete zvolit povolené způsoby slučování PR (merge commit, squash, rebase) a sjednotit tak historii pro celý tým.