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í
- Odstranění merge
- Fast-forward merge
- Fixing mistakes
- Zajištění dobrého výkonu
- Zajištění spolehlivosti
Ř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:
- Provedení nějakých změn v pracovním adresáři pomocí vašeho oblíbeného editoru.
- Informování gitu o provedených změnách.
- 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:
- 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.
- 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.




