Mercurial - első tapasztalatok egy elosztott verziókezelővel

Már régóta szemezek az elosztott verzió kezelő rendszerekkel. Egyfelől azért, mert úgy látom ez a trend (néhány év múlva az elosztott verzió kezelők fogják átvenni az  SVN helyét, úgy ahogyan annak idején az SVN került az akkoriban elterjedt CVS helyére), másfelől magam is szembesültem már az SVN néhány hátrányával, amiken az elosztott verziókezelő rendszerek felülemelkednek. No de mi is az az elosztott verzió kezelő rendszer?

Feltételezem, hogy az olvasó találkozott már  központi (centralizált) verziókezelő rendszerrel. Ilyen például a hagyományos SVN, vagy a CVS. Központi verziókezelő esetén ugye van egy központi szerver (pl. SVN szerver), a felhasználók pedig innen húzzák le, illetve ide tolják fel a saját változtatásaikat. Minden feltöltést egy letöltésnek kell megelőznie, ami az adott felhasználó gépén összefésüli a szerveren lévő és a lokális változatot. Ha az összefésülés automatikusan megoldható (általában megoldható), akkor azt a rendszer el is végzi, ha nem, akkor kézzel kell összefésülnünk a változatokat, majd az eredményt feltöltjük, és onnantól az lesz az aktuális változat. Az egyes változatokat vissza lehet keresni, egy egy állományt vissza lehet állítani a régi állapotra, és változatonként pontosan nyomon követhetjük, hogy hol mi változott. Nagyon röviden valami ilyesmiről szól egy központi verziókezelő.

Elosztott (decentralizált) verziókezelő esetén más a helyzet. Ott egyetlen központi tároló (repository) helyett minden felhasználónak van egy saját külön tárolója a gépén. A külön tárolónak külön verziókezelése van, tehát nem kell minden egyes változást a központi szerverre tölteni. Itt rögtön látszik is az elosztott verziókezelők egyik nagy előnye az SVN-el szemben. SVN esetén ha valami nagy feladaton dolgozunk, és a feladat megoldása közben menteni szeretnénk egy egy mérföldkövet, azt csak az egyetlen központi tárolóba menthetjük. Így választhatunk, hogy vagy félkész dolgokat mentegetünk a központi tárolóba (ezzel esetleg működésképtelen vagy hibás verziókat hozva létre), vagy mindaddig nem készítünk verziót, amíg el nem készültünk a feladattal (így viszont a feladat ideje alatt elveszítjük a verziókezelésből eredő előnyöket). Mindkét megoldás rossz, de mindenki saját szája íze szerint választhat, hogy melyik a kisebbik. Elosztott verziókezelés esetén nincs ilyen gond, hiszen a saját lokális tárolójába mindenki olyan verziókat hoz létre, amilyet akar (lehetnek ezek között működésképtelen, félkész állapotok is), és csak akkor tölti fel az egészet a központi tárolóba, ha kész van a feladattal (esetleg le is tesztelte, stb.). Így a központi tárolóban mindig egy stabil, használható változat van. Ez már önmagában nagy előny, de mint látni fogjuk, ez csak egyetlen a sok közül. Tehát egy központi tároló helyett itt több tároló van, és mivel nincs is olyan fogalom, hogy "központi" tároló, ezekből a lokális tárolókból tetszőleges hierarchiák kialakíthatóak. Tehát pl. egy projekt 2 nagyobb feladatra osztható, és mindkét feladaton dolgozik 2-2 fejlesztő. Ebben az esetben a két fejlesztőnek van 1-1 saját tárolója, ezekből mindig az adott feladat tárolójába szinkronizálnak, és ha az adott feladat elkészül, akkor tolják ki az egészet a "központi", stabil tárolóba. De hasonló módon akár az is megoldható, hogy 2 "központi" tárolót tartunk fenn. Az első egy "testing", a második egy "stabil" változat. Ha elkészül egy feladat, akkor az a "testing" rendszerbe kerül. Itt valaki leteszteli, és ha megfelelőnek találja, kihelyezi a stabil tárolóba. Ez csak néhány példa a teljesség igénye nélkül, de sok más elrendezés is elképzelhető. Például ami nekem nagyon tetszett, hogy nyílt forrású projektek esetén ha a forráskód tárolására SVN-t használnak, akkor két módon lehet beszállni a fejlesztésbe. Az egyik, hogy elérési jogot kérünk a tárolóhoz. Itt ugye az a dolog veszélye, hogy akinek ilyen jogot adunk, abban nagyon meg kell bízni, hisz kontrollálatlanul telepakolhatja a kódot mindenfélével. A másik eset, hogy a felhasználó elvégzi a módosítást, majd generál egy patch-t, amit elküldhet a projekt vezetőjének, aki átnézi azt, és kézzel befésüli az SVN-be. Ez viszont elég kényelmetlen. Elosztott verziókezelő esetén a dolog igen gördülékenyen megy, mivel a felhasználó csinál egy másolatot (klónt) a központi kódbázisról, elvégzi a változtatásokat (itt használhatja a lokális változatának saját verziókezelését), majd ha elkészült, értesíti a projekt gazdáját, hogy húzza át a módosítást a fő kódbázisba. Ez tulajdonképpen olyan mint mikor az ember patch-et küld egy SVN-es projekthez, de sokkal egyszerűbb, és gördülékenyebb.

