Jasně, umím Git…

in code

…tak to pěkně kecáš! ;-) Tedy pokud jsi nepoužil Git ve větším týmu s několika aktivními větvemi.

Nejednou jsem slyšel (nebo jen viděl v CV) a určitě ještě hodně krát uslyším, jak někdo „umí Git“. Po otázkách typu „U jakého projektu jste Git používal?“, „Kolik lidí v týmu bylo?“ či „Kolik jste měli přibližně aktivních větví?“ je hned jasno.

Ale nikomu se nedivím. Než jsme u nás v týmu přešli na Git, používal jsem ho jen na své malé projekty, kam jsem přispíval na 99 % pouze já do jediné větve – master. Přečetl jsem si knihu Pro Git, vyzkoušel si téměř vše a občas z toho i něco málo udělal. Samozřejmě jsem se v té době hodnotil, jako že Git umím více než dobře. Po přechodu v našem týmu jsem přesto brzy udělal průšvih a museli jsme repositář opravovat.

Dnes už tedy vím, jak jsem byl mimo. Nechci tím říkat, že byste měli jít a smazat si Git z CVčka (budu ale rád za poznámku, kde a jak jste ho používali!), spíš vám chci pomoct do začátku s užitečnými tipy. Z knih se dočtete většinou teorii a jak vše funguje od A do Z. Ale bude vám chybět co kdy jak proč použít, což chci napravit následujícím seznamem.

1. Konfigurace

Začneme zlehka. S konfigurací. Přizpůsobíme si prostředí, aby maximálně vyhovovalo.

Barvičky

Nejvíc základní (tedy po nastavení user.name a user.email) jsou barvy. Zobrazit si status nebo diff obarveně rozhodně zvýší efektivitu.

git config --global color.ui yes

Prompt

Je docela šikovné si nakonfigurovat konzolový prompt, abyste vždy měli na očích, ve které větvi jste, a případně také v jakém stavu. Tím se eliminují chyby, kdy zapomenete, ve které větvi se nacházíte a provedete něco… špatného. Jako třeba pushnout vývojovou větev do produkční, protože přeci naposled jsem byl v produkční, sakra!

Osobně používám následující prompt:

[michal@dev] 12:34:56 789 ~/workspace/emptygit
**master**$

Vždy tedy vidím, ve které větvi jsem, a pokud mám Git ve stavu „nothing to commit“, pak název větve není tučný. Kód, které je potřeba vložit do .bashrc, mám na GitHubu ve svém msh (my or Michael's shell).

Pokud chcete zajít ještě dál, doporučuji oh-my- git, který zobrazí všechno možné.

Pushování pouze aktuální větve

Příkaz git push automaticky pushuje všechny větve, které jsou svázané s nějakou vzdálenou větví. Pokud máte commity ve dvou větvích a chcete pushnout jen jednu z nich, musíte specifikovat kam pushovat. Pak se ale může stát, že budete ve větvi new-cool-feature a napíšete ze zvyku git push origin maser. A jéje.

Proto je dobré si nastavit, že git push bude pushovat jen aktuální větev, nikoliv všechny.

git config --global push.default current

Kdybyste náhodou potřebovali poté pushovat všechny větve, najděte si to v manuálu. Tohle si nemusíte pamatovat, protože to stejně nebudete nikdy potřebovat. :-)

Merge changelogů

Jestliže Gitujete aplikaci s debianím balením, setkáte se s merge hellem, který nikdo nerad řeší. Sice téměř vždy není co řešit, ale stejně to nikoho nebaví. Naštěstí existuje speciální mód mergování právě pro tyto situace: http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob;f=lib/git-merge- changelog.c

2. Základní operace

Log

Klasický git log odvede svou práci, ale každému prostě sedne něco jiného. Naštěstí lze log konfigurovat. Osobně jsem si oblíbil tento:

git log --pretty=format:"%Cgreen%h%Creset %ad %C(cyan)%an%Creset - %s%C(red)%d%Creset" --graph --date=short

Pokud potřebujete vidět log ze všech větví, pomůže parametr --all. Užitečné, pokud se potřebujete podívat na detail, jak se mergovalo a jaké jsou ve všech větvích aktuální stavy.

Aliasy

Jelikož si ale předchozí příkaz nelze zapamatovat, a i kdyby, nikoho nebude bavit neustále vypisovat, je možné vytvořit aliasy. Aliasy lze udělat na všechno možné, uvedu jen příklad pro předchozí příkazy.

