Cíl

Naučit se pracovat s verzovacím systémem 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 a
  • řešení konfliktů.

Úvod

Git je poměrně 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 (viz Git User’s Survey 2012, otázka 8). 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.

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 na slidech “Working on branches” a “Working with remotes”.

V tomto cvičení budeme opět pracovat s projektem Midnight commander1. cvičení. 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í nebo pracovat se vzdálenými repozitáři.

  • Dále existuje ještě spousta dalších nástrojů. 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 <příkaz>
git <příkaz> --help

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
    
  2. 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

  1. 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
    # Changes not staged for commit:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    #    modified:   lib/keybind.h
    #    modified:   src/filemanager/midnight.c
    #
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #
    #    misc/ext.d/doc.sh
    #    ...
    no changes added to commit (use "git add" and/or "git commit -a")
    

    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ů.

  2. 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.

  3. Provedeme commit, t.j. uložíme všechny naše změny do lokálního repozitáře:

    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ř. nano):

    git config --global core.editor nano
    
  4. 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í.

  5. 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

Doposud 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.

  2. 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řejný SSH klíč. Jak toho docílit najdete na samostatné stránce. 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.

  3. 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: 34, done.
    remote: Compressing objects: 100% (17/17), done.
    remote: Total 20 (delta 17), reused 6 (delta 3)
    Unpacking objects: 100% (20/20), done.
    From ssh://rtime.felk.cvut.cz/osp/mc
     * [new branch]      master     -> osp/master
     * [new branch]      only-directories -> osp/only-directories
     * [new branch]      only-directories-old -> osp/only-directories-old
    
  4. Příkaz nám vypíše, že v repozitáři byly tři 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ětve má prefix <remote>/ (v našem případě origin/ nebo osp/).

  5. 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 (patch) použijte jeden z následujících příkazů:

    git log -p osp/only-directories ^master
    gitk osp/only-directories ^master
    

    Výše uvedené příkazy zobrazují změny provedené jednotlivými commity. Pokud nás zajímá celková změna bez rozdělení na jednotlivé committy, pomůže nám příkaz git diff:

    git diff master..osp/only-directories
    git diff master osp/only-directories
    

    První příkaz zobrazí změny, které obsahuje verze only-directories a nejsou ve větvi master. Druhý příkaz zobrazí kompletní rozdíl, tj. i změny, které jsou ve větvi master a ne v only-directories.

  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í své větve masterorigin/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 operace 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 merge --abort
    
  • 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 takto:

On branch homework
You have unmerged paths.
  (fix conflicts and run "git commit")

Changes to be committed:

    modified:   doc/man/mc.1.in

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:   src/filemanager/find.c

Konflikt lze řešit následujícími způsoby:

  • Soubor s konfliktem otevřeme v textovém editoru a najdeme sekvence <<<<<<<<, ========= a >>>>>>>>, kterými jsou označené jednotlivé konfliktní oblasti. Tato místa musíme opravit tak, aby kód dával smysl a poté zmíněné sekvence znaků smažeme.
  • Použijeme příkaz git mergetool, který spouští grafický nástroj (např. kdiff3 nebo meld), který nám se slučováním pomůže.

    kdiff3 vedle sebe zobrazuje 3 různé verze projektu: poslední společnou verzi (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 řádek, které mají v levém sloupci ? – tj. konfliktů.

Při řešení konfliktů nám může pomoct i příkaz gitk --merge, který zobrazí pouze commity, které modifikovaly konfliktní soubory. Jednoduše tam můžeme zjistit, které změny konflikt způsobily.

Git obsahuje poměrně jednoduchý algoritmus (v porovnání s jinými verzovacími systémy) pro slučování různých verzí souborů. Některým studentům se to nelíbí a stěžují si. Přečtěte si názor Linuse Torvaldse, proč tomu tak je.

Repozitář na GitHubu

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 na GitHubu. V závislosti na tom, jakým protokolem chcete k repozitářům na GitHubu přistupovat si nastavte buď password caching (pro HTTP) nebo SSH klíče (pro SSH).

  2. Vytvoření repozitáře na GitHubu. Můžete založit buď nový projekt nebo udělat tzv. “fork” existujícího projektu.

    Pro účely tohoto cvičení si založte fork Midnight commanderu. Do takto vytvořeného repozitáře pak nahrajete vypracovaný úkol z dnešního cvičení.

  3. Do svého repozitáře na GitHubu nahrajete větev z lokálního repozitáře některým z těchto způsobů:

    git remote add github https://github.com/<mujlogin>/mc.git
    git push github HEAD:homework
    

    nebo

    git push https://github.com/<mujlogin>/mc.git homework
    

    nebo

    git push ssh://git@github.com/<mujlogin>/mc.git homework
    

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í Midnight Commanderu (origin/master). Výsledek musí jít zkompilovat a musí obsahovat funkcionalitu přidanou do větve only-directories. Výsledek uložte do vámi vytvořeného repozitáře (forku) na GitHubu do větve homework.

Pokud někdo nechápe, proč vznikají konflikty a proč není obecně možné je vyřešit automaticky pomocí nástrojů, podívejte se na prezentaci připravenou kolegou Lisovým.
Comment by sojkam1 Čt 13. březen 2014, 10:59:40 CET

Nemuzu se nejak zbavit problemu s ssh klicem. Uz na cviceni byl problem s importem klice ze serveru postel (ubehlo i vice jak 10 minut a presto se nevedlo prihlasit), nakonec se to ale nejak povedlo. Nicmene ted jsem v situaci, kdy kontrolni ssh git@rtime.felk.cvut.cz info mi probehne v poradku, ale git fetch mi neprochazi s hlaskou Permission denied (publickey). Kdyz jsem se pres ~/.ssh/config snazil nastavit alias pro server tak, aby pouzival dany klic, pres ssh pristup s aliasem opet prosel v poradku, ale git alias nepoznal (pridani remote repozitare osp jako ssh://fel_rtime/osp/mc, pak nasledny git fetch osp). Hadam teda z toho, ze je nejaky problem na trase git - ssh. Jenze jaky a co s tim?

Comment by liebepav St 19. březen 2014, 19:37:47 CET

Abych vám mohl spolehlivě poradit, potřebuji víc informací. Pošlete je případně na osp@rtime.felk.cvut.cz, než do komentáře.

Nemuzu se nejak zbavit problemu s ssh klicem. Uz na cviceni byl problem s importem klice ze serveru postel (ubehlo i vice jak 10 minut a presto se nevedlo prihlasit), nakonec se to ale nejak povedlo.

Klíč máte na serveru uložen. První import proběhl v 13. 3. v 10:00, pak jste klič změnil v 10:20. Kdyby byly problémy s importem, pošlete výstup příkazu check-ssh-key.

Nicmene ted jsem v situaci, kdy kontrolni ssh git@rtime.felk.cvut.cz info mi probehne v poradku, ale git fetch mi neprochazi s hlaskou Permission denied (publickey). Kdyz jsem se pres ~/.ssh/config snazil nastavit alias pro server tak, aby pouzival dany klic, pres ssh pristup s aliasem opet prosel v poradku,

Žádné aliasy by neměly být potřeba. Pokud potřebuje mít klíč pojmenovaný jinak, než výchozí id_rsa (zdá se, že jste klíč pojmenoval key), stačí přidat do .ssh/config řádku Host rtime.felk.cvut.cz. Případně by taky mělo fungovat použití ssh-add ~/.ssh/key.

Pokud nic z výše uvedeného nezabere, máte pravděpodobně špatně nakonfigurované vzdálené repozitáře v gitu. Tam bych pak potřeboval vidět výstup git remote -v, případně celý obsah souboru .git/config ve vašem repozitáři.

Comment by sojkam1 St 19. březen 2014, 20:30:52 CET

Méně zkušenější uživatelé Linuxu (jako třeba já) se možná potýkali / potýkají s problémem při provedení příkazu

# sudo git fetch osp

kdy obdrží hlášku Permission denied (publickey). A to i v případě, že jim v pořádku prochází příkaz

# ssh git@rtime.felk.cvut.cz info

Je to způsobeno tím, že při použití magického slůvka sudo se jakoby přepnete na chvíli do účtu root. Vámi vygenerovaný ssh klíč je tedy potřeba nakopírovat ještě do adresáře /root/.ssh/ (obdoba toho v /home/user/.shh/, ale pro účet root).

Poté by již s provedením příkazu

# sudo git fetch osp

neměl být žádný problém.


Pokud ale stále tápete, nebo Vám něco stále nefunguje, postupujte dle tohoto návodu: (Návod předpokládá, že se snažíte připojit k git@rtime.felk.cvut.cz/osp/mc z Vašeho vlastního stroje, nikoliv ze stanice v učebně s109)

  1. Připojte se k Vašemu účtu na postel.felk.cvut.cz

    # ssh -A uzivatel@postel.felk.cvut.cz
    

    Slovo uzivatel nahraďte Vašim loginem, kterým se připojujete na stanici v učebně s109. Následně budete vyzvání k zadání hesla.

  2. Vygenerujte SSH klíč

    # ssh-keygen
    

    Lokaci a název klíče raději nechte jak je, pokud opravdu nepotřebujete měnit.

    Dále se doporučuje zadat nějaké heslo, pokud jste ale líní jako já, a nechcete při každém připojení zadávat heslo, heslo nevyplňujte a prostě 2x zmáčkněte Enter, načež se Vám vygenerují ve složce ~/.ssh 2 nové soubory (Veřejný a privátní SSH klíč).

    Po vygenerování klíče doporučuji provést tento příkaz

    (Nevím, zdali je to nutné, ale klíč se mi aktualizoval až poté, co jsem spustil tento příkaz)

    # eval $(ssh-agent)
    

    Informaci, zda server načetl již nový klíč získáme pomocí příkazu

    # check-ssh-key
    

    Mezitím, než server aktualizuje seznam nových klíčů se můžeme odhlásit příkazem

    # exit
    
  3. Zkopírování nových klíčů na vlastní stroj

    Pro zkopírování vygenerovaných SSH klíčů ze serveru postel.felk.cvut.cz jsem využil nástroje sshfs, v případě, že jej nemáte, lze jednoduše doinstalovat například pomocí

    # sudo apt-get install sshfs
    

    Tento šikovný nástroj nedělá nic jiného, než že „namountuje„ (propojí) Vámi vybraný adresář na disku s nějakým adresářem na serveru postel.felk.cvut.cz.

    Nejdříve vytvoříme přípojný adresář, který propojíme se serverem.

    # mkdir /home/user/postel
    

    Dále provedeme propojení se serverem postel.felk.cvut.cz

    # sshfs uzivatel@postel.felk.cvut.cz: /home/user/postel/
    

    Po přípojení můžeme provést zkopírování vygenerovaných SSH klíčů

    # cp -a /home/user/postel/.ssh/. /home/user/.ssh/.
    # cp -a /home/user/.ssh/. /root/.ssh/.
    

    Nyní se už můžete odpojit a smazat mountpoint

    # sudo fusermount -u /home/user/postel
    # rm -r /home/user/postel
    

4) Jestli Vám ani teď nebude fungovat příkaz

    # sudo git fetch osp

Tak už Vám není pomoci XD
Comment by dendimar Pá 21. březen 2014, 00:13:33 CET

Poté by již s provedením příkazu

# sudo git fetch osp

neměl být žádný problém.

Není jediný důvod, proč byste měli používat příkaz sudo. Naopak existuje spousta důvodů, proč byste ho v tomto případě použít neměli. Viz https://rtime.felk.cvut.cz/pipermail/osp/2014/000038.html.

Po vygenerování klíče doporučuji provést tento příkaz

(Nevím, zdali je to nutné, ale klíč se mi aktualizoval až poté, co jsem spustil tento příkaz)

# eval $(ssh-agent)

To je zbytečné a s importem klíče to nijak nesouvisí. Po vygenerování klíče použijte příkaz check-ssh-key ze kterého se dozvíte, jestli import už proběhl nebo ne. Mimo dobu cvičení probíhá import jednou za hodinu (v celou).

  1. Zkopírování nových klíčů na vlastní stroj

Pro zkopírování vygenerovaných SSH klíčů ze serveru postel.felk.cvut.cz jsem využil nástroje sshfs.

[…]

Po přípojení můžeme provést zkopírování vygenerovaných SSH klíčů

# cp -a /home/user/postel/.ssh/. /home/user/.ssh/.
# cp -a /home/user/.ssh/. /root/.ssh/.

Jednodušší je použít příkaz scp, který je rovnou součástí balíku ssh - nemusí se nic instalovat a mountovat.

# scp -r user@postel.felk.cvut.cz:.ssh/. ~/.ssh

Na adresář /root zapomeňte a sudo nepoužívejte.

Comment by sojkam1 Pá 21. březen 2014, 20:26:39 CET