Det er mange forskjellige bruksområder for forgreninger og svn merge, og denne seksjonen beskriver de vanligste som du sannsynligvis vil komme over.
For å fullføre eksempelet vårt vil vi nå reise fram i tiden. Tenk deg at flere dager har gått, og mange forandringer har skjedd både i trunk og på den private grenen din. Tenk deg så at du er ferdig med arbeidet på den private grenen; funksjonaliteten eller feilrettingen er endelig fullført, og nå vil du flette alle forandringene fra grenen din til trunk så andre kan få glede av dem.
Så hvordan bruker vi svn merge i dette
tilfellet?
Husk at denne kommandoen sammenligner to trær og legger
forandringene inn i en arbeidskopi.
For å motta forandringene må du derfor ha en arbeidskopi av
trunk.
Vi går ut i fra at du enten har den originale liggende (helt
oppdatert), eller at du nylig hentet ut en fersk arbeidskopi av
/calc/trunk.
Men hvilke to trær skal sammenlignes? Ved første øyekast ser det innlysende ut: Bare sammenlign seneste treet fra trunk med det seneste treet fra forgreningen. Men pass på – denne antakelsen er feil, noe som mange nye brukere har brent seg på. Siden svn merge opererer på samme måte som svn diff, vil en sammenligning mellom trærne i nyeste trunk og gren ikke bare vise forandringene som du har gjort på grenen. En slik sammenligning viser alt for mange forandringer: Den vil ikke bare vise tilleggingene av dine forandringer på grenen, men også fjerningen av forandringer i trunk som aldri skjedde på din gren.
For å bare vise forandringene som skjedde på din gren, må du
sammenligne tilstanden ved starten av grenen din i forhold til
dens endelige tilstand.
Ved å bruke svn log på grenen kan du se at
den ble opprettet i revisjon 341.
Og den endelige tilstanden får du ved å bruke
HEAD-revisjonen.
Dette betyr at du vil sammenligne revisjonene 341 og
HEAD i forgreningskatalogen og legge disse
forskjellene inn i en arbeidskopi av trunk.
|
Tips |
|---|---|
|
En fin måte å finne revisjonen en gren ble opprettet i
(“basen” av forgreningen) er å bruke valget
Så hvis vi går videre i eksempelet,
$ svn log --verbose --stop-on-copy \
http://svn.example.com/repos/calc/branches/my-calc-branch
…
------------------------------------------------------------------------
r341 | bruker | 2002-11-03 15:27:56 -0600 (tor, 07 nov 2002) | 2 lines
Endrede filstier:
A /calc/branches/my-calc-branch (fra /calc/trunk:340)
$
Som forventet er den siste revisjonen skrevet ut av denne
kommandoen den revisjonen der
|
Her er den siste fletteprosedyren, og deretter:
$ cd calc/trunk $ svn update På revisjon 405. $ svn merge -r 341:405 http://svn.example.com/repos/calc/branches/my-calc-branch U integer.c U button.c U Makefile $ svn status M integer.c M button.c M Makefile # … Undersøk forskjellene, kompiler, test osv … $ svn commit -m "Flettet forandringer mellom r341:405 fra my-calc-branch til trunk." Sender integer.c Sender button.c Sender Makefile Sender fildata ... La inn revisjon 406.
Igjen, legg merke til at loggmeldingen veldig spesifikt nevner området av forandringer som ble flettet inn på trunk. Husk alltid å gjøre dette, fordi det er vital informasjon som du vil trenge senere.
For eksempel, tenk at du bestemmer deg for å fortsette
arbeidet på grenen en uke til, for å fullføre feilrettingen
eller en forbedring av den originale funksjonaliteten.
Depotets HEAD-revisjon er nå 480, og du er
klar til å utføre en ny fletting fra den private grenen din til
trunk.
Men som diskutert i “Beste praksiser for fletting” vil du ikke
flette forandringene du allerede har flettet før; du vil bare
flette alt “nytt” på grenen siden forrige gang du
flettet.
Trikset er å finne ut hva som er
nytt.
Første skritt er å kjøre svn log på trunk og se etter en loggmelding fra forrige gang du flettet fra grenen:
$ cd calc/trunk $ svn log … ------------------------------------------------------------------------ r406 | bruker | 2004-02-08 11:17:26 -0600 (søn, 08 feb 2004) | 1 line Flettet forandringer mellom r341:405 fra my-calc-branch til trunk. ------------------------------------------------------------------------ …
Aha!
Siden alle grenforandringene som skjedde mellom revisjonene 341
og 405 ble flettet til trunk som revisjon 406, vet du nå at du
bare vil flette grenforandringene etter dette – ved å
sammenligne revisjonene 406 og HEAD.
$ cd calc/trunk $ svn update På revisjon 480. # Vi ser at HEAD er 480 for øyeblikket, så vi bruker den for å utføre # flettingen: $ svn merge -r 406:480 http://svn.example.com/repos/calc/branches/my-calc-branch U integer.c U button.c U Makefile $ svn commit -m "Flettet forandringer mellom r406:480 fra my-calc-branch til trunk." Sender integer.c Sender button.c Sender Makefile Sender fildata ... La inn revisjon 481.
Nå inneholder trunk alle forandringene som ble gjort i den andre omgangen på grenen. På dette punktet kan du enten slette grenen (dette vil vi komme tilbake til) eller fortsette arbeidet på grenen og repetere denne prosedyren for etterfølgende flettinger.
En annen vanlig bruksmåte for svn merge
er å omgjøre en forandring som allerede er blitt lagt inn.
Tenk deg at du jobber glad og fornøyd på en arbeidskopi av
/calc/trunk, og plutselig finner ut at
forandringen du gjorde langt tilbake i revisjon 303, som
forandret integer.c, er helt feil.
Den skulle aldri vært lagt inn.
Du kan bruke svn merge for å
“angre” forandringen i arbeidskopien din, og
deretter legge inn de lokale forandringene til depotet.
Alt du trenger å gjøre er å spesifisere en
omvendt forskjell:
$ svn merge -r 303:302 http://svn.example.com/repos/calc/trunk U integer.c $ svn status M integer.c $ svn diff … # Sjekk at forandringen er fjernet … $ svn commit -m "Fjernet forandringen som ble lagt inn i r303." Sender integer.c Sender fildata . La inn revisjon 350.
En måte å tenke på en depotrevisjon er som en spesifikk
gruppe av forandringer (noen versjonskontrollsystemer kaller
disse forandringssett –
changesets).
Ved å bruke -r-valget kan du be svn
merge om å legge inn et forandringssett, eller et helt
område av forandringssett, til arbeidskopien din.
I vårt tilfelle med å omgjøre en forandring ber vi svn
merge om å legge inn forandringssett nummer 303
baklengs inn i arbeidskopien vår.
Husk at det å rulle tilbake en forandring som dette er
akkurat likt enhver annen svn
merge-operasjon, så du bør bruke svn
status og svn diff for å forsikre
deg om at arbeidet ditt er i den tilstanden du vil det skal være
i, og deretter bruke svn commit for å sende
den endelige versjonen til depotet.
Etter innleggingen er dette spesielle forandringssettet ikke
lenger representert i HEAD-revisjonen.
Og så tenker du kanskje:
Nåh, dette omgjorde vel egentlig ikke innleggingen?
Forandringen eksisterer fortsatt i revisjon 303.
Hvis noen henter ut en versjon av
calc-prosjektet mellom revisjonene 303 og
349, vil de se den gale forandringen, ikke sant?
Ja, det stemmer.
Når vi snakker om å “fjerne” en forandring, snakker
vi egentlig om å fjerne den fra HEAD.
Den originale forandringen eksisterer fortsatt i depotets
historie.
I de fleste situasjoner er dette greit nok.
De fleste er bare interessert i å følge HEAD
av et prosjekt uansett.
Det er imidlertid spesielle tilfeller der du virkelig vil
ødelegge alle spor etter innleggingen, kanskje la noen inn et
konfidensielt dokument ved en ulykke.
Dette er ikke så lett, viser det seg, fordi Subversion ble
spesielt designet for å aldri miste informasjon.
Revisjoner er uforanderlige trær som bygger på hverandre.
Det å fjerne en revisjon fra historien vil forårsake en
dominoeffekt som vil føre til kaos i alle etterfølgende
revisjoner og muligens gjøre alle arbeidskopiene
ubrukelige.[23]
Det som er fint med versjonskontrollsystemer er at
informasjon aldri går tapt.
Selv om du sletter ei fil eller en katalog, kan den være borte
fra HEAD-revisjonen, men objektet eksisterer
fortsatt i tidligere revisjoner.
Et av de vanligste spørsmålene nye brukere spør om, er:
“Hvordan får jeg den gamle fila eller katalogen min
tilbake?”.
Første skritt er å definere nøyaktig hvilket element du skal prøve å hente tilbake. Her er en nyttig metafor: Du kan tenke på hvert objekt i depotet som om det eksisterer i et slags todimensjonalt koordinatsystem. Det første koordinatet er et spesifikt revisjonstre, og det andre koordinatet er en sti inne i dette treet. Så hver versjon av fila eller katalogen din kan bli definert som et spesifikt koordinatpar.
Subversion har ingen Attic-katalog som
CVS har,[24] så du må bruke svn log for å
finne det eksakte koordinatparet som du vil hente tilbake.
En god strategi er å kjøre svn log --verbose
i en katalog som inneholdt det slettede elementet ditt.
Valget --verbose viser en liste over alle
forandrede elementer i hver revisjon; alt du trenger å gjøre er
å finne revisjonen der du slettet fila eller katalogen.
Du kan gjøre dette visuelt, eller ved å bruke et annet verktøy
for å undersøke utdataene fra loggen (ved hjelp av
grep, eller kanskje ved hjelp av et
inkrementelt søk i en tekstbehandler).
… ------------------------------------------------------------------------ r808 | joe | 2003-12-26 14:29:40 -0600 (fre, 26 des 2003) | 3 lines Endrede filstier: D /calc/trunk/real.c M /calc/trunk/integer.c La inn Fast Fourier transform-funksjoner i integer.c . Slettet real.c fordi koden nå ligger i double.c . …
I eksempelet går vi ut i fra at du ser etter en slettet fil
kalt real.c.
Ved å se gjennom loggene for en foreldrekatalog, har du funnet
ut at denne fila ble slettet i revisjon 808.
Derfor er den siste versjonen av fila der den fortsatt
eksisterte i revisjonen like før dette.
Konklusjon:
Du vil hente tilbake stien
/calc/trunk/real.c fra revisjon 807.
Dette var den vanskelige delen – etterforskningen. Nå som du vet hva du vil hente tilbake, har du to forskjellige valg.
En måte er å bruke svn merge for å legge
inn revisjon 808 “i revers”.
(Vi har allerede gått gjennom hvordan vi omgjør forandringer, se
“Omgjøre forandringer”.)
Dette vil ha samme effekten som å legge til
real.c en gang til som en lokal
modifisering.
Fila vil bli klargjort for tillegging, og etter en innlegging
med svn commit vil fila eksistere i
HEAD igjen.
Men i dette spesielle eksempelet er det kanskje ikke den
beste strategien.
Å legge inn revisjon 808 i revers vil ikke bare klargjøre
real.c for tillegging, men loggmeldingen
indikerer at det vil også bli omgjort forandringer i
integer.c, noe som du ikke ønsker.
Du kan selvfølgelig bakoverflette revisjon 808 og deretter kjøre
svn revert på de lokale forandringene i
integer.c, men denne teknikken skalerer
ikke alltid like bra.
Hva hvis det var 90 filer som forandret seg i revisjon
808?
En annen og mer målrettet strategi er å ikke bruke svn merge i det hele tatt, men derimot svn copy. Kopier ganske enkelt den eksakte revisjonens og stiens “koordinatpar” fra depotet til arbeidskopien din:
$ svn copy --revision 807 \
http://svn.example.com/repos/calc/trunk/real.c ./real.c
$ svn status
A + real.c
$ svn commit -m "Hentet tilbake real.c from revisjon 807, /calc/trunk/real.c ."
Legger til real.c
Sender fildata .
La inn revisjon 1390.
Plusstegnet som vises i statusoversikten indikerer at
elementet ikke bare er klargjort for tillegging, men er
klargjort for tillegging “med historie”.
Subversion husker hvor det ble kopiert fra.
I framtiden vil kjøring av svn log på denne
fila gå tilbake forbi gjenoppstandelsen av fila og gjennom hele
historien den hadde før revisjon 807.
Med andre ord, denne nye real.c er ikke
egentlig ny; den er en direkte etterkommer av den originale,
slettede fila.
Selv om eksempelet vårt viser at vi henter tilbake ei fil, legg merke til at disse samme teknikkene virker like godt når det gjelder å hente tilbake slettede kataloger.
Versjonskontroll blir vanligvis brukt til programutvikling, så her er en rask kikk på to av de vanligste forgrenings-/flettemønstere som blir brukt av programmeringsteam. Hvis du ikke bruker Subversion til programutvikling, kan du hoppe over denne seksjonen. Hvis du er en programutvikler som bruker versjonskontroll for første gang, følg nøye med, da disse fremgangsmåtene blant erfarne brukere ofte er ansett som de beste metodene. Disse prosessene er ikke spesifikke for Subversion; de kan brukes med ethvert versjonskontrollsystem. Men det kan hjelpe å se dem beskrevet i Subversionterminologi.
Programvare har vanligvis denne livssyklusen: Kode, teste, offentliggjøre, repetere. Det er to problemer med denne prosessen. For det første trenger utviklere å skrive ny funksjonalitet mens kvalitetssikringsteam tester versjoner som er ment å skulle være stabil. Nytt arbeide kan ikke stoppe opp mens programvaren blir testet ut. For det andre, teamet må nesten alltid støtte eldre, utgitte versjoner av programvaren; hvis en feil blir oppdaget i den seneste koden, eksisterer den sannsynligvis også i utgitte versjoner, og kundene vil ønske å få denne feilen rettet uten å måtte vente på en ny stor utgivelse.
Og det er her versjonskontroll kan hjelpe. Den vanlige prosedyren ser ut som dette:
Utviklerne legger inn alt nytt arbeid til
trunk.
Daglige forandringer legges inn på
/trunk:
Ny funksjonalitet, retting av feil og så videre.
trunk blir kopiert til en
“utgivelses”-gren.
Når teamet synes programvaren er klar for utgivelse (for
eksempel en 1.0-versjon), kan /trunk
bli kopiert til /branches/1.0.
Teamene fortsetter å arbeide
parallelt.
Et team begynner inngående testing av utgivelsesgrenen, mens
et annet team fortsetter på nytt arbeid (for eksempel på det
som skal bli versjon 2.0) i /trunk.
Hvis det blir funnet feil på et av stedene, blir
nødvendige reparasjoner flettet fram og tilbake.
Men på et punkt stopper også denne prosessen.
Grenen er “frosset” for avsluttende testing
rett før en utgivelse.
Grenen blir merket og utgitt.
Når testingen er ferdig, blir
/branches/1.0 kopiert til
/tags/1.0.0 som et referanseøyeblikksbilde.
De merkede filene pakkes og sendes ut til kundene.
Grenen blir vedlikeholdt over tid.
Mens arbeidet fortsetter på /trunk for
versjon 2.0, blir fortsatt feil som blir funnet på
/trunk flettet derfra til
/branches/1.0.
Når et tilstrekkelig antall feil er blitt rettet, kan
vedlikeholderne bestemme seg for å lage en 1.0.1-utgivelse:
/branches/1.0 blir kopiert til
/tags/1.0.1, og de merkede filene blir pakket og
utgitt.
Hele denne prosessen repeteres mens programvaren modnes: Når arbeidet for 2.0 er komplett, blir en ny utgivelsesgren for 2.0 opprettet, testet, merket og eventuelt utgitt. Etter noen år ender depotet opp med et antall grener i “vedlikeholdsmodus”, og et antall merker som representerer ferdige, utgitte versjoner.
En funksjonalitetsgren er den typen
gren som har vært det dominerende eksempelet i dette
kapittelet, den typen du har arbeidet på mens Sally fortsetter
å arbeide på /trunk.
Det er en midlertidig gren som er opprettet for å arbeide på
en kompleks forandring uten å la det gå ut over stabiliteten
til /trunk.
I motsetning til utgivelsesgrener (som kanskje må bli støttet
for alltid) blir funksjonalitetsgrener født, brukt en stund,
flettet tilbake til trunk, og deretter
til sist slettet.
De har en begrenset nyttighetsperiode.
Igjen, prosjekt-policy varierer veldig når det gjelder
nøyaktig når det passer å opprette en funksjonalitetsgren.
Noen prosjekter bruker ikke funksjonalitetsgrener i det hele
tatt; innlegginger til /trunk er tilgjengelig for alle.
Fordelen med dette systemet er at det er enkelt – ingen
trenger å lære om forgrening eller fletting.
Ulempen er at koden i trunk ofte er
ustabil eller ubrukelig.
Andre prosjekter bruker forgreninger til det ekstreme; ingen
forandringer blir noen gang lagt direkte
inn på trunk.
Til og med de enkleste forandringer blir laget på en gren med
kort levetid, nøye kontrollert og deretter flettet til trunk.
Så blir grenen slettet.
Dette systemet garanterer en eksepsjonelt stabil og brukbar
trunk til enhver tid, men med en pris i form av mer arbeid i
prosessen.
Noen prosjekter velger en rute midt i mellom.
De insisterer på at /trunk skal kunne
kompilere og passere systemsjekker til enhver tid.
En funksjonalitetsgren er bare nødvendig når en forandring
krever et stort antall innlegginger som kan gå ut over
stabiliteten.
En god tommelfingerregel er å stille dette spørsmålet:
Hvis utvikleren jobbet i flere dager i isolasjon og deretter
la inn hele den store forandringen på en gang (så
/trunk aldri ble ustabil), ville den
blitt for stor for gjennomlesing?
Hvis svaret til det er “ja”, bør forandringen bli
utviklet på en funksjonalitetsgren.
Etter hvert som utvikleren legger inn økende forandringer til
grenen, kan de bli sett over av kolleger.
Til sist har vi spørsmålet om hvordan man best kan holde en fremtidig gren i “synk” med trunk etterhvert som arbeidet går fram. Som vi nevnte tidligere, er det en stor risiko å arbeide på en gren i flere uker eller måneder; forandringer på trunk kommer strømmende inn, til punktet der de to utviklingslinjene er såpass forskjellige at det kan bli et mareritt å flette grenen tilbake til trunk.
Denne situasjonen unngås best ved å jevnlig flette trunk-forandringer inn på grenen. Lag en regel: En gang i uken fletter du forrige ukes forandringer til grenen. Vær nøye når du gjør dette; flettingen må sjekkes for å unngå problemet med gjentatte flettinger (som beskrevet i “Følge flettinger manuelt”). Du må skrive nøyaktige loggmeldinger med detaljer om hvilket revisjonsområde som allerede er blitt flettet (som demonstrert i “Flette en hel gren til en annen”). Det kan høres skremmende ut, men er faktisk ganske enkelt å gjøre.
Etterhvert er du klar til å flette den
“synkroniserte” funksjonalitetsgrenen tilbake til
trunk.
For å gjøre dette, start med å gjøre en avsluttende fletting
av de siste trunk-forandringene til grenen.
Når det er gjort, vil de siste versjonene av grenen og trunk
være absolutt like, bortsett fra forandringene på grenen din.
Så i dette spesielle tilfellet, vil du flette ved å
sammenligne grenen med trunk:
$ cd trunk-i-arbeidskopien
$ svn update
På revisjon 1910.
$ svn merge http://svn.example.com/repos/calc/trunk@1910 \
http://svn.example.com/repos/calc/branches/mybranch@1910
U real.c
U integer.c
A nykatalog
A nykatalog/nyfil
…
Ved å sammenligne HEAD-revisjonen for
trunk med HEAD-revisjonen på grenen, kan du
definere ett delta som beskriver kun de forandringene som du
gjorde til grenen; begge utviklingsgrener inneholder allerede
forandringene fra trunk.
En annen måte å tenke på dette mønsteret er at den ukentlige synkroniseringen er det samme som å kjøre svn update i en arbeidskopi, mens det siste trinnet med fletting er det samme som å kjøre svn commit fra en arbeidskopi. Når alt kommer til alt, er jo en arbeidskopi en grunn, privat gren. Det er en gren som bare er i stand til å lagre én forandring om gangen.
[23] Subversionprosjektet har imidlertid planer om å legge inn en svnadmin obliterate-kommando som vil ta seg av oppgaven med å permanent slette informasjon. I mellomtiden, se “svndumpfilter” for en mulig omvei om problemet.
[24] Fordi CVS ikke versjonerer trær, lager den et
Attic-område innenfor hver depotkatalog
som en måte å huske slettede filer på.