git config --global alias.l 'log --pretty=format:"%Cgreen%h%Creset %ad %C(cyan)%an%Creset - %s%C(red)%d%Creset" --graph --date=short'
git config --global alias.ll 'log --pretty=format:"%Cgreen%h%Creset %ad %C(cyan)%an%Creset - %s%C(red)%d%Creset" --graph --date=short --all'

Prvním příkazem jsem si vytvořil alias git l, což mi provede zmíněný log. Druhý příkaz vytvoří alias git ll, což je ten samý log přes všechny větve.

Tig

I když je Git na příkazové řádce fajnový a lze si různě nakonfigurovat, stále nastane situace, kdy je potřeba něco grafického. Tig je právě jedním takovým programem. Pro konzoli. Používám velice často.

Grafické nástroje

Jako skutečným grafickým nástrojem budiž gitk či gitg, ale s nástrojem tig není potřeba konzoli opouštět.

Oprava commitu

Může se stát, že do commitu zapomenete zahrnout nějaké nové soubory. Nebo ještě najdete jednu chybku a nechcete to ukazovat v logu. Nebo zapomene do zprávy napsat číslo ticketu. Nebo jste jednoduše perfekcionalista a chcete si ve zprávě opravit hrubku. Tak přesně na to všechno tu je parametr --amend. Díky tomuto parametru lze opravit poslední commit. ``

git commit --amend

Představte si to jako kdybyste udělali další nový commit, ale sloučil se s tím předchozím. Tedy cokoliv přidáte do stage, se přidá k předchozímu commitu. Tedy pro změnu pouze commit message musíte mít stage čistý, samozřejmě.

Ale pozor! Tímto vlastně starý commit smažete a přestane existovat. Za žádnou cenu nesmíte takovou změnu provést, pokud jste již poslední commit pushnuli. Tím byste kolegům repositář rozbili!

Přidání jen části souboru

Často se mi stává, že při programování featury najdu ve stejném souboru chybu. Nenechám ji tam samozřejmě ležet a opravím ji. Ale nechci ji pak commitnout spolu s novou featurou. Tedy potřebuji commitnout jeden soubor na dvě části. Je možnost nejprve něco odmazat, commitnout, a pak to zase vrátit. Proč to však dělat krkolomně, když mohu využít parametr -p (patch):

git add -p

Tak dostanu na výběr, co chci do stage přidat.

Proč bych něco takového chtěl dělat? Většinou to asi tolik nevadí, ale pokud ta chyba není nějaký překlep nebo drobnost, lepší je to rozdělit. Pak při zpětném dohledávání je mnohem jasnější, co se dělalo a proč. Pokud už jste to nechtíc commitli nebo jste prostě lenoši, je dobré alespoň uvést do commit message, co vše v tom commitu je.

3. Pokročilejší operace

Cherry-pick

Když si vzpomenu, kdy jsem o cherry-picku slyšel poprvé, vybavím si strach. Bál jsem se toho, co mi to provede. Moc jsem tomu nevěřil. Přitom na tom nic není…

Situace: dělali jste featuru a narazili jste na chybu. Tu jste (díky předešlému tipu) commitli zvlášť, ale do vývojové větve. Objeví se u vás produktový manažer a oznámí vám, že to je kritická chyba a musí jít ven co nejrychleji. Nejlépe ihned.

Máte na výběr: buď se přepnete do správné větve a kód tam upravíte ručně a znovu commitnete. Nebo se jednoduše přepnete do správně větve, zavoláte cherry-pick, což znovu aplikuje commit v aktuální větvi a jste hotovi.

git cherry-pick #commithash

Mimochodem lze cherry-picknout také více commitů najednou. Tedy není problém ani pokud situace nebude o malé chybě, ale o celé vetší feature o několika commitech.

Zpětné větvení

Představte si situaci: začnete novou featuru. Uděláte commit. Dva. Tři. Zjistíte, že to bude celé komplikovanější a nechcete ostatním rozbít kód. Jenže už jste začali ve sdílené větvi…

Možná vás teď překvapím. Sice se ta větev jmenuje tak, jako na serveru, ale už jen tím, že jste si ji stáhli k sobě, jste v podstatě začali novou. Proto se říká, že je Git decentrializován. U vás to je jiná větev a vy jen posíláte commity z této větve do té vzdálené, odkud si to všichni synchronizují. Proto se může stát chyba, že pushnete commity z vývojové větve do provozní, jak jsem popisoval v sekci „Pushování pouze lokální větve“. To je hodně důležité si uvědomit. Abych to ještě trochu zkomplikoval: větev je pouze ukazatel na commit.

Když si to uvědomíte, zjistíte, že vlastně Git odvětvil automaticky a situaci o dva odstavce výše lze řešit velice jednoduše. Nejprve vytvořím novou větev s novými commity a poté vrátím ukazatel u mé lokální větve o X commitů zpět.

git branch newfeature
git reset --hard HEAD~x

Kde místo x dosaďte počet commitů.

Stash

Řekněme, že jste procesní šampion ve větvení, ale stále nastávají situace, kdy máte rozdělanou práci a potřebujete na chvíli odskočit udělat něco jiného. Commitovat však ještě nechcete a clonovat si nový Git repozitář vás už také nebaví. Řešení je jednoduché: stash.

stash rozdělané změny jednoduše uloží bokem a vyčistí pracovní kopii. Tím si můžete odskočit udělat fix v jiné větvi a pak se zase vrátit k rozdělané práci příkazem stash pop.

git stash
git stash pop

Dokonce, pokud se po návratu rozhodnete spíše pokračovat v nové větvi, není problém… Tohle teda patří mezi věci, které použijete asi jednou za Uherský rok. :-)

git stash branch newfeature

Edit: Pozor – existuje také git stash drop, což vaši práci zahodí. Doporučuji neplést. Případně lze používat git stash apply, jak doporučuje topa v diskuzi pod článkem.

Submoduly

Hodně lidí na ně nadává, ale to spíš proto, že jim nerozumí. Osobně se mi líbí. Pro BOObook.cz používám například Bootstrap, Font Awesome, Closure Library a další. Mohu buď přijít na jejich stránky a stáhnout si kód. Nebo si dát do mého repositáře odkaz na repositáře těchto komponent na konkrétní commit. Což je co submoduly dělají – drží cestu ke Gitu a hash commitu.

Jakou to má výhodu? Jedna, že mohu jednoduše upgradovat. Nemusím znovu stahovat kód a doufat, že jsem stáhl vše, co je potřeba. Dokonce mohu upgradovat podstatně detailněji, mohu upgradovat na konkrétní commit. Klidně i downgradovat, když na to přijde. Druhá, že mi nebobtná můj projekt. Spoustu takových komponent není zrovna malých. A třetí, pokud uvidím problém či změnu, mohu se podívat na blame a zjistit kdy a proč to v tom submodulu vzniklo.

Proto mám submoduly rád. Je ale potřeba s nimi umět pracovat.

Ten, kdo spravuje submodule, ho musí přidat: git submodule add. Poté ostatní, když si aktualizují, si musí ten modul zinicializovat (protože si stáhnou jen odkaz; teď je potřeba vycheckoutovat submoduly): git submodule init.

Ten, kdo spravuje submodule, občas musí upgradovat. Jednoduše vleze do adresáře submodulu a checkoutne se na commit, který chce. Poté se vrátí o úroveň výše do svého repositáře a tam commitne jako jakýkoliv jiný soubor. Ostatní po aktualizace uvidí, že tam je změna a musí si stáhnout změny (zase se jim stáhl jen update ukazatele), takže musí zadat: git submodule update.

A to je celý zázrak. Ten, kdo nespravuje submodule, si stačí pamatovat pouze init při prvním setkání submodulu (většinou při klonování, nové za běh projektu moc nevznikají) a update po aktualizaci ukazatele.

Pak je tedy ještě jeden příkaz: git submodule sync. To zase musí udělat každý, pokud se změnila adresa submodulu. Buď to a nebo znovu vyklonovat.

4. Týmový duch

Tagování

Ať už v týmu či v projektu o jednom vývojáři, doporučuji tagovat. Tak se dá případně kdykoliv dohledat, co přesně za kód v dané verzi byl. Je pravda, že jsem takhle dohledával pro konkrétní verzi asi jen dvakrát, ale stejně je hezké vidět releasy i v Gitu a ruce přidáním tagu neupadnou. Při vydání verze se stejně musí upravit setup.py, debianí changelog, či cokoliv jiného. Stačí tuto změnu otagovat taky verzí. V Gitu se lze pak na tag dostat jednoduše pomocí git checkout tagname.

Merge vs. rebase

Především lidi po přechodu z SVN mají tendenci pushovat po každém commitu. Za prvé – nedělejte to. Za druhé to má neblahé důsledky, protože jak jsme si řekli, jen vyclonování způsobí de facto odvětvení. Pokud potom chcete pusnout a někdo to stihl před vámi, musíte pullnout změny a pushnout znovu. To vytváří nepřehledné „kolejničky“. Co to je?

