Git: fixup!

Ukážeme si, jak na malinko hežčí historii v gitu :)

Tomuhle článku by měl ideálně předcházet článek o tom, jak si představuji "dokonalý" pullrequest, na ten bohužel teď není čas, ale pro představu by mohl stačit Understanding the GitHub Flow.

Když něco commituji, tak ideálně cílím na commity, které dávají jako celky smysl. Pokud z vás často padají věci typu "refaktoring", "typo", "fixed", "really fixed", "omg now it's really fixed", tak bych se nebál tvrdit, že to úplně ideálně neděláte. Taky se tím občas prohřeším, ale jak se říká v anonymních alkoholicích, prvním krokem je přiznat si, že máme problém.

git commit --amend

Otevřu pullrequest, něco nacommituju, dojde uřvanej Procházka, napíše mi že to mám blbě a pošle mi tam několik gifů. Nezbývá než sednout k IDE a vyřešit komentáře, kdo by se s ním hádal...

No ale chceme tu hezkou historii, že? Takže vyřeším komentáře a protože nechci psát commit "refaktoring" tak to amendnu do předchozího. Čímž jsem vyřešil problém s hezkou historii. Jenže představte si když máte 500+ řádků změn, vyřešíte komentáře, všimnete si něčeho dalšího a opravíte to taky...

A teď nastavávají dvě situace... člověk co má zkontrolovat, že jste vyřešili review, tak buď se zaměří jenom na ty sekce co komentoval poprvé a tedy nemá šanci si všimnou dalších nesouvisejících změn, které tak můžou projít přes review bež povšimnutí, a nebo musí projít úplně celý pullrequest znovu... To asi nebude ideální situace, že?

Závěr tedy je, že nechceme amendovat do pushnutých commitů (dokud nejsou pushnuté, tak to nevadí, protože na ně ještě nedělal nikdo review), když nám dělá na kód někdo review, protože mu přidáváme práci.

git rebase -i master

Další, lepší, řešení je commitovat hala bala a před mergem vždy rebasnout commity na master, vyhezkat commit message a posquashovat bordel. Samozřejmě nutnou podmínkou je, že na to před mergnem nezapomenete :)

git-fixup-history

Interaktivní rebase je strašně mocná věc. Ovšem nebudu zabíhat do detailů, protože předmětem tohohle článku není vysvětlit interaktivní rebase, to by bylo tak na pět článků.

Zkrátka, když je review hotovo, zkontrolováno a pullrequest čeká už jen na merge, tak větev rebasnu na master, s přepínačem -i jako interactive :)

git rebase -i master

Větev se mi rebasne a pak se mě git zeptá jak ty moje commity co proti masteru přibyly chci učesat. Otevře mi textovej editor se seznamem těch commitů a na začátku jsou magická klíčová slova.

pick cb422ff Add StockPosition to InventoryItem
pick ceada87 typo
pick 4fd776a refactoring
pick be2340e fixed bug
pick b7be27c cs
pick b4cf8de typo

Mimo jiné tady můžu buď použít squash nebo fixup. Protože všechny ty zprávy kromě první jsou bordel, použiju fixup, který stejně jako squash commity spojí, ale navíc commit message zahazuje. A protože jsem línej, tak píšu jenom f.

pick cb422ff Add StockPosition to InventoryItem
f ceada87 typo
f 4fd776a refactoring
f be2340e fixed bug
f b7be27c cs
f b4cf8de typo

"Uložím soubor", git spustí magii a v historii mi zůstane jenom jeden commit.

3ac0d76 Add StockPosition to InventoryItem

Ten forcepushnu do pullrequestu, počkám jestli si CI nebude stěžovat a můžeme mergovat.

git commit --fixup

Jenže když já jsem línej psát ty commit message! Popravdě, bylo mi to jedno, dokud mi kolega Láďa neukázal, že je nemusím psát, protože existuje --fixup.

Zkrátka, když commituju a vím že commituju věci relevantní k nějakému konkrétnímu commitu, tak použiju --fixup

# cb422ff Add StockPosition to InventoryItem
git commit --fixup=cb422ff

což mi vygeneruje commit se stejnou zprávou, jakou má commit na který dělám fixup a před něj dá magické klíčové slovo fixup!

cb422ff Add StockPosition to InventoryItem
ceada87 fixup! Add StockPosition to InventoryItem

Což je v důsledku víc psaní, než jenom git commit -m "typo", tak si uděláme alias :)

git config --global alias.cif = "!git commit --fixup=HEAD"

A teď už stačí jenom

git cif

Tolik ušetřených znaků! :) Ale to není všechno, teď můžu použít --autosquash

git rebase -i --autosquash master

Ten mi udělá to, že automaticky upraví ten soubor, který se otevře při interactive rebasu, na něco takového

pick cb422ff Add StockPosition to InventoryItem
fixup ceada87 fixup! Add StockPosition to InventoryItem
fixup 4fd776a fixup! fixup! Add StockPosition to InventoryItem
fixup be2340e fixup! fixup! fixup! Add StockPosition to InventoryItem
fixup b7be27c fixup! fixup! fixup! fixup! Add StockPosition to InventoryItem
fixup b4cf8de fixup! fixup! fixup! fixup! fixup! Add StockPosition to InventoryItem

Tady už teď nemusím vůbec nic upravovat, protože je to připravené tak jak jsem to chtěl a po uložení získávám krásné

3ac0d76 Add StockPosition to InventoryItem

Na což si samozřejmě taky uděláme alias

git config --global alias.rbi = "rebase -i --autosquash"

A už stačí jenom

git rbi master

Nejhezčí řešení

Nejspíš jste si taky všimli toho nevzhledného fixup! spamu... Ono to v důsledku vůbec ničemu nevadí, protože ty zprávy zahazujeme, ale co kdyby šlo i tohle vyřešit?

No.. abychom se toho zbavili, musíme upravit cif alias tak, aby nedělal --fixup=HEAD, ale místo HEAD tam dal nejnovější commit, který neobsahuje fixup!

Pomocí git-log --grep se sice dá filtrovat historie, ale neumí to negaci. Po chvilce googlení jsem našel celkem pěkné řešení, již klasicky, na StackOverflow.

#! /bin/bash

if (( $# < 1 )); then
  echo >&2 "Usage: $0 pattern [<since>..<until>]"
  exit 1
fi

pattern=$1
shift

git log --format=%H $@ |
  grep -v -f <(git log --format=%H "--grep=$pattern" $@) |
  git log -1 --format="%H" --stdin --no-walk

Maličko jsem script upravil, aby vypsal jenom hash nejnovějšího záznamu. Uložíme ho třeba jako ~/bin/git-log-vgrep-most-recent-commit, přidáme mu executable flag chmod +x ~/bin/git-* a upravíme cif alias na

git config --global alias.cif = "!git commit --fixup=$(git-log-vgrep-most-recent-commit 'fixup!')"

A teď při použití git cif vypadá historie takto

cb422ff Add StockPosition to InventoryItem
ceada87 fixup! Add StockPosition to InventoryItem
4fd776a fixup! Add StockPosition to InventoryItem
be2340e fixup! Add StockPosition to InventoryItem
b7be27c fixup! Add StockPosition to InventoryItem
b4cf8de fixup! Add StockPosition to InventoryItem

Pustím git rbi master, uložím a dostávám

3ac0d76 Add StockPosition to InventoryItem

Voňavější už to snad ani být nemůže :)

Mám tu chybu? Fix me

Autor:

comments powered by Disqus