[[!meta title="Verzovací systém Git"]]
[[!toc levels=2]]
Cíl
===
Naučit se pracovat s verzovacím systémem [Git][git]. Jedná se o velmi
rozšířený verzovací systém používaný mnoha open source projekty.
Konkrétně nás bude zajímat:
* využití Gitu pro distribuovaný vývojový model, který je praktikován
mnoha open source projekty,
* efektivní prohledávání historie projektu, které je užitečné
například při rozhodování, jak správně vyřešit určitý konflikt.
[git]:http://git-scm.com/
Úvod
====
Git je velmi univerzální nástroj pro správu a synchronizaci dat
v souborech. Kromě verzování softwaru ho lidé používají
k [mnoha dalším činnostem][gitsurvey-usedfor]. Jedna z často
zmiňovaných nevýhod Gitu je, že oproti jiným verzovacím systémům je
těžší se ho naučit a používat. Možným důvodem je to, že git nabízí
větší funkcionalitu než většina ostatních systémů, která se ale
využije jen ve speciálních případech – například jen u extrémně
velkých projektů jako Linuxové jádro. Ať už to tak je, nebo ne, faktem
je, že Git se neustále vyvíjí a mnoho úsilí je věnováno právě zlepšení
uživatelské přivětivosti.
[gitsurvey-usedfor]:http://www.survs.com/app/7/wo/GffJpx48NlVaG9rZZGKsI0/0.0.0.7.7.3.3.1.1.5.1.1.1.9.1
Pro pochopení Gitu je důležité mít základní představu o tom, jak Git
pracuje s větvemi, což bylo popsáno v [[přednášce|prednasky/intro-to-git.pdf]]
na slidech "*Working on branches*" a "*Working with remotes*".
V tomto cvičení budeme opět pracovat s projektem
[Midnight commander][mc] z [[1. cvičení|1]]. Pokud s gitem začínáte,
doporučuji v průběhu jednotlivých kroků kontrolovat stav repozitáře
pomocí grafických nástrojů:
* Příkaz `gitk` zobrazuje graficky historii a dovoluje její
interaktivní procházení. Přijímá stejné volby jako `git log`, takže
v příkazech níže ho můžete použít i pro "vizualizaci" výstupu `git
log`.
* Příkaz `git gui` je grafický nástroj částečně nahrazující příkazy
`git status` a `git commit`. Pomocí menu je možné provádět i další
operace jako např. vytváření větví a práce se vzdálenými repozitáři.
* Dále existuje ještě spousta dalších [nástrojů][tools]. Já osobně používám
kromě výše zmíněných příkazů ještě `tig` a `qgit`.
Dále se vám určitě bude hodit dokumentace:
git help
git
--help
[tools]:http://git-scm.com/docs/git-tools
Postup
======
1. Základní nastavení. Aby vaše commity obsahovaly správné údaje o
vás, nastavte si jméno a email:
git config --global user.name "Your Name Comes Here"
git config --global user.email you@yourdomain.example.com
1. Předpokládáme, že máte zdrojové kódy midnight commanderu někde na
disku, takže se přesuňte do adresáře s nimi.
cd ~/mc
Lokální větve
-------------
2. Možná budete chtít zachovat vaší práci z prvního cvičení. Uložíme
jí do nové větve *cviceni1*. Nejprve se ale podíváme v jakém stavu
máme zdrojové kódy:
git status
Dostaneme něco jako:
# On branch master
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: po/az.po
# modified: po/be-tarask.po
# modified: po/be.po
# ...
# modified: src/cmddef.h
# modified: src/main.c
Vidíme, že pracujeme na větvi *master* a vzhledem k verzi uložené v
repozitáři máme několik změněných souborů.
[mc]:http://www.midnight-commander.org/
1. Midnight commander má bohužel jednu vlastnost, která může ztížit
vaše začátky s gitem. Při kompilaci dojde k automatickému
přegenerování některých souborů v repozitáři a git potom hlásí, že
jsme tyto soubory změnili a "otravuje" s nimi při každém commitu.
Naštěstí existuje možnost jak gitu říct, že nás změny v některých
souborech nezajímají:
git update-index --assume-unchanged $(git ls-files po m4)
Příkaz `git ls-files po m4` vypíše všechny soubory z adresářů *po*
a *m4*, které jsou spravované gitem a příkazem `git update-index
--assume-unchanged` pak gitu sdělíme, aby si do indexu poznamenal,
že má tyto soubory ignorovat.
> *Poznámka:* Možná víte o souborech *.gitignore* (viz `man
> gitignore`), které slouží k podobnému účelu. Ostatně midnight
> commander je také využívá, jak se můžete snadno přesvědčit:
>
> cat .gitignore
>
> Soubory *.gitignore* se ale vztahují pouze na soubory, které
> ještě nejsou součástí repozitáře, což není případ výše zmíněných
> souborů.
3. Vytvoříme větev *cviceni1* a přepneme se na ni:
git checkout -b cviceni1
Nyní už by měl `git status` ukazovat že pracujeme s větví
*cviceni1* a vidíme pouze námi modifikované soubory.
4. Nyní provedeme *commit*:
git commit -a -m 'Zmeny z prvniho cviceni'
Parametrem `-a` říkáme, že "commitujeme" vše (all) a `-m` udává
komentář ke commitu (message). Kdybychom `-m` vynechali, git spustí
editor a nechá nás napsat zprávu v něm.
> *Poznámka:* Pokud vám nevyhovuje výchozí editor (většinou `vi`),
> nastavte si, že chcete používat jiný editor (např. `pico`):
>
> git config --global core.editor pico
5. Nyní se přepneme zpět na větev *master*:
git checkout master
V adresáři se vám teď objeví verze bez vašich úprav z 1. cvičení.
6. Seznam větví v našem repozitáři zjistíme příkazem
git branch
Aktuální větev je označena hvězdičkou `*`.
Práce s více vzdálenými repozitáři
----------------------------------
Do teď jsme pracovali pouze s jedním vzdáleným repozitářem. Mezi velké
výhody gitu (a ostatních distribuovaných verzovacích systémů) patří
schopnost pracovat s více vzdálenými repozitáři.
1. Které vzdálené repozitáře máme nakonfigurované zjistíme pomocí
git remote -v
Vidíme, že máme nakonfigurovaný repozitář s názvem *origin* a jeho
URL.
1. Pokud chceme nějaký vzdálený repozitář používat často,
vyplatí se ho pojmenovat krátkým jménem (v příkladu níže *osp*),
abychom nemuseli pořád psát dlouhé URL:
git remote add osp ssh://git@rtime.felk.cvut.cz/osp/mc
V tomto repozitáři je uloženo zadání dnešní úlohy. Abyste se k němu
dostali, musí server znát váš veřenjný SSH klíč. Jak toho docílit
najdete na [[samostatné stránce|rtime-git-ssh-key]]. Přístup k
repozitářům na serveru rtime budete potřebovat i v písemce. **Proto
vám důrazně doporučujeme zprovoznit přístup už na tomto cvičení,
abyste při písemce neztráceli čas.**
2. Nyní můžeme stáhnout obsah právě přidaného repozitáře:
git fetch osp
V případě úspěchu bude výstup vypadat následovně:
remote: Counting objects: 17, done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 10 (delta 4), reused 7 (delta 1)
Unpacking objects: 100% (10/10), done.
From ssh://rtime.felk.cvut.cz/osp/mc
* [new branch] master -> osp/master
* [new branch] only-directories -> osp/only-directories
3. Příkaz nám vypíše, že v repozitáři byly dvě nové větve. Všechny
větve ze vzdálených repozitářů vypíšeme příkazem
git branch -r
Zjednodušeně řečeno, jediný rozdíl mezi lokální a vzdálenou větví
je v tom, že jméno vzdálené věteve má prefix `/`.
4. Nyní nás zajímá co je ve větvích, které jsme právě stáhli:
git log osp/only-directories ^master
git log master..osp/only-directories
Tyto dva příkazy jsou ekvivalentní a vypisují commity, které jsou
ve větvi *osp/only-directories* a zároveň nejsou (^) ve větvi *master*.
Chcete-li vidět i změny v kódu použijte jeden z následujících příkazů:
git log -p osp/only-directories ^master
gitk osp/only-directories ^master
6. Podobně můžeme postupovat i s původním repozitářem. Jméno *origin*
je výchozí a proto ho nemusíme zadávat:
git fetch
Co přibylo ve větvi *master* od prvního cvičení zjistíme příkazem:
git log master..origin/master
Slučování větví (merge)
-----------------------
Operace slučující dvě a více větví do jedné se nazývá *merge*. V Gitu
Můžou při slučování nastat tři situace:
* *Already up-to-date* je situace, kdy už je větev, kterou chceme
sloučit, dosažitelná z aktuální větve (už byla sloučena v
minulosti). Při sloučení nedojde k žádné změně.
* *Fast-forward* je opak předchozí situace, tj. aktuální větev je
dosažitelná ze slučované větve. To odpovídá situaci, kdy
aktualizujeme na novější verzi. Při sloučení je větev (tj. ukazatel
na poslední commit) posunuta na novější verzi.
* *True merge*. Pokud nenastane jedna z předchozích situací, jedná se
o skutečné sloučení. Výsledek je nějaká kombinace obou verzí.
Pokud došlo v případě *True merge* ke změně stejného místa v kódu v
obou větvích, dojde k tzv. *konfliktu*, který musí být vyřešen ručně.
1. Zkuste provést sloučení vaší větve *master* s *origin/master*
git merge origin/master
Výsledkem bude *Fast forward* a uvidíme jaké soubory byly změněny:
git merge origin/master
Updating 0ebd30c..a99dc51
Fast forward
configure.ac | 1 +
contrib/Makefile.am | 9 +-
...
Toto je velmi častá operace a proto lze oprace `fetch` a `merge`
nahradit jedním příkazem
git pull
2. Vždy, když člověk pracuje na nějaké netriviální změně, je užitečné,
založit si na to samostatnou větev. Dnešním úkolem bude sloučit
větev *osp/only-directories* s větví *master* a protože to není
triviální založte si na to novou větev:
git checkout -b homework
3. Pokud provedete sloučení
git merge osp/only-directories
Výsledek bude vypadat pravděpodobně takto:
Auto-merging doc/man/mc.1.in
Auto-merging src/filemanager/find.c
CONFLICT (content): Merge conflict in src/filemanager/find.c
Automatic merge failed; fix conflicts and then commit the result.
Vidíme, že se automaticky povedlo sloučit změny v souboru
`doc/mc.1.in`, ale při slučování změn ve `src/find.c` už takové
štěstí nemáme a výsledkem je konflikt.
Řešení konfliktů
----------------
Konflikt lze řešit několika způsoby (viz také `git merge --help`):
* Vzdáme to a vrátíme se k verzi před slučováním
git reset --hard
* Konflikt vyřešíme a oznámíme to gitu příkazy `git add` a `git
commit` (jak nám git napovídá v hláškách)
V průběhu řešení konfliktu je užitečné používat příkaz `git status`,
Abychom zjistili co je ještě potřeba vyřešit. V našem případě vypadá
Výstup zhruba takhle:
# On branch homework
# Changes to be committed:
#
# modified: doc/man/mc.1.in
#
# Unmerged paths:
# (use "git add/rm ..." as appropriate to mark resolution)
#
# both modified: src/filemanager/find.c
Konflikt lze řešit následujícími způsoby:
* V textovém editoru najdeme sekvence `<<<<<<<<`, `=========` a
`>>>>>>>>`, kterými jsou označené jednotlivé konfliktní oblasti.
Tato místa musíme opravit tak, aby dávala smysl a poté zmíněné
sekvence znaků smažeme.
* `git mergetool` je nástroj, který spouští grafický nástroj (např.
[kdiff3][kdiff3]), který vám se slučováním pomůže.
`kdiff3` vedle
sebe zobrazuje 3 různé verze projektu: poslední společná verze
(base), verzi z větve před slučováním (local) a verzi ze slučované
větve (remote) tj. té uvedené jako parametr v příkazu `git merge`.
Ve spodní části obrazovky je pak vidět výsledek slučování, který
můžeme měnit buď přímou editací a nebo výběrem jednotlivých verzí
pomocí tlačítek A, B a C. V tomto okně je potřeba zbavit se všech
řadek, které maji v levém sloupci `?` - tj. konfliktů.
* `gitk --merge` - zobrazí pouze commity, které modifikovaly
konfliktní soubory.
[kdiff3]:http://kdiff3.sourceforge.net/
Repozitář na repo.or.cz
---------------------------
K tomu, aby výsledky vaší práce na open source projektech byly snadno
Dostupné pro ostatní je užitečné založit si vlastní repozitář, odkud
si budou moct ostatní vaše změny stáhnout.
1. [Zaregistrujte se][reg] na [repo.or.cz][roc]. K repozitáři na
repo.or.cz se přistupuje protokolem SSH a autorizace se provádí na
základě veřejných klíčů.
1. Pokud žádný klíč nemáte, vytvořte si ho příkazem
ssh-keygen
Příkaz se vás zeptá kam klíč uložit. Výchozí volba vám zpočátku
bude stačit. Dále se zeptá na heslo (passphrase) k vašemu
privátnímu klíči. Pokud žádné heslo nezadáte, bude se moct
kdokoli, kdo se dostane k souboru s vaším klíčem (např.
administrátor školních serverů), autorizovat jako vy.
2. Pokud jste se rozhodli chránit klíč heslem, je otravné zadávat
heslo vždy, když klíč používáte. Naštěstí existuje program
`ssh-agent`, který uchovává odheslované klíče v paměti a
poskytuje je vždy, když je nějaký oprávněný program potřebuje.
Vy tak zadáte heslo jen jednou a to když předáváte klíč
ssh-agentovi příkazem
ssh-add
[reg]:http://repo.or.cz/reguser.cgi
[roc]:http://repo.or.cz/
2. Vytvoření repozitáře na repo.or.cz.
Můžete založit buď [nový projekt][new] a nebo udělat tzv.
[fork existujícího projektu][forkmc]. Pro oba typy projektů můžete
zvolit zda bude repozitář pouze automaticky aktualizovanou kopií
jiného repozitáře (*mirror mode*) a nebo zda-li bude možné do něj přímo
ukládat nové commity (*push mode*).
Pro účely tohoto cvičení si
[založte fork][forkmc] [Midnight commanderu][w/osp.git] v *Push módu*, který se
bude jmenovat podle vašeho loginu. Do tohoto repozitáře pak
nahrajete úkol z dnešního cvičení.
3. Do vašeho vzdáleného repozitáře můžete nahrát vaší větev některým z
těchto způsobů
git push ssh://@repo.or.cz/srv/git/midnight-commander/osp/.git homework
nebo
git remote add repo-or-cz ssh://@repo.or.cz/srv/git/midnight-commander/osp/.git
git push repo-or-cz homework
[new]:http://repo.or.cz/regproj.cgi
[forkmc]:http://repo.or.cz/regproj.cgi?fork=midnight-commander/osp.git
Zadání
======
Proveďte sloučení větve `ssh://git@rtime.felk.cvut.cz/osp/mc
only-directories` s aktuální vývojovou větví `origin/master`. Výsledek
uložte do [vámi vytvořeného repozitáře (forku) na repo.or.cz][forkmc]
do větve `homework`.
[w/osp.git]:http://repo.or.cz/w/midnight-commander/osp.git