Za krásnout timeline v gitu

Práce s Gitem: Rebase vs. Merge a udržování čitelné historie

Git je mocný nástroj pro správu verzí, který na rozdíl od konkurenčních systémů jako Mercurial či Bazaar umožňuje přepisovat historii. Tato funkce je extrémně užitečná, ale také vyžaduje opatrnost – přirovnání k ostrému noži je zde zcela na místě.

Proč dbát na čitelnou historii commitů?

Pokud je pro vás práce s repozitářem checkout na konkrétní branch a pak už jen kód; bude to pro vás asi zbytečná práce navíc. Je třeba se naučit některé postupy, protože když to budete řešit postaru, tak umřete.

Upravit historii commitů tak, aby byla přehledná, je nejen estetickou záležitostí, ale je to hlavně praktické:

Při práci na featurách obvykle začnu s hrubým rozložením commitů, které posléze zpětně upravím, aby reflektovaly logickou posloupnost změn. Díky tomu se do repozitáře nedostanou zbytečné chyby, dočasné úpravy nebo hesla.

Chci v historii commitů zanést přidávání/odebírání/změny featur. To, že jsem si zrovna v 15:31 aktualizoval base od kolegů nikoho nezajímá.

S commity se dá manipulovat. Když na nějaké featuře pracuji delší dobu, vznikají commity s featurou nesouvisející, které vznikli uvědoměnním si nějakého nedostatku. Tyto commity nemusejí čekat, až konečně featuru dodělám. A rovnou je mohu mergnout či cherry-picknout vejš.

Rebase vs. Merge: Kdy použít který?

Při synchronizaci kódu mezi branchemi existují dvě hlavní možnosti: rebase a merge.

Rebase:

Merge:

Nejčastější problémy při použití rebase

Ztráta dat při force push

Pokud po rebase provedete git push --force, přepíšete historii na serveru. Pokud mezitím kolega pushnul změny, mohou se nenávratně ztratit. (Ještě se to dá někdy zachránít pomocí bitsec, ale potřeboval jsem to asi dvakrát v životě.)

Tip: Před push --force si stáhněte aktuální stav repozitáře pomocí git fetch.

Problémy při pullu forceované branche

Pokud se pokusím aktualizovat lokální branch z branche která byla rebasována a forceovaná na vzdáleném repozitáři, dojde ke vzniku nadbytečnému merge commitu.

Řešení: Použít git pull --rebase.

Zastarání feature branche a příiš mnoho konfliktů

Pokud jsem na nějakou branch dlouho nešáhl, mohlo mezi tím v rodičovské branchy přibýt příliš mnoho změn.

Nejdříve to zkusím podobrém:

git checkout <parent> && git pulreb git checkout <child> && git rebasef <parent>

A nebo se na to vykašlu. Dám git rebase --abort, a udělám to humpolácky:

Oni mi tam samozřejmě vzniknou podobné konflikty jako v předchozím případě. Jenže jich vznikne mnohem méně, a jsou srozumitelnější.

Feature branching workflow

Běžně používám feature branching, tedy každou featuru vyvíjím v samostatné branchi. Během práce pravidelně rebasuji proti parent branchi (např. master nebo v1.0):

git checkout v1.0 && git pulreb git checkout v1.0-query-parser && git rebasef v1.0

Když je feature hotová, sloučím ji merge committem:

git checkout v1.0 && git mergef v1.0-query-parser

Následně branch smažu:

git branch -d v1.0-query-parser

Zatímco u aktualizace feature branche chci, aby se mi všechny nově příchozí commity pěkně přeskládali před ty moje, v případě zařazení do hlavní branche chci, aby to bylo vidět, aby tam byly "kolejničky".

Zkratky, které užívám

Pro časté operace jsem si definoval aliasy:

Stažení a zaktualizování branche
pulreb = pull --rebase
Stažení a zaktualizování branche
pulreb = pull --rebase
Rebase proti zadané branchi
rebasef = rebase --rebase-merges
Merge s vytvořením kolejniček
mergef = merge --no-ff
Rebasnout všechny čekající "fixup" commity
rebase-auto = "rebase -i --autosquash"
Přidání staged změn do konkrétního commitu
ammendto = "!f() { git commit --fixup=$1 && git rebase-auto $1~1; }; f"

Závěr

Git je mocný nástroj, který při správném použití může usnadnit řêdu každodenních operací. Používání rebase a merge na správných místech pomáhá udržet historii čitelnou a usnadnit spolupráci v týmu. Klíčové je rozumět rizikům a vědět, kdy je který postup vhodný.

Také píšou: