Výlet do říše verzí: problémy se slučováním větví

16. 2. 2004
Doba čtení: 5 minut

Sdílet

Dnes si rozebereme problémy se slučováním větví v CVS a dojde konečně i na slibované sledování cizího projektu ve vlastním repository.

Krutá realita

Při veselém (a zejména intenzivním) využívání slučování narazíme na několik problémů. Zvláště pokud využíváme $Keywords$ (které jistě všichni známe již z RCS), se nám při různém mergování sem a tam vyrojí spousta nepříjemných konfliktů, protože se samozřejmě klíčová slova budou rozvíjet na různé nesouhlasné hodnoty. Proto je velmi užitečné používat lokální parametr -kk, který zajistí, že všechna klíčová slova zůstanou ve svém základním tvaru (např. $Date$ místo $Date: 2004/01/26 14:52:13 $), tedy stejná, ať se jedná o revizi, jakou chce.

Další problém nastává při tzv. vícenásobném slučování. Větev VETEV jsme již připojili k HEAD, ovšem posléze jsme do VETEV přidali nějaké další změny a chtěli bychom do HEADu dostat i ty. Ovšem příkaz -jVETEV nalezne opět revizi společnou ještě oběma větvím, to znamená, že se nám větší část linie revizí sloučí podruhé, což samozřejmě není příliš žádoucí.

Abychom se tomu vyhnuli, budeme muset použít dva parametry -j a u prvního z nich nějak upřesnit, že se má začít slučovat až od okamžiku, kdy jsme větve spojili poprvé. Bohužel nám CVS situaci ani trochu neusnadní, neboť si informace o historii mergování vůbec neukládá; proto se o sebe musíme postarat sami. Můžeme například specifikovat u prvního parametru za dvojtečkou datum, od kterého se má slučovat, a někam si zapisovat, kdy jsme naposledy větve sloučili. Nebo si po každém sloučení můžeme vytvořit na větvi tag a pak slučovat až od něj dál. (A za chvíli se z obsáhlosti seznamu tagů i pomalosti tagování zejména obrovských souborů s obrovskou historií – typicky gettextoidní .po soubory – zblázníme.)

Tak se také dostáváme k dalšímu zásadnímu problému – při sloučení se nám ztratí všechna historie změn ve větvi, kterou mergujeme, takže nevíme co, kdy, jak, ani proč. Můžeme si sice přehazovat logy, diffy a anotace mezi různými větvemi, ovšem to je poměrně pracné a únavné, zvláště pokud mergujeme velmi často.

Pro tento problém ovšem zřejmě neexistuje v rámci CVS žádné elegantní řešení. Já osobně obvykle jednoduše CVSoidní mergování nepoužívám. Pokud větvení využívám spíše okrajově, prostě jednotlivé patche ručně backportuji (tedy vezmu diff daného commitu a aplikuji jako patch na cílovou větev, což většinou nebývá tak moc práce a alespoň si tak změny ještě jednou projdu a zkontroluji). Pokud chci větvení využít intenzivněji, použiji něco jiného než CVS ;-).

S cizími zdrojáky ve vlastním modulu

V řadě případů je velmi praktické mít možnost pohodlně si udržovat (například release od release) cizí zdrojové kódy a vlastní změny proti nim. Například dlouhodobě udržujeme nějaký větší patch, u kterého si chceme určovat historii včetně hrubého historického kontextu jednotlivých našich změn.

Když jsme poprvé importovali zdrojové kódy (příkazem cvs import), museli jsme si vymyslet nějaký tzv. vendor tag a release tag. Tehdy jsme je odbyli a něco jsme si vymysleli. Ovšem k čemu slouží ve skutečnosti?

CVS podporuje poměrně zajímavou funkci. Můžeme totiž v repository ve speciální větvi udržovat soubory z nějakého externího zdroje, přičemž v této větvi vlastně vždy bude obsah posledního importu. Tedy u každé nové verze projektu, který sledujeme, ji vezmeme a naimportujeme do našeho CVS. Pak můžeme v CVS s touto „originální“ verzí pracovat jako s každou jinou větví, tzn. slučovat si ji dle libosti například s HEAD větví apod.

Tato speciální větev (budeme jí říkat hezky anglicky vendor branch) vždy žije pod číslem 1.1.1 a vytvoří se v každém modulu – a to při prvním importu. Buď si jí již nemusíme všímat a další vývoj vést v hlavní větvi (jak jsme to dělali doteď), nebo si můžeme ve vendor branch udržovat originální verzi. Pokud pak „výrobce“ uvolní novou verzi, stačí, když ji znovunaimportu­jeme úplně stejným způsobem jako poprvé, jenom patřičně změníme release tag.

