tirsdag den 2. oktober 2007

Versionsstyring

(Opdateret 4. februar 2008: Der er kommet en opfølgning om branches)

Indledning

Så vidt jeg husker, så er versionsstyring danske oversættelse af revision control. Man undgår normal ordet version og det er sikkert for at skelne det i forhold til software configuration management, som jo er det at styre forskellige versioner af software - og typisk endda i forskellige konfigurationer. Revision control er det, som hjælper os med at kontrollere de små skridt, som tilsammen udgør den rejse, som configuration management kontrollerer.

Jeg vil her skrive lidt om værktøjer til versionsstyring. Jeg vil ikke skrive om, hvorfor - det antager jeg, at vi er enige om.

At det ikke altid har været en selvfølgelighed med versionsstyring, oplevede jeg på egen krop for efterhånden nogle år siden. Jeg sad som "schweizerkniv" (projektleder, arkitekt, udvikler, 2nd level supporter og underviser i en og samme person) på et produkt, som havde været længe undervejs. Faktisk havde der allerede været to andre personer på dette før mig og begge var forlængst over alle bjerge. Jeg spurgte derfor ofte mig selv om, hvordan en given del af programkoden kunne have været opstået. Derfor gik jeg til min chef og bad om et versionsstyringsværktøj.

"Hvorfor det?" lød svaret, "du er da alene på projektet". Ja, men nogle gange, så har man brug for at kunne gå tilbage til en tidligere version. "Jamen, kan du ikke bare gemme en kopi et sted på din harddisk?". Jo, men det ville være rart at kunne gå længere tilbage. "Hvad med at gemme flere kopier...?". Her gav jeg så op.

Nu får jeg min tidligere chef til at fremtræde lettere naiv, og det er uretfærdigt, for han var alt andet. Og man kan ikke ligefrem sige, at jeg præsenterede ham for det, der på nydansk hedder en god business-case. Jeg kan ikke fortænke ham i at se store umiddelbare udgifter, og kun magre og usikre gevinster langt ude i fremtiden. Jeg skulle - set i bagklogskabens tydelige skær - have forberedt mig bedre.

Men tiderne har heldigvis ændret sig, og i dag er versionsstyringsværktøjer allestedsnærværende - hvis man har glemt hvorfor, så kan man kigge i indledningen til denne artikel, for en god og dejlig kort introduktion (artiklen er i øvrigt også god at blive klog af, idet den sammenligner fire rimeligt almindelige versionsstyringsværktøjer).

Men selvom versionsstyring er blevet så udbredt, at det nærmest falder i et med tapetet, så er der stadig nogle overvejelser, som man skal gøre sig. Der er flere grundlæggende forskellige tilgange, med hver deres fordele og ulemper.

Granularitet

Det første man skal gøre sig klart er, hvad der er en grundlæggende enhed for versionsstyringen. Nogle værktøjer versionsstyrer filer (f.eks. CVS), mens andre værktøjer versionsstyrer hele arkivet (f.eks. Subversion).

Hvis ændringer kun er helt uafhængige skridt i en fil ad gangen, så betyder denne skelnen ikke noget, men når ændringer begynder at spænde over flere filer ad gangen, så vil det være en fordel at kunne håndtere ændringerne samlet (i såkaldte change sets).

Værktøjer, som versionstyrer hele arkivet, vil naturligt kunne tilbyde atomare (dvs. "alt eller intet") ændringer, og ændringerne vil være samlet naturligt. I filbaserede værktøjer, vil kan kunne opleve at ændringer kun sker halvt, hvis der er problemer undervejs, og man kan efterfølgende kun gætte sig til sammenhængen ud fra f.eks. tid og kommentar til ændringen (nyere versioner af CVS forsøger at lappe på dette ved at give et unikt "commitid" til alle filer, som sendes til CVS i samme kommando).

Atomare ændringer er specielt vigtigt, hvis man ønsker at håndhæve forretningsregler vha. f.eks. precommit-triggere - det kan ikke hjælpe noget, at man har fået opdateret de to første filer i et change set, hvis man under behandlingen er den tredje fil opdager, at hele ændringen skulle have været afvist.

Bemærk også, at selvom understøttelse af change sets er en stor hjælp til change management, så er det ikke nok. Ændringer vil tit opstå ad flere omgange, og den fulde beskrivelse af ændringen vil derfor være det samlede antal skridt.

Låsning vs. fletning

Den anden store skillelinje er i metoden til at undgå konflikter ved samtidige ændringer (helt uden versionsstyring vil det være den ændring, som gemmes sidst, der vinder...).

Den ene metode er den klassiske låsningssematik: Man kan kun fortage en ændring, når man forud har fået en eksklusiv ret til dette. Dette tvinger ændringerne til at ske serielt i forhold til hinanden, idet en ændring ikke kan starte førend den forgående er afsluttet. Ændringskonflikter vil derfor ikke eksistere. Ulempen er, at man med låsen kan komme til at blokere for andre, og der er de klassiske eksempler på deadlocks og låse som ikke bliver frigivet rettidigt (f.eks. pga. ferier). Låsning fungerer bedst, når der er høj granularitet, lavt overlap og kort låsningstid.

Den anden metode er fletning: Enhver kan foretage ændringer - til gengæld vil man efterfølgende kunne konstatere, når en ændring kommer til at konflikte med en anden ændring - og ansvaret for at løse konflikten påhviler den som kom sidst.

Den typiske måde at løse konflikten på, vil være fletning, som kan ske mere eller mindre automatiseret: hvis en ændring er foretaget i den øverste 1/3 af en fil, og en anden ændringen i den nederste 1/3, så vil en automatisk fletning typisk lade begge ændringerne ske, idet de ikke overlapper. Dette kan selvsagt medføre et resultat, som er absurd eller direkte forkert - i praksis sker det dog yderst sjældent, og hvis der er en passende stringent fortolkning - f.eks. fordi det, som er flettet, er kildetekst til et program - så vil det ofte være åbenlyst, når dette sker.

Fordelen er, at man aldrig bliver stoppet undervejs - medmindre at man rent faktisk render ind i en konflikt.

Ulempen ved fletning er, at det ikke er alt som umiddelbart lader sig flette. Ved billedfiler vil det f.eks. sjældent være indlysende, hvordan to resultater skal kombineres (nogle billedformater indeholder originalen uændret samt en liste af efterfølgende operationer, og i så fald vil man måske kunne flette - men det er absolut specieltilfælde).

Et andet eksempel er typiske tekstbehandlingsdokumenter. MS-Word dokumenter lader sig trods alt manuelt flette i Word, så der kan man slippe omkring det med en vis egen-indsats. OpenOffice's ODF format lader sig heller ikke umiddelbart flette: ODF dokumenter er en zip'pet samling af XML-dokumenter - zip-filer lader sig i praksis kun flette ved at flette indholdet, og XML-dokumenter er godt nok tekstfiler, men jeg mangler stadig at se en god flettealgoritme til XML-dokumenter (hint: tekstbaserede flettealgoritmer er som oftest liniebaserede, og det er XML ikke - derimod er XML hierarkisk i opbygningen, så det hjælper heller ikke at "normalisere" dokumentet).

Central vs. decentral

Den tredje store forskel imellem implementationerne er opstået relativt fornyligt, nemlig decentrale systemer.

Førhen var det altid sådan, at der var et centralt "opbevaringssted" (repository). De fleste værktøjer tillod en eller flere arbejdskopier, men der var altid et sted, hvor man dels kunne finde originalen, dels havde en autoritativ kilde til historikken.

I den centrale løsning vil man hente de seneste opdateringer fra det centrale repository (hvor andre tidligere har sendt deres ændringer hen), og samtidig er det her, at man selv sender sine ændringer hen, så andre også kan få glæde af dem. Der er altså et fast centralt holdepunkt og en lang række satellitter rundt om dette.

Nu er der så kommet decentrale eller distribuerede løsninger til, hvor enhver kopi som udgangspunkt er lige så god som alle andre kopier.

I den decentrale løsning vil man kunne hente ændringer et vilkårligt andet sted fra (hvis dette medfører konflikter vil man selv skulle løse dette) og tilsvarende vil man kunne sende sine ændringer vilkårlige steder hen (inden man kan gøre dette, så vil man selv skulle løse eventuelle konflikter). Der er altså ingen fast rollefordeling. Hvor den centrale løsning nærmest er et hjul med eger, så er den decentrale løsning nærmere et netværk - og vel at bemærke ikke et fast netværk, men derimod mere ad hoc baseret.

Det er oplagt, at modellen med et centralt repository lader sig realisere i den decentrale model (hvis alle er enige om, at lade en knude være "master"). Man kan også forstille sig et hierarki i flere niveauer, hvilket kunne bruges til at modellere states, som f.eks. "under udvikling", "under kvalitetssikring" og "produktionsmodent".

Samtidigt er en decentral udgave af den centrale model langt mindre sårbar. Hvis den centrale knude falder ud - permanent eller midlertidigt - så kan de tilbageværende knuder reorganisere sig og køre videre fra det punkt, som svarer til deres kollektive viden om tilstanden inden udfaldet.

Eller med andre ord: den decentrale model "...gives you plenty of rope to swing with - and plenty of rope to hang yourself!".

Afsluttende bemærkninger

Det lyder indlysende, men det er faktisk vigtigt at kende sin implementation godt.

Et eksempel er, at det med CVS er nemt at finde ud af, hvor slettede filer oprindeligt lå - man laver bare en søgning på den del af serverens filsystem, hvor repository ligger, og når man finder filen i en "Attic"-folder, så ser man, hvor denne folder er placeret.

Et andet eksempel er, at CVS er case-sensitiv/in-sensitiv afhængigt at det underliggende filsystem. Arbejder man med forskellige filsystemer på hhv. server og klient, så vil det være en god ide, at have en klippefast konvention for case.

Et tredje eksempel er, at man i CVS kan slette eller flytte filer permanent inkl. historik, ved at manipulere filsystemet på repository direkte (det ødelægger til gengæld godt og grundigt muligheder for at reproducere tidligere versioner (forstået i videste forstand) ud fra reposity...!)

Et fjerde eksempel er, at Subversion i fsfs formatet for repository gemmer hele changesettet i en enkelt fil - det er altså ikke muligt at committe en ændring, som samlet set er større end den største filstørrelse på det underliggende filsystem, også selvom de enkelte ændringer ikke overstiger den.

Et femte eksempel er, at Subversion i working copy gemmer en kopi svarende til repositoryversionen til sammenligning (det gør dels at netværkstrafikken bliver minimeret, idet der kan kommunikeres med deltaer, dels at ændringsdetekteringen bliver mere robust, men også at pladsforbruget bliver fordoblet).

Hvordan finder man ud af det? Som så meget andet ved at få praktiske erfaringer. Så der er ikke andet for end at komme i gang med et eksperimentere, vel?

Ingen kommentarer: