PROSJEKT
Del artikkel
Skrevet av: Edmond Peci, Jonas Rydgård og Markus Rud
PROSJEKT
Del artikkel
Skrevet av: Edmond Peci, Jonas Rydgård og Markus Rud
Jo flere kokker, desto mer søl gjelder også i programvareutvikling. Programvareutvikling som involverer større utviklingsteam kan fort bli uoversiktlig og kaotisk hvis alle utviklerne følger sine egne rutiner og prosedyrer. Flere mennesker jobber på samme programvare, og innen du er ferdig med å legge til din funksjonalitet, så kan programvaren i prosjektet ha endret seg mange ganger. Denne artikkelen presenterer ulike metoder for samarbeid innenfor versjonskontrollsystemet Git, og hvordan man kan jobbe med dette i team av ulik størrelse. Artikkelen tar for seg elegante løsninger som gir svært oversiktlige prosjekter, men også mer skumle kommandoer som kan føre til stor ødeleggelse i prosjektet dersom de ikke brukes rett.
Det finnes i dag mange versjonskontrollsystemer slik som Git, Helix Core, SVN og Mercurical. Denne artikkelen fokuserer utelukkende på Git siden dette er det mest brukte versjonskontrollsystemet og er støttet av en rekke tredjeparter. Git fungerer i bunn og grunn ved at det sporer filene dine og lager øyeblikksbilder, såkalte “commits”, av tilstanden på de ulike filene i prosjektet. Dette gir en oversikt over ulike endringer du har gjort mellom øyeblikksbildene og ofte er det også koblet opp til en ekstern skylagring eller fjernserver for sikkerhetskopiering. Git-verktøyet kobles mot en spesifikk filmappe lokalt på datamaskinen der programvaren er lagret og når en da gjør endringer i en fil, sammenliknes dette opp mot forrige øyeblikksbilde så man enkelt kan se endringene. Så for å lage et nytt øyeblikksbilde, velger man filene man ønsker å legge til og genererer øyeblikksbildet. Øyeblikksbildet har typisk en kommentar som beskriver endringene, og en hash, altså en type ID, for å identifisere det. Dette gir muligheten til å navigere seg frem og tilbake mellom øyeblikksbilder om det skulle det oppstå problemer eller man ønsker å se detaljer om hva som ble implementert når.
Dette høres kanskje enkelt ut, men selv versjonskontroll kan fort bli uoversiktlig og komme ut av kontroll ved at for eksempel kode feilaktig slettes av en annen i teamet eller at alt slutter å fungere uten at noen skjønner hvorfor . Det kan også bli mange øyeblikksbilder, slik at det blir vanskelig å navigere frem og tilbake. I Git er det ofte flere ulike måter å gjennomføre den ulike funksjonaliteten på og ulike utviklere har ofte forskjellige metoder og preferanser for hvordan man tenker det er best å gjøre det. Det er derfor viktig med noen felles spilleregler for hvordan ting skal gjøres innad i teamet. De neste avsnittene gir derfor noen gode og mye brukte praksiser for hvordan man unngår at det blir fullstendig kaos og tap av kode.
Det første og kanskje viktigste tipset er å benytte såkalte grener (branches). Dette er pekere til rekker av øyeblikksbilder som på samme måte som i et tre splitter seg ut ifra hverandre og stammen, men som i motsetningen til et tre, fletter også grener i Git seg sammen igjen. Disse grenene gir flere utviklere muligheten til å jobbe med ulike endringer og implementasjoner uten å ødelegge for hverandre ved at de jobber på adskilte grener. Hvis man skal implementere noe nytt, går man til hovedkoden, eller stammen i tre-analogien, splitter ut en ny gren og gjør endringene der. Når man er fornøyd med endringen, lager man et nytt øyeblikksbilde av tilstanden på grenen, og fletter grenen tilbake inn i stammen eller hovedgrenen som det ofte kalles i Git-prosjekter. Men som nevnt tidligere er det mange måter å gjøre ting på i Git, og sammenflettingen er ingen unntak. Så hvis hver utvikler plutselig følger sin egen flette-metode, ender historikken av sammenflettinger i prosjektet plutselig opp med å se ut som en skål med spagetti. For å forhindre at dette skjer, er det laget en rekke ulike arbeidsflyter for grener som passer på at historikken holdes strukturert. Tre populære arbeidsflytsmetodikker er stammebasert (trunk based), GitHub flyt (GitHub flow) og Git flyt (Git flow) og disse blir nærmere presentert i avsnittene under.
Stammebasert arbeidsflyt er basert på prinsippet om å utelukkende ha én gren som alle jobber på. Utsplittede grener bør ikke brukes med mindre det er helt nødvendig, og skal da ha kort levetid. Utgivelser av programvaren kan splittes ut til egne grener for at man enkelt kan finne frem til den og for at den ikke blandes med den fremtidige utviklingen. Fordelen med denne arbeidsflyten er at man effektivt kan gi raske utgivelser av programvaren og at den integreres enkelt med kontinuerlig integrasjon (CI). En visualisering av hvordan historikken vil blir seende ut er vist i figuren under hvor man ser hovedgrenen (Master), en midlertidig utviklingsgren (Feature) og en programvareutgivelse (Release). Ulempen med denne arbeidsflyten er at hvis det er mange utviklere i prosjektet, blir det fort en del kaos da alle jobber på samme grenen.
GitHub flyt er brukt og anbefalt av GitHub som en av de største plattformene for skylagring av Git-prosjekter. Denne arbeidsflyten følger 6 prinsipper:
Figuren under viser hvordan historikken typisk kan bli seende med GitHub flyt hvor man ser en hovedgren og to utviklingsgrener. I denne flyten jobber hver av utviklerne på hver sin gren og fletter denne først inn på hovedgrenen når alt er klart. Det gjør at man unngår at alle fletter inn sine halvferdige endringer og alt blir kaos.
Git-flyt er en hakket mer innviklet arbeidsflytsmetodikk sammenlignet med de overnevnte. I stedet for å bruke en eller to grener, så er hele fem grener tatt i bruk. De kalles hoved (Master), utvikling (Development), funksjon (Feature), programvareutgivelse (Release) og hurtigfiks (Hot Fix). De to viktigste grenene i Git-flyt er hoved og utvikling. Disse grenene bør låses slik at man ikke kan flette inn kode uten at det er gjort via en fletteforespørsel som en annen i teamet har godkjent. De tre andre grenene dekker andre bruksområder. Funksjons-grener lages hver gang ny funksjonalitet skal implementeres. Utgivelses-grener lages for en ny programvareutgivelse, og hjelper til med å isolere utviklingen av en ny versjon av programvaren fra eksisterende utgivelser. Når en utgivelse er klar, flettes øyeblikksbildet inn i både hovedgrenen og utviklingsgrenen for å både respektivt beholde utgivelsen og legge grunnlaget for videre utvikling. Hurtigfiks-grener er grener fra hovedgrenen som typisk implementerer fikser for små feil og små forbedringer på siste utgivelse. En figur for hvordan Git historikken kan bli seende ut ved bruk av Git-flyt, er vist under hvor man kan se at ting flettes på kryss og tvers mellom de ulike grenene.
Den muligens største fordelen med denne arbeidsflyten er at den tillater parallell utvikling og beskytter den utgitte programvaren for utilsiktede endringer. Hovedgrenen vil derfor alltid være stabil for utgivelse, mens utviklere kan jobbe med funksjonalitet i separate grener. Git-flyt kan derfor være nyttig ved større prosjekt med flere versjoner og utviklere. En ulempe med modellen er at det fort kan bli overkomplisert og tidkrevende med alle gren-kategoriene samt mange flettinger frem og tilbake mellom grenene.
De tre arbeidsflytmodellene beskrevet her tar for seg tre ulike nivåer av kompleksitet og muligheter, men de gir ikke en oppskrift som er skrevet i stein som vil fungere perfekte for ethvert prosjekt. Det viktigste er at prosjektet har en definert strategi for hvordan man skal jobbe med grenene og flette dem sammen uten at det blir for mye administrasjon i forhold til hva prosjektet krever.
Som nevnt flere ganger tidligere er flettingen en svært viktig del av Git og noe som kan gjøres på mange ulike metoder. I dette kapittelet beskrives to av disse flettemetodene nærmere og beskriver fordeler og ulemper med de forskjellige. Den mest vanlige metoden for å flette en utviklingsgren inn i den felles hovedgrenen, er ved å benytte funksjonen kalt git merge. Denne funksjonen kombinerer endringene i begge grenene som flettes og lager et nytt øyeblikksbilde av kombinasjonen. Dette er en god måte å flette inn endringer i kodebasen på ettersom det er en ikke-ødeleggende operasjon noe som betyr at all historikk blir bevart, og de eksisterende grenene endres ikke. Ulempen er at grenene kan bli forurenset av veldig mange øyeblikksbilder av sammenflettinger, og hvis du er en tilhenger av rene, oversiktlige og lineære Git historikktrær, kan det fort se ganske så kaotisk ut. Men, det finnes et verktøy for å håndtere dette, nemlig rebasing.
Git rebase kommandoen kan være litt mer mystisk og skremmende enn mange andre Git kommandoer, i hvert fall inntil man lærer å bruke den. Den er ikke like mye brukt som git merge kommandoen og en annen faktor som gjør rebasing litt skremmende er at den faktisk skriver om på Git historikken. Ved sikkerhetskopiering til skylagringen kan rebasing også kreve en tvungen opplastning slik at man risikerer å skrive over andres kode. Hvis kommandoen benyttes feil, så kan den faktisk gjøre stor skade i prosjektet. Men pust ut! Hvis man lærer å bruke den riktig, og følger den gylne regel for rebasing, så kan faktisk denne kommandoen bli din nye bestevenn!
Den gylne regel for rebasing: “Aldri utfør rebasing når du er på en offentlig gren.”
Vi skal komme tilbake til den gylne regel for rebasing senere. Så for nå, la oss se bort ifra alle fallgruvene til rebasing, og fokusere på hva det faktisk går ut på. Mens sammenfletting betyr å kombinere to grener til ett enkelt øyeblikksbilde, så er rebasing prosessen å flytte inn øyeblikksbilder fra en annen gren til halen av dine nye øyeblikksbilder. Dette er vist i figuren under, der den nye funksjonen (feature) er illustrert av de blå nodene, mens de grønne nodene illustrerer øyeblikksbildene til for eksempel hovedgrenen som vi ønsker å flette inn i. Som man ser fra sammenflettingen til venstre i bildet, så blir disse to grenene kombinert til ett nytt øyeblikksbilde illustrert ved en oransje node. For rebasing derimot, så observeres det at øyeblikksbildene fra hovedgrenen blir lagt til i historikken til funksjonalitetsgrenen til høyre i bildet slik at det ser ut som at øyeblikksbildene til hovedgrenen alltid har vært en del av funksjonalitetsgrenens historikk.
Nå ser alt bra ut i funksjonsgrenen, men det er en viktig forskjell mellom venstre og høyre del av bildet overfor. Funksjonalitetsgrenen er enda ikke flettet inn i hovedgrenen på den høyre delen. Ettersom øyeblikksbildene fra hovedgrenen allerede har lagt til i funksjonalitetsgrenen når man rebaser, så gir det jo mening at en fletting av disse to grenene bør være enkelt og problemfritt. Men, ved å ta en nærmere titt, så er det ikke fullt så enkelt. Man er nødt til å gjennomføre en såkalt “spole-fremover fletteforespørsel” (fast-forward merge) for å beholde en lineær historikk. Ettersom funksjonalitetsgrenen allerede har blitt rebasert (rebased) på hovedgrenen, så vil en enkel fletteforespørsel automatisk resultere i en spole-fremover fletting på grunn av den felles historikken. For sikkerhets skyld så kan man sette et flagg for fletteforespørselen, – –ff–only, som passer på å avvise fletteforespørselen hvis historikken mellom grenene avviker.
Det siste steget er å sende de sammenflettede øyeblikksbildene av hovedgrenen til fjernserveren. Men, siden Git nå ser at du har endret Git historikktreet ved å bruke rebase, så vil den avvise forsøket på å sende øyeblikksbildene til fjernserveren ettersom den lokale historikken ikke samsvarer historikken som ligger på fjernserveren. Git vil da foreslå at man henter alle endringene (pull) fra fjernserveren for å samkjøre historikken, men dette vil da generere et nytt øyeblikksbilde av sammenflettingen, som vist med den oransje noden i figuren over – nettopp det man vil unngå. Løsningen blir da å tvinge gjennom en opplastning (push) av øyeblikksbildet man innledningsvis ville sende til fjernserveren ved å sette tvangsflagget –f i opplastningen. Dette vil bytte ut den fjernlagrede av hovedgrenen med den lokale hovedgrenen og alt er tipp topp, eller?
Nå er det en veldig passende tid å gjenbesøke den gylne regel for rebasing. Fordi hvis noen andre sender et øyeblikksbilde til hovedgrenen etter du har rebaset, men før du får lastet opp, så vil deres øyeblikksbilde bli overskrevet og faktisk slettet som følge av opplastningen du tvang gjennom. Den gylne regel for rebasing, tar for seg nettopp denne problemstillingen. Den sier at man aldri skal benytte rebasing på en offentlig gren, som hovedgrenen da er på grunn av at flere utviklere fletter inn kode i grenen. Ulempen med regelen er at den er veldig rigid og i et større prosjekt vil man sannsynligvis ha en rekke offentlige grener man ønsker å rebase på. Løsningen på denne problemstillingen tilbys av en rekke Git servere, som for eksempel Bitbucket. Her kan man sette opp fletteforespørsler i brukergrensesnittet til serveren som inkluderer flagget – –ff–only og gjør at fletteforespørselen blir avvist dersom noen har lastet opp et nytt øyeblikksbilde til hovedgrenen siden din rebase. Ettersom fletteforespørselfunksjonaliteten håndteres av fjernserveren, så vil serveren alltid ha oversikt om øyeblikksbilder har blitt lastet opp i mellomtiden.
Versjonskontroll er et av de viktigere verktøyene for programvareutvikling, spesielt i team. Det gir god sporbarhet, mulighet til å organisere arbeidet med definerte arbeidsflyter og kontrollert versjonering av programvaren som blir utviklet. Det er likevel lett å redusere sporbarheten ved å forsøple Git historikktreet, enten som følge av for mange og dårlig beskrevne øyeblikksbilder, eller kaotiske flettingsforespørsler. Denne artikkelen har sett på noen arbeidsflytmodeller som kan være gode utgangspunktet for felles spilleregler i et team. Videre har artikkelen presentert forskjellen på ulike flette-metoder og hvordan man kan gjøre sammenflettingen i team ryddig, samt hva man bør være spesielt oppmerksom på for å opprettholde et så ryddig historikktre som mulig og unngå å slette andres historikk. Så nå er det bare å sette i gang og eksperimentere i Git, og husk på at så lenge du kun eksperimenterer lokalt på din egen datamaskin gjør du stort sett ingen skade da du alltids kan tilbakestille til siste versjon på fjernserveren.
R&D Manager