Je čas si ukázat, jak to všechno vlastně funguje v praxi. Nejdříve si vytvoříme zbrusu nový modul:

pasky@machine[1:0]~/test/foo-v$ echo 'brm' >kuk
pasky@machine[1:0]~/test/foo-v$ echo 'brm2' >kuk2
pasky@machine[1:0]~/test/foo-v$ cvs -d/home/pasky/cvsrepo import \
-m"REL1 by VENDOR" foo VENDOR REL1

N foo/kuk
N foo/kuk2

No conflicts created by this import
pasky@machine[1:0]~/test/foo$ cvs -d/home/pasky/cvsrepo co -d../foo-co foo
cvs checkout: Updating ../foo-co
U ../foo-co/kuk
U ../foo-co/kuk2 

Teď máme data v modulu foo – originální verzi v adresáři foo-v, verzi z CVS pak v adresáři foo-co.

A dostáváme se konečně ke specialitě těchto větví. Pokud totiž nemáme na HEADu vlastní modifikace, automaticky se vždy při checkoutu a update použije poslední revize z vendor branche. To z nich činí mimořádně praktický nástroj například pro údržbu vlastních modifikací nějakého software.

pasky@machine[1:0]~/test/foo-v$ echo zmena ve vendor branchi >kuk2
pasky@machine[1:0]~/test/foo-v$ cvs -d/home/pasky/cvsrepo import \
-m"REL2 by VENDOR" foo VENDOR REL2

U foo/kuk
U foo/kuk2

No conflicts created by this import
pasky@machine[1:0]~/test/foo-v$ cd ../foo-co
pasky@machine[1:0]~/test/foo-co$ cvs up
cvs update: Updating .
U kuk2 

Pokud již jsme nějaké změny na souboru provedli, import nás na to upozorní a rovnou nám i nabídne předvyplněný příkaz pro slučovací checkout -j, který nám do HEAD absorbuje změny provedené v „upstreamu“.

pasky@machine[1:0]~/test/foo-co$ echo moje zmena >kuk
pasky@machine[1:0]~/test/foo-co$ cvs ci -m"Moje zmena."
cvs commit: Examining .
Checking in kuk;
/home/pasky/cvsrepo/foo/kuk,v  <--  kuk
new revision: 1.2; previous revision: 1.1
done
pasky@machine[1:0]~/test/foo-co$ cd ../foo-v/
pasky@machine[1:0]~/test/foo-v$ echo dalsi zmena ve vendor branchi >kuk
pasky@machine[1:0]~/test/foo-v$ cvs -d/home/pasky/cvsrepo import \
-m"REL3 by VENDOR" foo VENDOR REL3

C foo/kuk
U foo/kuk2

1 conflicts created by this import.
Use the following command to help the merge:

        cvs -d /home/pasky/cvsrepo checkout -j<prev_rel_tag> -jREL3 foo 

Zkusíme si ho tedy v adresáři foo-co provést:

pasky@machine[1:0]~/test/foo-co$ cvs -d /home/pasky/cvsrepo checkout -jREL2 -jREL3 foo

cvs checkout: Updating foo
U foo/kuk
RCS file: /home/pasky/cvsrepo/foo/kuk,v
retrieving revision 1.1.1.1
retrieving revision 1.1.1.2
Merging differences between 1.1.1.1 and 1.1.1.2 into kuk
rcsmerge: warning: conflicts during merge 

Nyní se tedy již stačí jen postarat o konflikty a máme vystaráno. Není nic snadnějšího, než si třeba kdykoliv vyrobit patch, který do zdrojových kódů projektu zahrne naše změny: cvs diff -u -r VENDOR

bitcoin_skoleni

Pozn.: CVS nám umožňuje si udržovat i více takovýchto větví najednou. Lze k tomu využít lokální parametr -b příkazu import, například -b 1.1.3. Ovšem něco podobného zřejmě využijeme pouze zřídka, proto se tím nebudu blíže zabývat. Jen pamatujte, že si musíte dávat pozor, aby vám seděl parametr -b a jméno vendor branche, do které importujete! 


Příště nás čeká popis administrace CVS, nejdříve z hlediska příkazu cvs admin, poté si blíže popíšeme administrativní soubory v modulu CVSROOT.