A legismertebb ilyen elosztott verziókezelő a Git. Ezt a linux kernel fejlesztéséhez hozták létre, de mostanra már sokan mások is használják. Git-ben tárolják pl. az Android forráskódját, a Diaspora-t, az Appcelerator forráskódját, a Facebook is ezt használja a nyílt forrású projektjeihez, és még több ezer projekt, amiket jellemzően a GitHub-on hosztolnak. (A GitHub egy Git alapú code hosting szolgáltatás, olyan mint a Google Code Hosting, vagy a Sourceforge, bár sok szempontból sokkal fejlettebb.) A Git után szorosan második helyen áll a Mercurial. Nem kell szégyenkeznia a Git mellett, hisz olyan projektek használják, mint a Mozilla böngésző, Python programozási nyelv, OpenJDK, Netbeans, stb. Ráadásul a Google Code Hosting-on is használhatjuk az SVN alternatívájaként. Valójában nekem is szimpatikusabb a Mercurial, mint a Git, mivel a Git egy scriptekből és programokból összelapátolt valami, míg a Mercurial tulajdonképpen egy Python program, így ha kell, ebbe jobban bele lehet nyúlni (pl. plugint fejleszteni hozzá), ráadásul az egész valahogy jobban egyben van. Van még néhány elosztott verziókezelő (pl. Bazaar, amit az Ubuntu Linux használ), de mind közül nekem a Mercurial tetszett legjobban, így erről fogok írni.