Většinou takové drobné commitování je u maintenance při opravě bugů a opravdu není nutné takové kolejničky vytvářet. Lepší je pro to použít rebase. Rebase znamená, že vaše commity odebere, fast-forwardne na poslední commit, který ve vzdálené větvi je, a poté vaše commity znovu aplikuje.

Tím se stanou dvě věci – nevytvoří se kolejničky a vytvoří se nové commity. S novým hashem. Tedy je to podobné, jako jsme probírali u ammend, – nesmí se rebasovat pushnuté commity!

Defaultně tedy git pull dělá merge. Jednoduše lze však říct, aby se raději zvolila metoda rebase:

git pull --rebase

Způsob větvení

Mít větev master jako stabilní větev nebo jako vývojovou? Mít pro každý aktivní release jednu aktivní větev? Dělat větev pro každou featuru? Dělat větev dokonce pro každý malý bug? Které všechny větve mít na vzdáleném serveru? Jak to mergovat mezi sebou?

To jsou jsou asi nejdůležitější otázky u začínajícího projektu. Nedokážu vám říct správnou odpověď. Všechny jsou správné. Záleží jen na vás, vašem kolektivu a jak to zapadá do vašeho způsobu releasování. To je krása Gitu – používáte ho tak, jak vám vyhovuje.

A už dost! S tímhle si už vystačíte.

P.S.: Možná se vám bude hodit Git Extras. Šikovná zjednodušovátka. Různé statistiky či mazání vzdálených větví apod. levou zadní.



8 responses

Hezkej článek :), za poslední roky jsem neviděl CV, kde by si uchazeč nenapsal, že umí git a mohu spočítat skoro na prstech jedné ruky kolik lidí mi dokázalo osvětlit základní rozdíl mezi rebase a merge, natož mi popsat workflow pro složitější situace.

Nejde ani o to se naučit pár příkazů, základní kámen úrazu vidím v tom, že tohle celé mění způsob práce programátora a není snadné si to za pár dní osvojit.

nikdy bych nepoužil git stash pop po té co jsem to jednou omylem vyměnil za drop :-) doporučuju používat pouze apply (pop a drop) zní dost podobně

S gitem doporucuju pouzivat github flow. Dost to omezi reseni konfliktu a jako velky plus mate sireni znalosti o kodu do celeho teamu.

Napsal jsem podobný Git prompt, který ale nepoužívá PROMPT_COMMAND, takže by snad měl být o trochu rychlejší.

https://gist.github.com/Dundee/944044

topa: Dropnutý shash se dá ještě zachránit s pomocí git reflog.

@Tomas: Jasně, tohle si nikdo neosvojí tím, že si bude doma zkoušet něco ať už podle knihy, tutoriálu nebo mého blog postu. Můj cíl byl ukázat rozdíl mezi umět a umět a dát praktické rady, co se hodí, případně proč něco funguje tak jak funguje. Snad se povedlo. :-)

@topa: Dříve jsi pop používal často. :-) Pro tebe jsem dopsal do článku.

@mh: Stojím si za svém, že nemá smysl doporučovat. Každý projekt potřebuje něco jiného. IMO lepší doporučovat pro konkrétní situaci, ale to jsem rozebírat nechtěl, už jen proto, že mám zkušenosti jen se třemi. :-) Každopádně by mě zajímalo, jak pomáhá GitHub flow k šíření znalosti o kódu?

@Daniel Milde: Rychlost by měla být stejná. PROMPT_COMMAND se liší jen v tom, že to může být klasická funkce. A tím tedy čitelnější. Samotné příkazy se musí vykonat tak jako tak. :-)

@Michal Hořejšek: Teoreticky by to myslím o trochu rychlejší být mohlo, protože se tam pokaždé nepřenastavuje PS1, ale jen vyhodnotí obsah té proměnné, ale asi to nebude měřitelné. Každopádně mi to přišlo jako zajímavá demonstrace možností bashe :)

Tig jsem neznal a asi začnu používat, vypadá docela mocně.

git config --global color.* yes

u mě (verze 2.0.2) nefunguje. Chybová hláška:

error: invalid key: color.*

Jinak děkuji za užitečné tipy.

Aha, pardon, je to git config --global color.ui yes, viz http://git- scm.com/book/en/Customizing-Git-Git-...