Zvládli-li jste základní nastavení a příkazy z úvodní kapitoly o Gitu, umíte si sami spravovat lokální práci v gitovském repozitáři a případně ji i synchronizovat na vzdálený server. Nicméně Git by nebyl distribuovaným nástrojem pro správu verzí, kdyby toho neuměl MNOHEM víc. Především pak:
Kromě toho dost pravděpodobně budete časem potřebovat milníky ve vývoji také taggovat, aneb označit vhodným jménem (typicky v1.0 a podobně), a možná ještě něco dalšího.
V Gitu jsou jednotlivé komity ukládány jako celkový aktuální binární obraz pracovního adresáře (tzv. snapshoty; nebo spíše ukazatele na tyto snapshoty). Větve (branches) pak nejsou nic jiného než ukazatel na vybraný komit, čili vlastně jenom SHA-identifikátor příslušného ukazatele.
Jelikož každý komit si musí pamatovat, z jakých rodičů byl vytvořen – typicky z jednoho, totiž předchozího stavu repozitáře, ale může i z více, právě když spojujete (mergujete) z více větví najednou – je pro Git snadné na požádání zrekonstruovat stav repozitáře ve vybraném okamžiku.
Speciální proměnná HEAD pak ukazuje na aktuálně vybranou pracovní větev (proto její častý výskyt v mnoha gitích příkazech).
Síla Gitu se pak ukazuje ve chvílích, kdy potřebujete práci z vedlejších větví buď zahodit nebo naopak promítnout do větve jiné.
Bez dalších parametrů vypíše příkaz git branch seznam všech v projektu dostupných větví:
git init
), takže ji najdete skoro všude, protože ji málokdy někdo přejmenuje.
Příkaz git branch VĚTEV pak vytvoří novou větev udaného jména, ale nepřepne na ni:
To znamená, že HEAD (označeno hvězdičkou *
u jména větve) stále míří na původní větev a jakékoliv další změny v repozitáři se budou odehrávat stále v ní!
Chcete-li začít pracovat v jiné větvi (tedy zaměnit pozici HEADu), musíte se na ni přepnout příkazem git checkout VĚTEV:
Jakékoliv změny, které nyní provedete v repozitáři, se budou zaznamenávat do této nové větve. Což znamená, že od této chvíle dál můžete provádět změny v obou větvích nezávisle na sobě (stačí se přepnout na druhou větev, něco v ní nakomitovat, přepnout se zpět a nakomitovat prozměnu něco jiného zde) a jak soubory, tak adresáře mohou v každé z nich vypadat úplně jinak!
PS: Je možné vytvořit novou větev a rovnou se na ni přepnout pomocí příkazu git checkout -b VĚTEV
.
Větve by vám samozřejmě nebyly k ničemu, kdybyste práci z nich nemohli (relativně) snadno promítat do jiných větví. A přesně k tomu slouží příkaz git merge VĚTEV. Je třeba si zapamatovat jedinou věc:
Git provede (respektive pokusí se provést, může samozřejmě dojít ke konfliktům) merge z udané větve do větve aktuální.
Je tedy třeba se nejdříve přepnout na větev (tj. změnit pozici HEADu), do níž chcete změny zanést:
Jak snadno se spojení větví podaří závisí samozřejmě na tom, jak moc se od sebe obě větve během vývoje vzdálily. V jednoduchých případech udělá Git všechnu práci za vás, ve složitějších dojde ke konfliktům a budete je muset ručně vyřešit.
git status
je pak nezbytný a konfliktní místa v nezmerdžovaných souborech budou příslušně vyznačena (hledejte znaky <<<<<<<
, =======
a >>>>>>>
.)
Když už máte víc větví, ve kterých zkoušíte nové věci (nebo opravujete chyby), tak by vás mohlo zajímat, které z větví už jste promítli – případně naopak ještě nepromítli – typicky například zpátky do hlavní pracovní větve (master). Git na to obsahuje dva přepínače u příkazu git branch
:
Nejčastější použití asi bude pro případ, že se nacházíte v hlavní pracovní větvi (master) a chcete do ní promítnout (merge) úpravy z větve jiné. Ale stejně dobře můžete stát v úplně jiné větvi, z níž jste časem oddělili ještě další větev. (Jsme v gitu, že ;-)
PS: Dodáním názvu větve za příkazy výše můžete dokonce specifikovat, proti jaké větvi vás stav merdžování zajímá.
Dříve nebo později začnete pracovat s repozitáři někde jinde (ať už na nějakém centrálním serveru nebo u někoho jiného na jeho počítači). A dříve nebo později se na některém z těchto míst objeví kromě hlavní pracovní větve (onen typický master) i větve jiné.
Ve výchozím nastavení bude příkaz git pull
aktualizovat kód pouze proti aktuální větvi, takže:
Netřeba říkat, že to samé se v obráceném směru týká i příkazu git push
.
Jak už víme, příkaz git branch
vypíše všechny vaše lokální vývojové větve. Nepřekvapivě tentýž příkaz, avšak s přepínačem --remote
(reps. -r
), vypíše prozměnu pouze větve na vzdáleném zdroji*.
PS: S přepínačem --all
(resp. -a
) obdržíte dohromady všechny jak lokální, tak externí větve.
Máte-li dostatečně nový git a není-li ve vašem lokálním repozitáři větev stejného jména, můžete se na vzdálenou větev přepnout i lokálně úplně stejně, jako kdyby to byla vaše lokální:
Pokud máte lokálně větev stejného jména, musíte tu vzdálenou namapovat na jméno lokálně jiné:
Máte-li nějaké větve již plné zuby (nebo jste v ní provedené změny již třeba zanesli do větve hlavní), je na čase se jí zbavit. K tomu nám dopomáhej příkaz git branch --delete VĚTEV
, resp. jeho zkrácená varianta git branch -d VĚTEV
:
PS: Máte-li na to práva, můžete smazat i vzdálenou větev na jiném zdroji pomocí například git push origin --delete VĚTEV
(s obvyklými poznámkami ke jménu origin a podobně).
Jelikož všemožných větví a zdrojů již začíná být trochu moc, nakresleme si pár obrázků „vo čem to všechno vlastně je“. Za příklad si vezmeme asi nejčastější případ práce v hlavní větvi cizího repozitáře (tedy spolupráci několika lidí nad někde – třeba GitHubu – centrálně umístěným repozitářem):
Naklonujmež si vzdálený repozitář. Jelikož už má nějakou historii, graf jeho stavu na našem počítači bude vypadat nějak takto:
Udělejmež na „masteru“ lokálně nějakou práci, tj. několikrát git add .; git commit:
Kolegové mezitím na server nahráli také něco nového:
Oj, oj – všichni jsme na stejné větvi, ale každý jsme tam od posledně změnili něco jiného! Co teď s tím? Zmerdžovat!
Bezva, všechny konflikty ze „slejvání“ vyřešeny! Teď to ještě dát vědět i kolegům na serveru:
git init --bare
. Repozitáře vyrobené bez --bare
slouží k práci a změny se do nich pullují.
PS 1: Operace git pull
je vlastně jenom zkratka (a nadstavba) nad provedením příkazů git fetch a git merge za sebou ručně.
PS 2: Nepřekvapí, že tohle všechno můžeme samozřejmě provádět i pro různé větve (jak místní, tak vzdálené). Příslušný graf operací (a SHA-štítků) pak bude jen náležitě „košilatější“ (za každou větev další odbočka ze stromečku).
Po předchozím slajdu a vysvětlení je asi celkem jasné, že když budeme práci z mnoha vedlejších větví promítat zpátky do větve hlavní, tak i při mazání odpovídajících popisek větví (protože nic jiného smazání větve nedělá!) zůstane náš strom vývoje kódu velmi rozvětvený a nepřehledný.
A právě pro tyto případy je k dispozici nástroj git rebase
, jehož úkolem je vzít (dosud neslitou) vedlejší větev a „nalepit“ ji za poslední komit větve hlavní (přičemž se cestou pokusí vyřešit konflikty při „slévání“ stejně jako git merge
). Čímž vlastně změní historii vývoje, která se nyní bude tvářit lineárně, ačkoli taková nebyla:
PS: Je doufám jasné, že měnit historii je lhaní. Nicméně pokud při lokálním vývoji vyrobíte úplnou stromečkovou spoušť, kolegové by vám asi moc nepoděkovali, kdybyste něco takového nacpali do chřtánu i jim. V takovém případě je odůvodněné (a dokonce i pochopitelně očekávané), že ten svůj „bordel“ nejdříve nějak pročistíte a teprve poté ho dáte k dispozici i ostatním. (K dispozici je kromě linearizace pomocí rebase i přepisování – včetně úplného odstranění – starších komitů pomocí rebase -i a dalších nástrojů.)
Existuje způsob práce s gitem, na který tak polovina vývojářů přísahá a ta druhá je kvůli tomu zase nesnáší ^_~
Jde o to, že původně se git prezentoval jako nástroj, kde se všechno dělá ve vlastních branchích a jen po dodělání se to „zmerdžuje“. Výsledkem je košilatý strom závislostí, který se v případě potřeby „zrybejsuje“. No a ta výše uvedená půlka vývojářů „rybejsuje“ automaticky skoro pokaždé, takže tam žádné větve ani nemá.
Jde totiž o to, že kromě obyčejného skládání komitů z různých větví (jako na předchozím slajdu) se dají komity také slučovat dohromady (podpříkaz squash pod rebase -i; zde někdo na serveru sloučil dohromady komity D a E do jednoho, ale až poté, co jste si vy mezitím udělali vlastní větev od komitu D):
No a tohle už v případě konfliktů snadno nezmerdžujete, protože to bude hlásit nejen konflikty mezi vaším e←f←g
a kódem na serveru (až po nové koncové F
), ale i kódem v D
a E
proti serveru, kde mezitím už je jenom jejich sloučení D+E
. A v takovouto chvíli vás právě zachrání jedině git pull --rebase
, které způsobí následující:
Cestou sice ještě budete asi muset vyřešit pár konfliktů, ale ty už budou všechny „klasické“, protože i vaše předchozí historie – vůči které jste vy sami nic neupravovali! – bude už odpovídat stavu na serveru.
git config --global pull.rebase true
.
Gitovský repozitář může referencovat různý počet cizích zdrojů (remotes):
git init
);
git clone
);
git remote add
).
Zatímco případu jedna se věnuje úvodní přednáška, na ty další se podíváme zde a nepřekvapivě vás budou zajímat především při práci na větším projektu s několika dalšími vývojáři.
Pokud jste váš repozitář naklonovali, bude obsahovat přinejmenším právě jeden zdroj, a to pod jménem origin
, což je výchozí pojmenování klonovaného zdroje:
Další zdroje můžete snadno přidat pomocí příkazu remote add JMÉNO ADRESA:
JMÉNO je tedy zkratka, pod jakou budete chtít příslušný zdroj referencovat.
Abyste se v dostupných zdrojích neztratili, příkaz remote show [ZDROJ] vám o nich podá nezbytné informace:
Zde je mimochodem vidět, že origin, z něhož byl tento repozitář naklonován, je ve stejném stavu, ale záložní zdroj zaloha je zjevně už o nějaký ten komit dále.
Je pochopitelné, že chcete-li promítnout svoje změny na zdroj (aneb „pushnout na vybraný remote“), musíte říct, na jaký:
V nejjednodušších případech tedy stačí pouhé git push
, které automaticky vezme vaši aktuální pracovní větev a promítne změny na její odpovídající zdroj.
Pokud máte na výběr z vícero větví a nemůžete zrovna vše nechat na výchozím výběru, musíte kromě zdroje uvést i větev, která se na něj má promítnout:
PS: git push origin HEAD
je zkratka, která promítne na zdroj aktuální větev pod stejným jménem, git push origin HEAD:master
pak aktuální větev promítne do větve master nezávisle na jejím lokálním jménu.
Opačným směrem – tedy od vybraného zdroje do vašeho lokálního repozitáře – je to podobné a vše obstará příkaz git pull ZDROJ [VĚTEV]
:
PS: git pull
je samozřejmě zkratka za stáhnutí změn ze zdroje pomocí git fetch
a jejich promítnutí do aktuálního stavu vašeho repozitáře pomocí git merge
.
Kromě výše uvedeného můžete zdroje také:
git remote rename StaréJménoZdroje NovéJménoZdroje
;
git remote remove ZDROJ
.
Zatím jsme si vesele pullovali a pushovali všechno všude, ale ve skutečnosti to není tak jednoduché (jsme přece v gitu, že ;-).
Asi původně základním modelem práce v gitu totiž je několik vývojářů, kteří spolupracují na stejném projektu, takže mají u sebe lokálně tzv. pracovní repozitář (vytvořený převážně příkazem git clone
), a v něm můžete pouze:
git pull
(resp. fetch & merge);
git push
, ale pouze do větví, které v cílovém repozitáři nejsou právě checkoutnuté (a ještě na to stejně musíte mít práva).
Pokud chcete, aby všichni mohli pushovat, jak se jim zlíbí, což je typický způsob práce, pokud cílový repozitář slouží jako centrální záloha pro všechny vývojáře, nesmí tento repozitář obsahovat žádný checkoutnutý kód. A takový repozitář se vyrábí příkazem git init --bare
.
.git
– místo toho je obsah tohoto adresáře přímo vlastním obsahem bare-repozitáře. Není divu, že nemůže být repozitářem pracovním ve výše uvedeném smyslu.
Při práci na komplexnějším projektu se asi celkem brzo dostanete do stavu, že budete chtít nějaké místo vývoje (tedy určitý komit) označit vhodným jménem pro snadnější pozdější vyhledávání a použití – takové označení verze určené k distribuci zákazníkům se přímo nabízí.
Git pochopitelně obsahuje pro toto podporu, a to jak ve formě „rychlých poznámkových značek“ (lightweight tags) pro rychlé označení nějakého místa na stomu vývoje, tak „plnokrevných orazítkování“ nějakého komitu (annotated tags), které obsahují identifikaci tagujícího, datum a zprávu a mohou být dokonce i GPG-podepsané.
Veškerou práci s tagováním zastává příkaz git tag
v nejrůznějších variantách:
git tag -a ZNAČKA -m "TAGOVACÍ ZPRÁVA"
– kompletní „těžkotonážní“ značka pro aktuální HEAD určená k distribuci do světa;
git tag ZNAČKA
– „pouze nálepka“ pro aktuální HEAD;
git tag ZNAČKA SHA-komitu
– nálepka pro libobolný komit v historii;
git tag -d ZNAČKA
– smazání příslušné značky;
Výpis značek v projektu zajišťuje příkaz git tag
bez parametrů, informaci ke konkrétní značce (nebo k aktuálnímu HEADu) pak podá příkaz git show
:
git tag
– výpis všech značek v projektu;
-l
umožňuje vypisovat značky filtrované pomocí „divokých“ znaků.
git show ZNAČKA
– ukáže kompletní popis daného komitu včetně všech informací ze značky.
Ve výchozím nastavení Git značky na jiné zdroje nepřenáší, ale při klonování nebo pullu je přinese.
Na vybraný zdroj je dostanete pomocí příkazu git push ZDROJ ZNAČKA
nebo všechny najednou pomocí git push ZDROJ --tags
.
Ke smazání vybrané značky na zdroji pak slouží příkaz git push ZDROJ --delete ZNAČKA
.
Stav projektu je sice možné checkoutnout pomocí značky (nebo též příslušného SHA-heše), jenže se tím dostanete do stavu tzv. detached HEAD, tj. HEAD
bude místo na pojmenovanou větev (typicky na konec masteru, pokud nejste v jiné vývojové větvi) ukazovat na nějaký historický komit (značka se ze svého místa pochopitelně nehne):
Což má ten efekt, že když na to zapomenete a provedete nějaké komity (zde C7 a C8)..
..a následně se vrátíte zpátky na master (nebo jinou pojmenovanou větev)..
..tak ty zůstanou doslova a dopísmene viset v luftě – nebudou patřit k žádné pojmenované větvi stromu vývoje a dosažitelné budou pouze pomocí svého SHA-heše!
Stane-li se vám tudíž, že budete potřebovat nějaké úpravy po značce (nebo klidně i prostě po nějakém jiném historickém komitu) provést, tak ji checkoutněte rovnou do nějaké nové větve (git checkout -b VĚTEV [ZNAČKA|KOMIT]
), abyste úpravy viděli v historii.
Jinou typickou situací jest, že máte něco rozpracovaného a někdo vám do toho zavolá, ať například co nejdřív aplikujete nějakou úpravu na vedlejší větvi. Poslední, co v takovou chvíli chcete udělat, je cpát do repozitáře vaši nedodělanou práci jen proto, abyste o ni nepřišli. A přesně od toho je příkaz git stash
.
git stash
prostě vezme aktuální rozpracovaný stav vašeho repozitáře – to znamená všechny modifikované (resp. přidané či smazané) soubory včetně změn už zanesených do indexu – a odloží ho na zásobník. Repozitář se tím vrátí do (čistého) stavu posledního komitu a vy máte ruce volné k jiné práci.
Když dokončíte vedlejší práci, vrátíte se ideálně do předchozího čistého komitu (tedy i ve stejné větvi) a pomocí git stash pop
si ze zásobníku přinesete zpátky rozpracovanou práci.
git merge
…
PS: Takto si odložit můžete více různých rozpracovaných stavů repozitáře. Jejich přehled vám podá git stash list
a vytáhnout jiný než poslední pak můžete pomocí git stash apply [--index] ID
. Vrácený stav se tímto ze zásobníku nemaže, k tomu slouží příkaz git stash drop ID
.
Ještě jeden pojem z gitího světa, než se s touto úvodní přednáškou rozloučíme – submoduly. V podstatě jde o repozitáře uvnitř repozitářů, které jsou obsluhovány nezávisle na hlavním repozitáři.
Používají se především tam, kde váš projekt závisí na nějakých externích knihovnách, které si tím pádem k sobě naklonujete a držíte je v odpovídajícím stavu (svými úpravami a občasným promítnutím změn z originálu).
Práci se submoduly má na starosti především příkaz git submodule
(a přepínač --submodule
u spousty dalších příkazů), ale zde ji nebume rozebírat, podívejte se například sem. Jen si ještě zapamatujte, že submoduly s sebou přinesou do repozitáře i konfigurační soubor .gitmodules (který především mapuje adresáře submodulů na adresy jejich zdrojů).
Git při obsluze repozitáře dokáže reagovat na některé akce (události) – jako například push nebo commit – a může před jejich vykonáním (případně po) provést další činnosti, v případě jejichž selhání nebude uvedená akce vůbec vykonána. Této funkcionalitě se říká git hooks. Mezi typická použití patří například:
Uvedené akce se zapnout pouhou přítomností odpovídajícím způsobem (podle typu události) pojmenovaného (spustitelného) souboru v adresáři .git/hooks. Jelikož v uvedených souborech může být vlastně cokoliv (například i pythoní skripty), jsou možnosti hooků značné.
Dál už nezbývá než studovat dokumentaci Gitu a sledovat debaty na internetu ^_^" Ale přeci jen ještě pár drobných rad: