Git - Kapitola 3. Vývoj s gitem

Řekněte gitu své jméno / Vytvoření nového repositáře / Jak vytvořit commit / Vytváření dobrých popisů commitů / Ignorování souborů / Jak mergovat / Řešení mergování / Getting conflict-resolution help during a merge / Odstranění merge / Fast-forward merge / Fixing mistakes / Oprava omylu novým commitem / Oprava omylů přepisem historie / Načítání staré verze souboru / Dočasné odložení rozdělané práce / Zajištění dobrého výkonu / Zajištění spolehlivosti / Kontrola repositáře pro poškození / Obnova ztracených změn

Obsah

Řekněte gitu své jméno
Vytvoření nového repositáře
Jak vytvořit commit
Vytváření dobrých popisů commitů
Ignorování souborů
Jak mergovat
Řešení mergování
Getting conflict-resolution help during a merge
Odstranění merge
Fast-forward merge
Fixing mistakes
Oprava omylu novým commitem
Oprava omylů přepisem historie
Načítání staré verze souboru
Dočasné odložení rozdělané práce
Zajištění dobrého výkonu
Zajištění spolehlivosti
Kontrola repositáře pro poškození
Obnova ztracených změn

Řekněte gitu své jméno

Před vytvářením commitů byste se gitu měli představit. Nejjednodušší způsob jak to udělat je zajistit že se v souboru .gitconfig ve vašem domovském adresáři vyskytují následující řádky:

[user]
        name = Your Name Comes Here
        email = you@yourdomain.example.com

(Další detaily najdete v manuálové stránce příkazu git-config(1) v odstavci "CONFIGURATION FILE".)

Vytvoření nového repositáře

Vytvoření čistého nového repositáře je velice jednoduché:

$ mkdir project
$ cd project
$ git init

Pokud máte nějaký výchozí obsah (řekněme tar balík):

$ tar xzvf project.tar.gz
$ cd project
$ git init
$ git add . # include everything below ./ in the first commit:
$ git commit

Jak vytvořit commit

Vytvoření nového commitu obnáší tři kroky:

  1. Provedení nějakých změn v pracovním adresáři pomocí vašeho oblíbeného editoru.
  2. Informování gitu o provedených změnách.
  3. Vytvoření commitu pomocí obsahu o kterém jste gitu řekli v kroku 2.

V praxi můžete prokládat a opakovat kroky 1 a 2 tolikrát kolikrát chcete: aby byl udržen přehled o tom co chcete uložit v kroku 3, git udržuje otisk obsahu stromu ve zvláštním přípravném prostoru (staging area) nazývaném "index."

Na začátku je obsah indexu identický s tím v HEAD. Příkaz "git diff --cached", který zobrazuje rozdíly HEAD a indexem, by tudíž v této chvíli neměl produkovat žádný výstup.

Modifikování indexu je jednoduché:

Pro aktualizaci indexu obsahem modifikovaného souboru, použijte příkaz

$ git add path/to/file

Pro přidání obsahu nového souboru do indexu, použijte

$ git add path/to/file

Pro odstranění souboru z indexu a z pracovního stromu proveďte

$ git rm path/to/file

Po každém kroku můžete ověřit že

$ git diff --cached

vždy ukazuje rozdíl mezi  HEAD a indexem - to je právě to co byste commitnuli pokud byste v danou chvíli vytvořili commit - a že

$ git diff

vypisuje rozdíl mezi pracovním stromem a index souborem.

Všimněte si že "git add" pouze přidá aktuální obsah souboru do indexu; další změny do souboru budou ignorovány pokud na souboru znovu nespustíte git add.

Až budete hotovi, prosťte spusťte

$ git commit

a git vás požádá o popis commitu a poté vytvoří nový commit. Zkontrolujte že commit obsahuje přesně to co očekáváte pomocí příkazu

$ git show

Jako zvláštní zkratka,

$ git commit -a

aktualizuje index o všechny modifikované nebo odstraněné soubory, vše v jednom kroku.

Několik příkazů užitečných pro sledování toho co budete commitovat:

$ git diff --cached # rozdíly mezi HEAD indexem; co by bylo commitnuto
                    # pokud byste commit spustili právě teď
$ git diff          # rozdíly mezi indexem a vaším pracovním adresářem;
                    # změny které by při commitu uloženy nebyly
$ git diff HEAD     # rozdíly mezi HEAD a pracovním stromem; které
                    # by byly uloženy pokud byste provedli "commit -a"
$ git status        # stručné shrnutí informací pro každý soubor.

Pro vytváření commitů, prohlížení změn v indexu a pracovním stromu, výběr jednotlivých  rozdílů pro zahrnutí do indexu (kliknutím pravým tlačítkem na daný rozdíl a výběrem "Stage Hunk For Commit") můžete také použít git-gui(1),

Vytváření dobrých popisů commitů

Ačkoliv to není vyžadováno, je dobrý nápad začínat popisy commitu krátkým (méně než 50 znaků dlouhou) řádkem shrnujícím změnu, následovaným prázdným řádkem a poté podrobným popisem. Například nástroje zasílající oznámení o commitech e-mailem používají první řádek jako předmět e-mailu, a zbytek popisu commitu jako tělo zprávy.

Ignorování souborů

V projektech často vznikají soubory které gitem spravovat nechcete. To typicky zahrnuje soubory generované při buildováním nebo dočasné záložní soubory vytvářené vaším editorem. Samozřejmě, nesledování souborů gitem je jenom otázka nevolání příkazu git add pro dané soubory. Nicméně tyto soubory povalující se všude okolo se rychle stávají otravnými, např. činí příkaz git add . prakticky nepoužitelným, a stále se zobrazují ve výstupu příkazu git status.

Můžete gitu říct aby ignoroval určité soubory vytvořením souboru .gitignore na nejvyšší úrovni vašeho pracovního adresáře, s obsahem například:

# Řádky začínající '#' jsou považovány za komentáře.
# Ignoruj soubory pojmenované foo.txt.
foo.txt
# Ignoruj (generované) html soubory,
*.html
# kromě foo.html který je udržován ručně.
!foo.html
# Ignoruj objekty a archivy.
*.[oa]

Detailní vysvětlení syntaxe najdete v manuálové stránce příkazu gitignore(5). Soubor .gitignore můžete také umístit do jiných adresářů pracovního stromu, a v tom případě budou platit pro tyto adresáře a jejich podadresáře. Soubory .gitignore mohou být přidány do repositáře jako jakékoliv jiné soubory (prostě na nich spusťte git add .gitignore and git commit, jako obvykle), což se může hodit pokud vzory ignorovaných souborů (například vzory odpovídající buildovaným souborům) dávají smysl i pro další uživatele kteří si naklonují váš repositář.

Pokud si přejete tyto vzory aplikovat jen na některé repositáře (namísto všech repositářů pro daný projekt), můžete je umístit do souboru ve vašem repositáři pojmenovaného .git/info/exclude, nebo do libovolného souboru specifikovaného v konfigurační proměnné core.excludesfile. Některé git příkazy mohou také přijímat vzory přímo na příkazové řádce. Detaily viz. manuálová stránka gitignore(5).

Jak mergovat

Dvě rozbíhající se větve můžete znovu spojit pomocí příkazu git-merge(1):

$ git merge branchname

kterým vývoj sloučíte z větve "banchname" do aktuální větve. Pokud existují konflikty - například, pokud je soubor v obou repositářích (vzdáleném i lokálním) upraven rozdílnými způsoby - v tom případě budete upozorněni; výstup může vypadat například takto:

$ git merge next
 100% (4/4) done
Auto-merged file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.

V problematických souborech jsou označena místa konfliktů, a po jejich manuálním vyřešení můžete index aktualizovat s jejich obsahem a spustit git commit, stejně jako byste udělali normálně při vytváření nového souboru.

Pokud výsledný commit prozkoumáte pomocí gitk, uvidíte že má dva rodiče, jednoho ukazujícího na vrchol aktuální větve, a druhého na vrchol druhé větve.

Řešení mergování

Pokud se merge nepodaří zpracovat automaticky, git ponechá index a pracovní strom ve speciálním stavu který vám dá všechny informace potřebné pro jeho ruční vyřešení.

Soubory s konflikty jsou v indexu zvláště označeny, takže dokud problém nevyřešíte a neaktualizujete index, příkaz git-commit(1) selže:

$ git commit
file.txt: needs merge

Také git-status(1) bude tyto soubory vypisovat jako "nesloučené" a do souborů s konflikty budou vloženy značky vymezující konflikty, například takto:

<<<<<<< HEAD:file.txt
Hello world
=======
Goodbye
>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

Jediné co musíte udělat je editovat tyto soubory, vyřešit tyto konflikty, a potom

$ git add file.txt
$ git commit

Všimněte si popis commitu již bude předvyplněn některými informacemi o mergi. Tuto zprávu můžete obvykle ponechat tak jak je, ale pokud chcete můžete pochopitelně přidat vlastní doplňující informace.

Výše uvedené kroky jsou vše co potřebujete k provedení jednoduchého merge. Nicméně git také poskytuje více informací pro pomoc s řešením konfliktů.

Getting conflict-resolution help during a merge

Všechny změny které byl git schopen zmergovat automaticky již byly do indexu přidány, takže příkaz git-diff(1) vypisuje pouze konflikty. Používá neobvyklou syntaxi:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,5 @@@
++<<<<<<< HEAD:file.txt
 +Hello world
++=======
+ Goodbye
++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

Připomeňme že commit který bude commitnut po vyřešení konfliktů bude mít dva rodiče namísto obvyklého jednoho: jeden rodič bude HEAD, vrchol aktuální větve; druhým bude vrchol druhé větve, která bude dočasně dostupná přes proměnnou MERGE_HEAD.

Index během mergování obsahuje informace o třech verzích každého souboru. Každé z těchto stádií (stage) souboru reprezentuje jinou verzi souboru:

$ git show :1:file.txt  # soubour ze společného předka obou větví
$ git show :2:file.txt  # verze z větve HEAD.
$ git show :3:file.txt  # verze z větve MERGE_HEAD.

Když požádáte příkaz git-diff(1) o vypsání konfliktů, provede třísměrný diff mezi konfliktními výsledky merge v pracovním stromě a stádií 2 a 3 tak aby byly zobrazeny pouze ty části jejichž obsah se změnil na obou stranách (jinými slovy, pokud výsledek mergování dané části pochází pouze ze stádia 2, nejedná se o konfliktní část. To samé platí pro stav 3).

Diff výstup uvedený výše ukazuje rozdíly mezi pracovní verzí souboru file.txt a jeho stádii 2 a 3. Takže namísto doplnění jediného znaku "+" nebo "-" na začátek každé řádky jsou nyní používány dva sloupce: první sloupec je používán pro rozdíly mezi prvním rodičem a verzí v pracovní kopii, a druhý pro rozdíly mezi druhým rodičem a pracovní kopií. (Další podrobnosti najdete v odstavci "COMBINED DIFF FORMAT" manuálové stránky příkazu git-diff-files(1).)

Po triviálním vyřešení konfliktu (ale před aktualizací indexu), bude diff vypadat takto:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,1 @@@
- Hello world
 -Goodbye
++Goodbye world

To naznačuje že v naší vyšešené verzi byl smazán řádek "Hello world" z prvního rodiče, smazán řádek "Goodbye" z druhého rodiče, a doplněn řádek "Goodbye world" který původně nebyl přítomen ani v jednom z rodičů.

Některé zvláštní volby příkazu diff umožňují generování diffu pouze oproti jednomu stádiu:

$ git diff -1 file.txt          # diff oproti stádiu 1
$ git diff --base file.txt      # to samé jako na předchozím řádku
$ git diff -2 file.txt          # diff oproti stádiu 2
$ git diff --ours file.txt      # to samé jako na předchozím řádku
$ git diff -3 file.txt          # diff oproti stádiu 3
$ git diff --theirs file.txt    # to samé jako na předchozím řádku.

Příkazy git-log(1) a gitk(1) také umožňují zvláštní pomůcky pro mergování:

$ git log --merge
$ gitk --merge

Tyto příkazy vypíšou commity existující jen v HEAD nebo MERGE_HEAD, a které modifikují nemergované soubory.

Můžete také použít git-mergetool(1),který vám dovolí mergovat soubory pomocí externích nástrojů jako jsou například Emacs nebo kdiff3.

Pokaždé když vyřešíte konflikt v souboru, aktualizujte index:

$ git add file.txt

Různá stádia souboru budou "odstraněna" a od této chvíle už příkaz git diff nebude rozdíly pro daný soubor vypisovat.

Odstranění merge

Pokud se dostanete do úzkých a rozhodnete se vzdát to a zbavit se toho zmatku, můžete se vrátit do stavu před mergem:

$ git reset --hard HEAD

Nebo, pokud už jste merge, kterého se chcete zbavit, commitnuli

$ git reset --hard ORIG_HEAD

Nicméně tento poslední příkaz může být v některých případech nebezpečný - nikdy nezahazujte commit který už jste commitnuli, pokud už tento commit byl přimergován do jiné větve, protože to může mást následující merge.

Fast-forward merge

Existuje ješte jeden zvláštní dosud nezmíněný případ, ošetřovaný poněkud jinak. V obvyklém případě má merge za následek merge commit se dvěma rodiči, odkazujícími na jednotlivé linie vývoje které byly zmergovány.

Avšak pokud je aktuální větev potomkem druhé - takže každý commit v ní dostupný už je v aktuální větvi přítomen - potom git provádí pouze "fast forward"; vrchol aktuální větve je posunut kupředu tak aby ukazoval na vrchol přimergovávané větve, aniž by byly vytvářeny jakékoliv nové commity.

Fixing mistakes

Pokud jste si rozvrtali pracovní strom, ale ještě jste svůj omyl necommitnuli, můžete celý pracovní strom vrátit do posledního commitnutého stavu příkazem

$ git reset --hard HEAD

Pokud máte commit který byste později raději neměli, existují dva zcela odlišné způsoby jak tento problém vyřešit:

  1. Můžete vytvořit nový commit který odstraňuje změny provedené předchozím commitem. Toto je správný způsob pokud už jste svou chybu uvolnili na veřejnost.
  2. Můžete se vrátit zpět a modifikovat starý commit. Toto byste nikdy neměli dělat pokud už jste změny pustili na veřejnost; git obvykle neočekává že se "historie" projektu mění, a nemůže korektně provádět opakované merge z větve jejíž historie se změnila.

Oprava omylu novým commitem

Vytvoření nového commitu který odstraňuje předchozí změny je velice jednoduché; prostě předejte příkazu git-revert(1) odkaz na chybný commit; například pro odstranění posledního commitu:

$ git revert HEAD

To vytvoří nový commit odstraňující změny z HEAD. Budete mít možnost upravit popis tohoto nového commitu.

Můžete také odstranit předchozí změnu, například předposlední:

$ git revert HEAD^

V tomto případě se git pokusí odstranit staré změny zatímco změny provedené od té doby ponechá nedotknuté. Pokud se novější změny překrývají s odstraňovanými změnami, potom budete požádání o manuální odstranění konfliktů, stejně jako v případě řešení merge.

Oprava omylů přepisem historie

Pokud je problematický commit ten nejnovější, a zatím jste ho nezveřejnili, můžete ho prostě  zničit pomocí příkazu git reset.

