Subversion on vakiintunut meillä töissä web-projektien versiohallintatyökaluksi. Se on kuitenkin tuskallisen hankala, jos on tarvetta hallita brancheja ja yhdistellä niiden välillä muutoksia. Edes Subversion 1.5:n merge tracking ei tunnu parantavan tilannetta ratkaisevasti, vaan edelleen merget menevät kovin helposti sekaisin ja branchien yhteyteen tallentuu ikävää metadataa, joka voi mennä rikki.

Git taas hoitaa branchit paljon paremmin. Siinä missä Subversion käsittelee brancheja tavallisina hakemistoina, joille on vain annettu erityinen merkitys, Git ymmärtää sisäisesti mitä branch oikeasti tarkoittaa.

Kuvailen tässä lyhyesti miten meillä normaalisti toteutetaan Subversion-repositoryt ja käyn sitten läpi vastaavat operaatiot Gitillä. Hakemistorakenne on Subversionissa tällainen:

/trunk          # Kehitysversio
/branches/1.0   # Jäädytetty 1.0
/branches/1.1   # Jäädytetty 1.1
/branches/kennu # Kehittäjän oma työversio

Kaikki kehitys tapahtuu trunkissa ja uusista julkaisuista tehdään uudet 1.x-branchit. Ne on helppo viedä tuotantoon svn switch -komennolla tuotantopalvelimella. Tarvittaessa esimerkiksi 1.0-branchiin voidaan tehdä pieniä korjauksia, jotka saadaan käyttöön tuotantopalvelimelle svn update -komennolla.

Suurinta päänvaivaa aiheuttavat kehittäjäkohtaiset branchit. Esimerkiksi minulla voi olla käytössäni /branches/kennu, jossa teen omaa kehitystyötäni ja committaan sen lopuksi yhteiseen trunkiin. Tässä kohtaa Subversion alkaa helposti yskiä, kun sen pitäisi seurata mitä muutoksia olen tehnyt omaan branchiini. Samalla sen pitäisi seurata mitä muutoksia trunkiin on ilmestynyt ja osata yhdistää ne minun branchiini. Jos tekee jotain vähän eksoottisempaa tai sählää propertyjen kanssa, saattaa Subversionin merge tracking tippua kärryiltä.

Hakemistohierarkia (trunk ja branches) Gitissä

Gitissä ei ole Subversionia vastaavaa hakemistopuuta trunkille ja brancheille. Sen sijaan käytössä on vain yksi hakemistohierarkia, joka on "aktiivinen" tietyssä branchissa. Oletuksena tämä branch on master, joka vastaa Subversionin trunkia. Uusia brancheja voidaan luoda git branch -komennolla tähän tapaan:

$ git branch             # listaa nykyiset branchit
* master
$ git branch 1.0         # luo uuden branchin nykyisestä masterista
$ git push origin 1.0    # työntää uuden branchin keskitettyyn repositoryyn
$ git branch             # nyt listassa näkyy uusi branchi ei-aktiivisena
  1.0
* master

Kehittäjäkohtaisia brancheja Gitissä ei tarvita lainkaan, sillä kehittäjillä on käytettävissään omat paikalliset Git-repositoryt. Normaali kehitystyö tapahtuu tässä paikallisessa repositoryssa ja siihen voi commitata niin usein kuin haluaa. Kun koodi on valmis yhdistettäväksi yhteiseen master-branchiin, se vain työnnetään git push -komennolla keskitettyyn repositoryyn.

Korjausten tekeminen vanhaan branchiin

Aiemmin luotujen branchien välillä hyppely työhakemistossa on helppoa git checkout -komennolla. Kehittäjän työskennellessä normaalisti master-branchissa hän voi siirtyä tekemään korjauksia 1.0-branchiin, commitata muutokset ja työntää ne sitten keskitettyyn repositoryyn. Lopuksi pitää muistaa palata takaisin master-branchiin:

$ git checkout 1.0
$ # (tehdään korjauksia tiedostoihin)
$ git commit -a -m 'Korjaus suoritettu'
$ git push
$ git checkout master

Ainoa jippo tässä on se, että jos kehittäjä ei ole aiemmin checkoutannut jonkun toisen tekemää branchia keskitetyltä repositorylta, niin se pitää ensin tehdä näin:

$ git checkout -b 1.0 origin/1.0
Branch 1.0 set up to track remote branch refs/remotes/origin/1.0.
Switched to a new branch "1.0"

