Git - Kapitola 4. Sdílení vývoje s ostatními
Získávání aktualizací pomocí git pull / Zasílání patchů do projektu / Importování patchů do projektu / Veřejné git repositáře / Vytvoření veřejného repositáře / Exportování git repositáře přes git protokol / Exportování git repositáře přes http / Tlačení změn do veřejného repositáře / Co dělat když push selže / Vytvoření sdíleného repositáře / Povolení procházení repositáře webovým prohlížečem / Příklady / Údržba tématických větví pro maintainera Linuxového subsystému
Obsah
- Získávání aktualizací pomocí git pull
- Zasílání patchů do projektu
- Importování patchů do projektu
- Veřejné git repositáře
- Příklady
Získávání aktualizací pomocí git pull
Po naklonování repositáře a provedení několika vlastních změn se můžete rozhodnout podívat se na změny provedené v původním repositáři a doplnit je do vaší práce.
Už jsme vidělit jak udržovat remote-tracking větve aktuální pomocí příkazu git-fetch(1),a jak sloučit dvě větve. Takže změny z původního repositáře můžete do master větve sloučit příkazem:
$ git fetch $ git merge origin/master
Nicméně, příkaz git-pull(1) poskytuje způsob jak toto provést v jediném kroku:
$ git pull origin master
Ve skutečnosti, pokud máte načtenu větev "master", potom příkaz "git pull" standardně merguje změny z HEAD větve původního repositáře. Takže výše uvedeného lze často docílit prostě příkazem
$ git pull
Obecněji, větev vytvořená ze vzdálené větve bude standardně načítat data z této vzdálené větve. Podívejte se na detaily o volbách branch.<name>.remote a branch.<name>.merge v manuálové stránce příkazu git-config(1), a diskusi o volbě —track v manuálové stránce příkazu git-checkout(1) kde se dozvíte jak tyto defaultní hodnoty měnit.
Navíc, abyste museli psát co nejméně, příkaz "git pull" vám také pomáhá vytvářením výchozího popisu commitu, dokumentujícího větev a repositář ze kterého jste načítala.
(Ale všimněte si že žádný takový commit nebude vytvořen v případě fast forward; namísto toho bude vaše větev pouze aktualizována na nejnovější commit z upstream větve.)
Příkazu git pull můžete také předat "." namísto "vzdáleného" repositáře, a v tomto případě pouze přimerguje větev z aktuálního repositáře; takže příkazy
$ git pull . branch $ git merge branch
jsou přihližně ekvivalentní. První je ve skutečnosti velmi široce používán.
Zasílání patchů do projektu
Pokud máte pouze několik málo změn, nejjednodušší způsob jak je odeslání do projektu může být prosté zaslání e-mailem:
Nejdříve, použijte příkaz git-format-patch(1); například:
$ git format-patch origin
Který vyprodukuje očíslovanou posloupnost souborů v aktuálním adresáři, jeden pro každý patch v aktuální větvi ale ne v origin/HEAD.
Poté můžete tyto patche načíst do vašeho mailového klienta a poslat je ručně. Nicméně, pokud jich máte mnoho k oeslání, můžete raději využít skript git-send-email(1) pro automatizaci tohoto procesu. Nejdříve se ale podívejte do mailing listu vašeho projektu a zjistěte v jaké formě chtějí patche dostávat.
Importování patchů do projektu
Git také poskytuje nástroj git-am(1) (am znamená "apply mailbox") pro importování e-mailem doručované posloupnosti patchů. Prostě uložte všechny zprávy obsahující patche, v daném pořadí, do jediného mailbox souboru, řekněme "patches.mbox", a potom spusťte
$ git am -3 patches.mbox
Git aplikuje všechny patche v daném pořadí; pokud najde jakékoliv konflikty, zastaví se a vy můžete konflikty opravit tak jak bylo popsáno v kapitole "Resolving a merge". (Volba "-3" říká gitu aby provedl merge; pokud dáváte přednost tomu aby git prostě přerušil činnost a ponechal váš pracovní strom a index nedotknutý, můžete tuto volbu vynechat.)
Jakmile je index aktualizován i výsledky po vyřešení konfliktů, namísto vytváření nového commitu prostě spusťte
$ git am --resolved
a git pro vás vytvoří commit a bude potračovat v aplikaci zbývajících patchů z mailboxu.
Konečným výsledkem bude posloupnost commitů, jeden pro každý patch v původním mailboxu, s informací o autorovi a popisem commitu načtenými ze zprávy obsahující jednotlivé části.
Veřejné git repositáře
Dalším způsobem jak zasílat změny do projektu je říci maintainerovi (osobě zodpovědné za repositár) daného projektu aby si změny načetl z vašeho repositáře pomocí příkazu git-pull(1). V sekci "Načítání aktualizací příkazem git pull" jsme tento příkaz popsali jako způsob jak načítat změny z "main" repositáře, ale stejně dobře funguje i opačným směrem.
Pokud vy i maintainer máte oba účty na stejném stroji, potom můžete jednoduše přetáhnout změny ze svých repositářů přímo; příkazy které jako argumenty přijímají URL repositářů přijmou také název lokálního adresáře:
$ git clone /path/to/repository $ git pull /path/to/other/repository
nebo ssh URL:
$ git clone ssh://yourhost/~you/repository
Pro projekt s několika málo vývojáři, nebo pro synchronizaci několika privátních repositářů, to může být plně postačující.
Nicméně, obvyklejším způsobem je udržovt oddělený veřejný repositář (obvykle na samostatném stroji) ze kterého si ostatní mohou načítat (pull) změny. To je obvykle pohodlnější, a umožňuje vám to jasně oddělit rozdělanou vlastní práci od veřejně viditelného obsahu.
Svou každodenní práci budete ukládat do soukromého repositáře, ale změny budete ze soukromého repositáře pravidelně "tlačit" (push) do veřejného repositáře, čímž ostatním vývojářům umožníte jejich načtení. Takže tok změn, v situaci kdy je jeden vývojář s veřejným repositářem, vypadá takto:
vy tlačíte
váš osobní repositář ------------------> váš veřejný repositář
^ |
| |
| vy načítáte | oni načítají
| |
| |
| oni tlačí V
jejich veřejný repositář <------------------- jejich repositář
Postup podrobněji vysvětlíme v následujících odstavcích,
Vytvoření veřejného repositáře
Předpokládejme že váš osobní repositář je v adresáři ~/proj. Nejdříve vytvoříme klon repositáře a řekneme příkazu git daemon že ho chceme zveřejnit:
$ git clone --bare ~/proj proj.git $ touch proj.git/git-daemon-export-ok
Výsledný adresář proj.git obsahuje "prostý" git repositář - je to pouze obsah ".git" adresáře, bez všech souborů okolo.
Dále, zkopírujte proj.git na server kde plánujete hostovat veřejný repositář. Můžete použít příkazy scp, rsync nebo cokoliv je nejpohodlnější.
Exportování git repositáře přes git protokol
Toto je preferovaná metoda.
Pokud server spravuje někdo jiný, měl by vám sdělit do kterého adresáře repositář umístět, a na jakém git:// URL bude dostupný. Poté můžete přeskočit na odstavec "Tlačení změn do veřejného repositáře" níže.
Jinak vše co musíte udělat je spuštění git-daemon(1);který bude poslouchat na portu 9418. Standardně umožní přístup do libovolného adresáře vypadajícího jako .git adresář a obsahujícího kouzelný soubor git-daemon-export-ok. Předání cest adresářů jako argumentů příkazu git daemon dále omezí exportované adresáře na dané cesty.
Můžete také spustit příkaz git daemon jako inetd službu, detaily najdete v manuálové stránce příkazu git-daemon(1) (zejména se podívejte na odstavec s příklady).
Exportování git repositáře přes http
Git protokol poskytuje lepší výkon a spolehlivost, ale na serveru s nastaveným web serverem, nastavení http exportů může být znatelně jednodušší.
Vše co musíte udělat je umístit nově vytvořený git adresář do adresáře dostupného přes webový server, a provést několik málo nastavení tak aby weboví klienti dostali potřebné doplňující informace:
$ mv proj.git /home/you/public_html/proj.git $ cd proj.git $ git --bare update-server-info $ mv hooks/post-update.sample hooks/post-update
(Pro vysvětlení posledních dvou řádek se podívejte do git-update-server-info(1) a githooks(5).)
Oznamte URL proj.git. Všichni ostatní poté budou schopni klonovat nebo načítat z daného URL, například z příkazové řádky takto:
$ git clone http://yourserver.com/~you/proj.git
(Poněkud sofistikovanější nastavení pomocí WebDAV, umožňující také "pushing" přes http, najdete v manuálové stránce příkazu setup-git-server-over-http).
Tlačení změn do veřejného repositáře
Všimněte si že dvě techniky popsané výše (exportování přes http nebo git) umožňuje maintainterům načítat vaše poslední změny, ale neposkytují možnost zápisu, který potřebujete pro aktualizaci veřejného repositáře posledními změnami z vašich soukromých repositářů.
Nejjednodušší způsob jak toho dosáhnout je použití příkazů git-push(1) a ssh; pro aktualizaci vzdálené větve pojmenované "master" posledními změnami z vaší "master" větve, spusťte:
$ git push ssh://yourserver.com/~you/proj.git master:master
nebo pouze
$ git push ssh://yourserver.com/~you/proj.git master
Stejně jako git fetch, i příkaz git push si bude stěžovat pokud nedojde k fast forward; v následující sekci najdete detaily o řešení této situace.
Všimněte si že cílem "push" je obvykle "bare" repositář. Můžete také pushovat do repositáře který má načtený pracovní strom, ale pracovní strom nebude pushem upraven. To může vést k neočekávaným výsledkům pokud větev do které pushujete je aktuálně načtenou větví!
Stejně jako v případě příkazu git fetch, můžete také nastavit konfigurační volby které vám ušetří psaní; takže například, po
$ cat >>.git/config <<EOF [remote "public-repo"] url = ssh://yourserver.com/~you/proj.git EOF
byste výše uvedený push měli být schopni provést pouze příkazem
$ git push public-repo master
Přečtěte si podrobnosti o remote.<name>.url, branch.<name>.remote, a remote.<name>.push v manuálové stránce příkazu git-config(1).
Co dělat když push selže
Pokud by výsledkem pushe nebyl fast forward vzdálené větve, potom push selže s chybovou hláškou
error: remote 'refs/heads/master' is not an ancestor of local 'refs/heads/master'. Maybe you are not up-to-date and need to pull first? error: failed to push to 'ssh://yourserver.com/~you/proj.git'
To se může stát, například pokud:
- používáte
git reset —hardpro odstranění jit publikovaných commitů, nebo - používáte
git commit —amendpro nahrazení již publikovaných commitů (jako v odstavci nazývaném “Oprava omylů přepisem historie”), nebo - používáte
git rebasepro "rebase" již publikovaných commitů (jako v odstavci nazývaném “Udržování posloupnosti patchů aktuální pomocí příkazu git rebase”).
Příkaz git push můžete donutit vykonat update, a to připojením znaku plus na začátek názvu větve:
$ git push ssh://yourserver.com/~you/proj.git +master
Kdykoliv je za normálních okolností modifikován vrchol veřejného repositáře, je modifikován tak že nově odkazuje na potomka původního commitu. Tím že v této vynutíte push, uvedenou konvenci porušíte. (Podívejte se do odstavce nazývaného “Problémy s přepisem historie”.)
Nicméně, toto je obvyklá praxe pro lidi kteří potřebují jednoduchý způsob jak publikovat rozpracované série patchů, a je to akceptovatelný kompromis pokud varujete ostatní vývojáře že větev budete spravovat tímto způsobem.
Je také možné že push takto selže pokud právo pushovat do daného adresáře mají i další lidé. V tom případě je správným řešením zkusit znovu push poté co nejdříve aktualizujete svou práci: buď příkazem pull nebo příkazem fetch následovaným rebase; více najdete v následujícím odstavci a manuálové stránce k příkazu gitcvs-migration(7).
Vytvoření sdíleného repositáře
Další možností jak spolupracovat je použití modelu běžně používaného v CVS, kde několik vývojářů se speciálními právy všichni zapisují a čtou z jediného sdíleného repositáře. Návod jak toto nastavit najdete v manuálové stránce gitcvs-migration(7).
Nicméně, ačkoliv na podpoře sdílených repositářů v gitu není nic špatného, tento mód fungování obecně není doporučován, jednoduše proto že způsob spolupráce který git podporuje - výměnou patchů a načítáním z veřejných repositářů - má tolik výhod nad centrálním sdíleným repositářem:
- Schopnost gitu rychle importovat a mergovat patche umožňuje jedinému maintainerovi zpracovávat i velké počty příchozích změn. A když to přeroste únosnou mez, příkaz
git pullnabízí maintainerovi jednoduchý způsob jak tuto činnost delegovat jiným maintainerům, a přitom umožňuje volitelné review příchozích změn. - Protože repositář každého vývojáře má stejnou kompletní kopii projektu, žádný repositář není "speciální" a pro jiného vývojáře je triviální převzít správu projektu, buď po vzájemné dohodě nebo, proto že původní maintainer je nedosažitelný nebo je s ním obtížné pracovat.
- Absence centrální skupiny "commiterů" znamená že je třeba méně formálních rozhodnutí kdo je "členem" a kdo ne.
Povolení procházení repositáře webovým prohlížečem
CGI skript gitweb poskytuje uživatelům jednoduchý způsob procházení souborů a historie vašeho projektu bez nutnosti instalovat git; podrobnosti o nastavení najdete v souboru gitweb/INSTALL ve zdrojových kódech gitu.
Příklady
Údržba tématických větví pro maintainera Linuxového subsystému
Tento odstavec popisuje jak Tony Luck používá git ve své roli maintainera IA64 architektury pro Linuxové jádro.
Používá dvě veřejné větve:
- Větev "test" do které jsou patche začleňovány nejdříve, takže jsou zveřejněny před integrováním se zbývajícím vývojem. Tento strom je dostupný pro Andrewa aby do -mm mohl vložit cokoliv bude chtít.
- Strom "release" do kterého jsou přesouvány patche pro finální testování, a slouží jaké pro jejich odesílání dále Linusovi (zasláním "please pull" žádosti).
Používá také několik dočasných větví ("topic branches"), přičemž každá z nich obsahuje skupinu logicky souvisejících patchů.
Pro podobné nastavení nejdříve vytvořte svůj pracovní strom naklonováním Linusova veřejného stromu:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/\ linux-2.6.git work $ cd work
Linusův strom bude uložen v remote větvi nazvané origin/master, a lze z ní aktualizovat příkazem git-fetch(1); můžete také sledovat další veřejné stromy tak že pomocí příkazu git-remote(1) vytvoříte "remote" větev a příkazem git-fetch(1) je budete udržovat aktuální; viz. také Kapitola 1, Repositáře a větve.
Nyní vytvořte větve ve kterých budete pracovat; ty začínají na současném vrcholu větve origin/master, a měly by být nastaveny (pomocí volby —track příkazu git-branch(1)) tak aby standardně načítaly změny z Linusovy větve.
$ git branch --track test origin/master $ git branch --track release origin/master
Jednoduše je lze aktualizovat příkazem git-pull(1).
$ git checkout test && git pull $ git checkout release && git pull
Důležitá poznámka! Pokud máte v těchto větvích jakékoliv lokální změny, potom tento merge v historii vytvoří commit objekt (bez lokálních změn git jednoduše provede "fast forward" merge). Mnoho lidí nemá v oblibě "šum" který to způsobuje v Linuxové historii, takže byste se tomu v "release" větvi měli důsledně vyhýbat, protože tyto rušivé commity se stanou součástí trvalé historie až Linuse požádáte o načtení změn z release větve.
A few configuration variables (see git-config(1)) can make it easy to push both branches to your public tree. (See the section called “Setting up a public repository”.)
Několik konfiguračních proměnných (viz. git-config(1)) může zjednodušit načítání změn z obou větví do vašeho veřejného stromu. (Podrobnosti najdete v odstavci nazvaném “Vytvoření veřejného repositáře”.)
$ cat >> .git/config <<EOF [remote "mytree"] url = master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git push = release push = test EOF
Potom můžete načítat změny z test a release stromů pomocí příkazu git-push(1):
$ git push mytree
nebo pushovat pouze z větve test nebo release pomocí:
$ git push mytree test
nebo
$ git push mytree release
Nyní aplikujeme několik patchů z komunity. Vymysleme nějaké pěkné krátké jméno pro větev do které umístíme tento patch (nebo skupinu patchů), a vytvořme novou větev z vrcholu Linusovy větve:
$ git checkout -b speed-up-spinlocks origin
Nyní aplikujme patch(e), spustíme několik testů, a commitneme změny. Pokud má patch několik částí, potom byste každou měli do větve aplikovat jako samostatný commit.
$ ... patch ... test ... commit [ ... patch ... test ... commit ]*
Až budete spokojeni s výsledkem změny, můžete ji načíst (pull) do větve "test" čímž ji připravíte na zveřejnění:
$ git checkout test && git pull . speed-up-spinlocks
Je nepravděpodobné že byste narazili na nějaké konflikty ... ale mohli byste pokud jste se trochu loudali a načetli jste nové verze z nadřazené (upstream) větve.
O něco později, po uplynutí doby dostatečné na otestování, můžete stejnou větev načíst do "release" stromu tak aby je bylo možno natlačit do upstream větve. Právě na tomto místě nejlépe oceníte to že každý patch (nebo sérii souvisejících patchů) držíte v samostatné větvi. Znamená to totiž že patche mohou být do "release" větve tlačeny v libovolném pořadí.
$ git checkout release && git pull . speed-up-spinlocks
Po chvíli budete mít několik větví, a i přes dobře zvolená jméno pro každou z nich nejspíše zapomenete k čemu vlastně byly, nebo v jakém jsou stavu. Pro připomenutí toho jaké změny v dané větvi jsou, použijte:
$ git log linux..branchname | git shortlog
Pokud vás zajímá zda již byla daná větev přimergována do testovací nebo release větve, použijte:
$ git log test..branchname
nebo
$ git log release..branchname
(Pokud větev zatím zmergována nebyla, uvidíte vypsáno několik položek. Pokud zmergovány byly, nevypíší příkazy nic.)
Jakmile patch dokončí velký cyklus (přesun z testovací do release větve, načtení Linusem a konečně zpět do vaší lokální "origin/master" větve), není už větev vyhrazená pro danou změnu potřeba. To zjistíte tak že výstup příkazu
$ git log origin..branchname
je prázdný. V tuto chvíli může být větev smazána:
$ git branch -d branchname
Některé změny jsou tak triviální že pro ně není třeba vytvářet samostatnou větev a potom ji mergovat do testovací i release větve. Takové změny stačí aplikovat přímo do "release" větve, a následně je přimergovat zpět do testovací větve.
Pro vytvoření diffstat a shortlog shrnutí změn pro přiložení do "please pull" požadavku zasílaného Linusovi můžete použít příkaz:
$ git diff --stat origin..release
a
$ git log -p origin..release | git shortlog
Tady je několik skriptů které toto ještě dále zjednodušují.
==== update script ====
# Update a branch in my GIT tree. If the branch to be updated
# is origin, then pull from kernel.org. Otherwise merge
# origin/master branch into test|release branch
case "$1" in
test|release)
git checkout $1 && git pull . origin
;;
origin)
before=$(git rev-parse refs/remotes/origin/master)
git fetch origin
after=$(git rev-parse refs/remotes/origin/master)
if [ $before != $after ]
then
git log $before..$after | git shortlog
fi
;;
*)
echo "Usage: $0 origin|test|release" 1>&2
exit 1
;;
esac
==== merge script ====
# Merge a branch into either the test or release branch
pname=$0
usage()
{
echo "Usage: $pname branch test|release" 1>&2
exit 1
}
git show-ref -q --verify -- refs/heads/"$1" || {
echo "Can't see branch <$1>" 1>&2
usage
}
case "$2" in
test|release)
if [ $(git log $2..$1 | wc -c) -eq 0 ]
then
echo $1 already merged into $2 1>&2
exit 1
fi
git checkout $2 && git pull . $1
;;
*)
usage
;;
esac
==== status script ====
# report on status of my ia64 GIT tree
gb=$(tput setab 2)
rb=$(tput setab 1)
restore=$(tput setab 9)
if [ `git rev-list test..release | wc -c` -gt 0 ]
then
echo $rb Warning: commits in release that are not in test $restore
git log test..release
fi
for branch in `git show-ref --heads | sed 's|^.*/||'`
do
if [ $branch = test -o $branch = release ]
then
continue
fi
echo -n $gb ======= $branch ====== $restore " "
status=
for ref in test release origin/master
do
if [ `git rev-list $ref..$branch | wc -c` -gt 0 ]
then
status=$status${ref:0:1}
fi
done
case $status in
trl)
echo $rb Need to pull into test $restore
;;
rl)
echo "In test"
;;
l)
echo "Waiting for linus"
;;
"")
echo $rb All done $restore
;;
*)
echo $rb "<$status>" $restore
;;
esac
git log origin/master..$branch | git shortlog
done