Alternativně můžete upravit pracovní adresář a aktualizovat index s opravou vašeho omylu, stejně jako byste vytvářeli novy commit, a potom spusťte

$ git commit --amend

čímž nahradíte starý commit novým commitem zahrnujícím vaše změny, s tím že nejdříve dostanete možnost upravit popis commitu.

Opět, toto byste nikdy neměli dělat s commitem který již byl přimergován do jiné větve; namísto toho používejte příkaz git-revert(1).

Je také možné nahradit commity ještě dále v historii, ale to je pokročilé téma na které se podíváme v v další kapitole.

Načítání staré verze souboru

In the process of undoing a previous bad change, you may find it useful to check out an older version of a particular file using git-checkout(1). We've used git checkout before to switch branches, but it has quite different behavior if it is given a path name: the command

V procesu odstraňování předchozí chybné změny se vám také může hodit načtení staré verze určitého souboru pomocí příkazu git-checkout(1). Již dříve jsme použili git checkout pro přepnutí mezi větvemi, ale pokud mu předáte cestu k souboru chová se úplně jinak: příkaz

$ git checkout HEAD^ path/to/file

nahradí path/to/file obsahem který měl v commitu HEAD^, a aktualizuje také index aby tomu odpovídal. Příkaz nemění větve.

Pokud se pouze chcete podívat na starou verzi souboru, bez modifikování pracovního adresáře, můžete toho docílit příkazem git-show(1):

$ git show HEAD^:path/to/file

což zobrazí požadovanou verzi souboru.

Dočasné odložení rozdělané práce

Řekněme že jste uprostřed práce na něčem komplikovaném, a najdete nesouvisející ale očividnou a triviální chybu. Dříve než budete pokračovat v práci chtěli byste ji opravit. V takovém případě můžete použít příkaz git-stash(1) pro uložení aktuálního stavu práce, a po opravě chyby (nebo, případně po provedení opravy na jiné větvi a následném návratu zpět), vrátit zpět rozpracované změny.

$ git stash save "rozdělaná práce na vlastnosti foo"

Tento příkaz uloží stranou na hromadu (stash) provedené změny, a nastaví pracovní strom a index tak aby odpovídal vrcholu aktuální větve. Potom můžete provést opravu jako obvykle.

... edit and test ...
$ git commit -a -m "blorpl: typofix"

Poté se můžete vrátit zpět k původně rozdělané práci pomocí příkazu stash pop:

$ git stash pop

Zajištění dobrého výkonu

Na velkých repositářích, git spoléká na compresi historie aby zabránil zabrání příliš velkého prostoru na disku nebo v paměti.

Tato komprese není prováděna automaticky. Tudíž byste čas od času měli spustit git-gc(1):

$ git gc

pro rekompresi archivu. To může být časově velmi náročné, takže nejspíš dáte přednost spouštění git gc v době kdy neděláte nic jiného.

Zajištění spolehlivost

Kontrola repositáře pro poškození

Příkaz git-fsck(1) spouští na repositáři mnoho kontrol konzistence, a oznamuje jakékoliv problémy. To může chvíli trvat. Zdaleka nejčastější varování je o "plandajících" (dangling) objektech:

$ git fsck
dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
dangling blob 218761f9d90712d37a9c5e36f406f92202db07eb
dangling commit bf093535a34a4d35731aa2bd90fe6b176302f14f
dangling commit 8e4bec7f2ddaa268bef999853c25755452100f8e
dangling tree d50bb86186bf27b681d25af89d3b5b68382e4085
dangling tree b24c2473f1fd3d91352a624795be026d64c8841f
...

Dangling objects are not a problem. At worst they may take up a little extra disk space. They can sometimes provide a last-resort method for recovering lost work—see the section called “Dangling objects” for details.

Plandající objekty nejsou problém. Přinejhorším mohou zabírat trochu místa na disku navíc. Občas mohou poskytovat poslední šanci jak obnovit ztracenou práci - detaily najdete v sekci nazvané “Plandající objekty”.

Obnova ztracených změn

Reflogs

Řekněme že příkazem git-reset(1) —hard upravíte větev, a potom si uvědomíte že daná větev byl váš jediný odkaz na daný bod v historii.

Naštěstí, git udržuje také log, nazývaný "reflog", všech předchozích hodnot dané větve. Takže v tomto případě můžete najít starou historii, například takto:

$ git log master@{1}

Uvedený příkaz vypíše commity dostupné z předchozí verze vrcholu větve "master". Tato syntaxe může být použita s jakýmkoliv git příkazem, ne pouze s příkazem git log. Některé další příklady:

$ git show master@{2}           # Ukaž kam větev ukazovala před 2,
$ git show master@{3}           # 3, ... změnami.
$ gitk master@{yesterday}       # Ukaž kam větev ukazovala včera,
$ gitk master@{"1 week ago"}    # ... nebo minulý týden.
$ git log --walk-reflogs master # Ukaž reflog položky pro master větev

Samostatný reflog je udržován pro HEAD, takže

$ git show HEAD@{"1 week ago"}

vypíše kam ukazoval HEAD před týdne, ne kam před týdnem ukazovala aktuální větev. To vám umožňuje prohlížet historii toho co jste načetli (check out).

Reflogy jsou standardně udržovány po 30 dní, a po této době mohou být odstraněny. Podrobnosti o tom jak řídit toto čištění najdete v manuálových stránkách k příkazům git-reflog(1) a git-gc(1), a také sekci "SPECIFYING REVISIONS" manuálové stránky k příkazu git-rev-parse(1).

Všimněte si že reflog historie je velice odlišná od normální git historie. Zatímco normální historie je sdílena všemi repositáři které pracují na stejném projektu, reflog historie sdílena není: říká pouze jak se během času měnily větve ve vašem lokálním repositáři.

Zkoumání plandajících objektů

In some situations the reflog may not be able to save you. For example, suppose you delete a branch, then realize you need the history it contained. The reflog is also deleted; however, if you have not yet pruned the repository, then you may still be able to find the lost commits in the dangling objects that git fsck reports. 

V některých situacích vás reflog ale nemusí zachránit. Předpokládejme například že smažete větev, a poté si uvědomíte že potřebujete historii kterou obsahovala. Příslušný reflog je také smazán; nicméně pokud jste zatím neprovedli čištění (prune) repositáře, můžete být stále schopni získat ztracené commity mezi plandajícími objekty vypisovanými git fsck příkazem. Detaily najdete v sekci nazvané “Plandající objekty”.

$ git fsck
dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
...

Zvolený z těchto plandajících commitů můžete prozkoumat například příkazem

$ gitk 7281251ddd --not --all

který dělá přesně to jak zní: říká že chcete vidět historii commitů popisovanou plandajícími commity, ale ne historii popisovanou všemi existujícími větvemi a tagy. Takže dostanete právě historii dosažitelnou ze ztraceného commitu. (A uvědomte si že se nemusí jednat o jediný commit: jako plandající označujeme pouze začátek linie vývoje, za kterou ale může být hluboká a komplexní smazaná historie.)

Pokud se rozhodnete že chcete historii zpět, můžete vždy vytvořit nový odkaz který na ni ukazuje, například vytvořit novou větev:

$ git branch recovered-branch 7281251ddd

Další typy plandajících objektů (bloby a stromy) také existují, a plandající objekty mohou vznikat v různých situacích.

Komentáře

K tomuto článku zatím žádné komentáře neexistují (nebo čekají na schválení).

Nový komentář

Všechny komentáře podléhají schválení - mezi odesláním komentáře a jeho zobrazením na této stránce tedy může být prodleva. Vyplníte-li e-mailovou adresu, budete o schválení či neschválení komentáře informováni.

V titulku ani v textu nejsou povoleny HTML tagy - budou automaticky odstraněny. Odstavec ukončíte prázdným řádkem.

(nepovinné)