Tämä tarkoittaa, että Git on luonut paikallisen branchin nimeltä "1.0", joka seuraa keskitetyn repositoryn branchia "1.0". Vastedes aina, kun ajetaan git pull, niin kyseiseen branchiin tehdyt muutokset päivittyvät kehittäjän työhakemistoon.

Tuotantopalvelimen päivittäminen ("svn switch" Gitissä)

Päivittäminen aloitetaan virkistämällä tuotantopalvelimen paikallinen repository ajan tasalle. Käyttämällä git fetch -komentoa (pullin sijaan) mahdolliset muutokset eivät vielä heijastu työhakemistoon:

$ git fetch          # noudetaan päivitykset keskitetystä repositorysta
$ git branch         # toistaiseksi näkyy vielä vain paikallinen master-branch
* master

Keskitetyyn repositoryyn ilmestyneet uudet branchit saa näkyviin -r-optiolla:

$ git branch -r
  origin/1.0
  origin/HEAD
  origin/master

Uusi branchi otetaan käyttöön checkout-komennolla seuraavasti:

$ git checkout -b 1.0 origin/1.0
Branch 1.0 set up to track remote branch refs/remotes/origin/1.0.
Switched to a new branch "1.0"

Nyt myös paikallisessa branchilistassa näkyy 1.0 ja se on aktiivinen:

$ git branch
* 1.0
  master

Näin uusi versio 1.0 on viety tuotantoon. Jatkossa pelkkä git pull päivittää tuotantoon kaikki siihen tehdyt korjaukset.

Korjausten vieminen masterista aiempaan branchiin

Usein tulee eteen tilanne, että jokin pieni bugi on korjattu master-branchissa, ja se pitäisi saada vietyä heti myös tuotannossa olevaan branchiin. Jos korjaus tehtäisiin toisin päin (korjattaisiin 1.0-branchiin), se olisi Gitissä helppoa mergettää masteriin merge-komennolla:

  $ git checkout master # siirrytään master-branchiin
  $ git merge 1.0       # yhdistetään masteriin muutokset, joita 1.0:aan on tehty
  $ git push       # työnnetään tämä muutos keskitettyyn repositoryyn

Mutta jos korjaukset onkin tehty masteriin, niitä ei kaikkia haluta viedä tuotantoon, koska joukossa voi olla uusia ominaisuuksia. Avuksi tulee git cherry-pick -komento, jolla historiasta voidaan ottaa mikä tahansa masteriin tehty muutos ja kopioida se haluttuun branchiin. Ensin oikea muutos pitää etsiä git log -komennolla:

$ git log
commit 52f4aea341a15050782b65189f5fc471a3f834c3
Author: Kenneth Falck
Date:   Tue Aug 25 19:30:33 2009 +0300

    New feature

commit 64be9ce4a7749d4400be69194b2493a4e50db67b
Author: Kenneth Falck
Date:   Tue Aug 25 19:30:14 2009 +0300

    Bugfix

Tästä nähdään, että haluttu korjaus on commitissa 64be9ce4a7749d4400be69194b2493a4e50db67b. Nyt se voidaan viedä tuotantobranchiin:

$ git checkout 1.0
$ git cherry-pick 64be9ce4a7749d4400be69194b2493a4e50db67b
$ git push

Olennaista on tietysti, että kehittäjä on alkujaan commitannut bugikorjauksen omana erillisenä muutoksenaan. Jos tästä on epävarmuutta, niin commitin sisällön voi aina tarkistaa git show -komennolla, joka näyttää sen diff-muodossa:

$ git show 64be9ce4a7749d4400be69194b2493a4e50db67b
commit 64be9ce4a7749d4400be69194b2493a4e50db67b
Author: Kenneth Falck
Date:   Tue Aug 25 19:30:14 2009 +0300

    Bugfix

diff --git a/NEWFIX.txt b/NEWFIX.txt
index ae4d5a1..84aacae 100644
--- a/NEWFIX.txt
+++ b/NEWFIX.txt
@@ -1 +1,2 @@
+The bug was now fixed.

Yhteenveto

Git on toimintatavaltaan paljon fiksumpi ja joustavampi työkalu kuin Subversion, mutta se vaatii jonkin verran totuttelua. Tässäkin artikkelissa mainitut komennot voivat tuntua ensin kryptisiltä, mutta niissä on loppujen lopuksi hyvin selkeä logiikka, joka aukeaa sitä paremmin mitä enemmän Gitiä käyttää. Paras tapa opiskella on tehdä itselleen yksinkertainen leikki-repository ja kokeilla miten branchit ja merget käytännössä toimivat.

Published 25.8.2009