Akit mélyebben érdekel a Mercurial, a http://hginit.com/ címen találhat egy nagyon jó angol nyelvű összefoglalót, de én is írok róla néhány sort.  Ha ki szeretnénk próbálni ennek a verziókezelő rendszernek a képességeit, legegyszerűbb, ha letöltjük a TortoiseHg klienst. Ez beépül a Windows jobb gombos menüjébe, így a fájlkezelőben könnyen adminisztrálgathatjuk a tárolóinkat. A kísérletezéshez hozzunk létre egy tárolót. Ehhez a TortoiseHg-ban van egy "Create repository here" menüpont. Az első dolog, amit észrevehetünk, hogy maga a tároló egy egyszerű könyvtár néhány speciális fájlal, tehát alapvetően nincs szükség szerverre a működéshez. Ez már önmagában is hasznos lehet. Ha kész a tároló, hozzunk is létre benne néhány fájlt. Eztán ha a tároló könyvtárán jobb gombot nyomunk, feltűnik az ismerős commit parancs. Ezzel az SVN-hez hasonlóan rögzíthetjük a változásokat, és létrehozhatunk egy verziót, ami az SVN-el ellentétben nem egy szerveren, hanem a könyvtárban lévő egyik mappában jön létre. Készítsünk még 1-2 verziót. Van egy Hg Workbench menüpont is, amivel szépen fában látjuk a változatokat, megnézhetjük a különbségeket, stb. Eztán jöhet a trükk, ami az elosztott verziókezelők legnagyobb előnye.  A menüben találunk egy "Clone" parancsot. Ezzel a teljes tárolót lemásolhatjuk még egy példányban, verzió történettel, mindennel együtt. A clone után mér két tárolónk van (két sima könyvtár néhány speciális fájlal és mappával). Most csináljunk néhány verziót az egyik, majd néhány verziót a másik tárolóban. Ez jelképezi a több fejlesztési ágat. A gyakorlatban ez úgy néz ki, hogy minden egyes fejlesztő klónozza magának a tárolót, és abba dolgozik. Szóval csináljunk néhány verziót mindkét tárolóban. Ha ezzel megvagyunk, akkor jön az, hogy a másodlagos (pl. fejlesztési) tárolót fésüljük vissza az elsődleges tárolóba. Ezt két módon tehetjük meg. Vagy a cél tárolóba húzzuk (pull) a változásokat a forrás tárolóból, vagy a forrás tárolóból nyomjuk (push) a cél tárolóba a dolgokat. Mindkét módozatnak van értelme. A push-ra jó példa, amikor mondjuk egy fejlesztő befejezett egy feladatot a saját lokális tárolójában, és ezt betolja a központi fejlesztői tárolóba (kb. az SVN commitnak felel meg). A pull használata pedig például akkor lehet hasznos, ha a stabil szál felelőse áthúzza a változásokat a fejlesztői szálból. Például GitHubon (ez Git alapú, de a mechanizmus ugyanaz) úgy szállhatunk be egy nyílt forrású projekt fejlesztésébe, hogy klónozzuk a projekt tárolóját, megcsináljuk a változtatásokat (ehhez semmilyen interakció nem kell ugye a projekt gazdájával), majd ha megvagyunk, küldünk egy pull request-et (ez kb. egy üzenet, hogy "kérlek, húzd át a változásokat az én tárolómból") a projekt gazdájának, aki átnézi, hogy mit változtattunk, és ha mindent rendben talál, áthúzza a változásokat a projekt fő tárolójába. Ez nagyon gördülékeny módja a nyílt forrású projekthez való hozzájárulásnak, sokkal kényelmesebb, mint SVN hozzáférést kunyerálni, vagy patch-eket küldeni. A példa kedvéért tehát nyissuk meg az elsődleges tárolónkat a Hg Workbench-ben, és húzzuk át a változásokat a másodlagosból. Ha ez megvan, láthatjuk a Workbench-ben, hogy a másodlagos tároló történettel, mindennel együtt egy új ágként megjelent a mi tárolónk történetében (magyarul látszik, hogy ott ketté vált a fa). Ez már majdnem jó, már csak egyesíteni kell a két ágat. Ehhez van a workbench-ben egy merge művelet. Ez igazából ugyanúgy megy, mint SVN-ben a merge-ölés, ha lehet, automatikusan merge-öl a rendszer, ha nem, akkor kézzel kell ezt megtenni. (Elvileg a Mercurial mindenféle metaadatot gyűjt a merge segítésére, de ezt a részét annyira nem próbáltam.) Ha megvan a Merge, jöhet egy commit, és rögtön egy szálon megy tovább az egész. Tehát ha a fejlesztés végén ránézünk a fára, lépten-nyomon kis elágazásokat és visszacsatolásokat fogunk látni, amik az egyes fejlesztések.

Úgy nagyon dióhéjban ennyi lenne a Mercurial bemutatása. Feltűnhetett, hogy minden műveletet a fájlrendszerben végeztünk, de ahogyan SVN esetén, természetesen itt is megvan a lehetősége annak, hogy egy repository-t egy szerveren tároljunk, amit HTTP-n, HTTPS-en, vagy SSH-n keresztül lehet elérni. A dolgok mechanizmusán ez nem sokat változtat, egyszerűen az ilyen tároló nem a fájlrendszerben van, hanem egy távoli szerveren, de ugyanúgy lehet klónozni, lehúzni (pull) róla a változásokat (ami az SVN update-hez hasonló), vagy felnyomni (push) oda a változásokat (ami az SVN commit-hoz hasonló). Célszerűen csak a fejlesztő lokális tárolóját érdemes a fájlrendszerben tartani, minden olyan tárolót, amit többen használnak (közös fejlesztési ág, stabil ág, stb.), szerveren érdemes tartani.

Remélem ezzel a kis ismertetővel sikerült kicsit kedvet csinálni az elosztott verzió kezelő rendszerek, azon belül is a Mercurial használatához.