Nå arbeider du og Sally på parallelle grener i prosjektet: Du arbeider på en privat gren, og Sally jobber i trunk, eller hovedlinjen av utviklingen.
For prosjekter som har et stort antall bidragsytere er det
vanlig for de fleste personer å ha arbeidskopier av
trunk.
Når noen må gjøre forandringer som vil ta litt tid og som
sannsynligvis kommer til å forstyrre trunk,
er standard prosedyre å lage en privat gren og legge inn
forandringer der til alt arbeidet er fullført.
Så, de gode nyhetene er at du og Sally ikke forstyrrer
hverandre.
De dårlige nyhetene er at det er veldig lett å drive
for langt avgårde.
Husk at ett av problemene med “krabbe inn i et
hull”-strategien er at når du er ferdig med grenen din, vil
det bli nesten umulig å flette inn dine forandringer tilbake til
trunk uten et stort antall konflikter.
Istedenfor kan du og Sally fortsette med å dele forandringer
mens du arbeider.
Det er opp til deg å bestemme hvilke forandringer som er verdt å
dele; Subversion gir deg muligheten til å selektivt
“kopiere” forandringer mellom grener.
Og når du er fullstendig ferdig med din gren, kan hele settet med
grenforandringer bli kopiert tilbake til
trunk.
I den forrige seksjonen nevnte vi at både du og Sally gjorde
forandringer til integer.c på forskjellige
forgreninger.
Hvis du ser på Sallys loggmelding for revisjon 344, kan du se at
hun forandret noen stavefeil.
Din kopi av den samme fila har uten tvil de samme skrivefeilene.
Det er sannsynlig at dine fremtidige forandringer i denne fila
vil påvirke de samme områdene som skrivefeilene ligger i, så du
ligger an til å få potensielle konflikter når du en vakker dag
fletter inn grenen din.
Da er det bedre å motta Sallys forandringer nå,
før du starter med å arbeide mye i det
samme området.
Det er på tide å bruke kommandoen svn merge. Det skal vise seg at denne kommandoen er en veldig nær slektning av svn diff-kommandoen (som du leste om i Kapittel 2, Grunnleggende bruk). Begge kommandoene er i stand til å sammenligne to vilkårlige objekter i depotet og beskrive forskjellene. For eksempel kan du spørre svn diff om å vise deg den eksakte forandringen gjort av Sally i revisjon 344:
$ svn diff -r 343:344 http://svn.example.com/repos/calc/trunk
Index: integer.c
===================================================================
--- integer.c (revisjon 343)
+++ integer.c (revisjon 344)
@@ -147,7 +147,7 @@
case 6: sprintf(info->operating_system, "HPFS (OS/2 or NT)"); break;
case 7: sprintf(info->operating_system, "Macintosh"); break;
case 8: sprintf(info->operating_system, "Z-System"); break;
- case 9: sprintf(info->operating_system, "CPM"); break;
+ case 9: sprintf(info->operating_system, "CP/M"); break;
case 10: sprintf(info->operating_system, "TOPS-20"); break;
case 11: sprintf(info->operating_system, "NTFS (Windows NT)"); break;
case 12: sprintf(info->operating_system, "QDOS"); break;
@@ -164,7 +164,7 @@
low = (unsigned short) read_byte(gzfile); /* read LSB */
high = (unsigned short) read_byte(gzfile); /* read MSB */
high = high << 8; /* interpret MSB correctly */
- total = low + high; /* add them togethe for correct total */
+ total = low + high; /* add them together for correct total */
info->extra_header = (unsigned char *) my_malloc(total);
fread(info->extra_header, total, 1, gzfile);
@@ -241,7 +241,7 @@
Store the offset with ftell() ! */
if ((info->data_offset = ftell(gzfile))== -1) {
- printf("error: ftell() retturned -1.\n");
+ printf("error: ftell() returned -1.\n");
exit(1);
}
@@ -249,7 +249,7 @@
printf("I believe start of compressed data is %u\n", info->data_offset);
#endif
- /* Set postion eight bytes from the end of the file. */
+ /* Set position eight bytes from the end of the file. */
if (fseek(gzfile, -8, SEEK_END)) {
printf("error: fseek() returned non-zero\n");
Kommandoen svn merge gjør omtrent nøyaktig det samme. Istedenfor å skrive forskjellene til terminalen din, blir de lagt direkte til arbeidskopien din som lokale modifikasjoner:
$ svn merge -r 343:344 http://svn.example.com/repos/calc/trunk U integer.c $ svn status M integer.c
Utdataene fra svn merge viser at din kopi
av integer.c ble patchet.
Nå inneholder den Sallys forandring – forandringen er blitt
“kopiert” fra trunk til din
arbeidskopi på din private gren, og eksisterer nå som en lokal
modifisering.
På dette punktet er det opp til deg å se over den lokale
modifiseringen og forsikre deg om at den fungerer
korrekt.
I et annet scenario er det mulig at ting ikke gikk så bra og
at integer.c gikk inn i en
konflikttilstand.
Du må kanskje løse konflikten ved hjelp av standard prosedyrer
(se Kapittel 2, Grunnleggende bruk), eller hvis du finner ut at
flettingen egentlig var en dårlig idé, kan du rett og slett gi
opp og kjøre svn revert på den lokale
forandringen.
Men hvis vi går ut i fra at du har sett over forandringen, kan du bruke svn commit til å legge inn forandringen på vanlig måte. På dette tidpunktet er forandringen blitt flettet inn i din depotgren. I versjonskontrollterminologi blir denne måten å kopiere forandringer mellom forgreninger på engelsk kalt porting. Det finnes ikke en standardisert betegnelse for dette på norsk, så vi bruker i denne boka uttrykket “flette”.
Når du legger inn de lokale modifiseringene, bør du forsikre deg om at loggmeldingen din nevner at du fletter en forandring fra en gren til en annen. For eksempel:
$ svn commit -m "integer.c: Flettet r344 (retting av skrivefeil) fra trunk." Sending integer.c Sender fildata . La inn revisjon 360.
Som du vil se i de neste seksjonene, er dette en veldig viktig “god praksis” å følge.
En liten advarsel: Selv om svn diff og svn merge er veldig like i konsept, har de i mange tilfeller forskjellig syntaks. Vær sikker på at du får lest om dem i Kapittel 9, Subversion Complete Reference for detaljer, eller spør svn help. For eksempel krever svn merge en arbeidskopi som et mål, det vil si en plass hvor den skal legge inn treforandringene. Hvis målet ikke er spesifisert, går den ut i fra at du prøver å utføre en av de følgende operasjonene:
Du vil flette katalogforandringer inn i den gjeldende arbeidskatalogen.
Du vil flette forandringene i en spesifikk fil inn i en fil med det samme navnet som eksisterer i den gjeldende arbeidskatalogen.
Hvis du fletter en katalog og ikke har spesifisert en målsti, går svn merge ut i fra det første tilfellet og prøver å legge inn forandringene til den gjeldende katalogen. Hvis du fletter ei fil, og denne fila (eller ei fil med det samme navnet) eksisterer i den gjeldende katalogen, går svn merge ut i fra det andre tilfellet og forsøker å legge inn forandringene til en lokal fil med det samme navnet.
Hvis du vil legge inn forandringer en annen plass, må du si fra om dette. Hvis du for eksempel sitter i foreldrekatalogen til arbeidskopien din, må du spesifisere målkatalogen som skal motta forandringene:
$ svn merge -r 343:344 http://svn.example.com/repos/calc/trunk my-calc-branch U my-calc-branch/integer.c
Du har nå sett et eksempel på svn merge-kommandoen, og du skal få se flere. Hvis du er forvirret omkring hvordan fletting faktisk virker, er du ikke alene om det. Mange brukere (spesielt de som er nye innen versjonskontroll) er usikker på den riktige syntaksen til kommandoen, og når denne funksjonaliteten skal brukes. Men frykt ikke, denne kommandoen er faktisk mye enklere enn du tror! Det er en veldig enkel teknikk for å forstå nøyaktig hvordan svn merge virker.
Hovedkilden til forvirringen er navnet på kommandoen. Terminologien merge – “flette” – indikerer på en måte at grener blir kombinert sammen, eller at det er en form for mystisk sammenblanding av data som foregår. Det er ikke tilfellet. Et bedre navn for kommandoen hadde vært svn diff-and-apply – “finn forskjell og legg denne til” – fordi det er alt som skjer: To depottrær blir sammenlignet, og forskjellene blir lagt inn i arbeidskopien.
Kommandoen tar tre argumenter:
Et innledende depottre (ofte kalt den venstre siden av sammenligningen),
Et slutt-depottre (ofte kalt den høyre siden av sammenligningen),
En arbeidskopi som skal motta forandringene som lokale forandringer (ofte kalt målet til flettingen).
Når disse tre argumentene er spesifisert, blir de to trærne sammenlignet og de forskjellene som programmet finner blir lagt inn i mål-arbeidskopien som lokale forandringer. Når kommandoen er ferdig, er ikke resultatet forskjellig fra om du hadde redigert filene for hånd, eller selv kjørt diverse svn add eller svn delete-kommandoer. Hvis du ikke liker resultatene, kan du enkelt kjøre svn revert for å omgjøre alle forandringene.
Syntaksen til svn merge lar deg spesifisere de tre nødvendige argumentene ganske fleksibelt. Her er noen eksempler:
$ svn merge http://svn.example.com/repos/branch1@150 \
http://svn.example.com/repos/branch2@212 \
min-arbeidskopi
$ svn merge -r 100:200 http://svn.example.com/repos/trunk min-arbeidskopi
$ svn merge -r 100:200 http://svn.example.com/repos/trunk
Den første syntaksen legger spesifikt opp alle tre argumentene, ved å nevne hvert tre på formen URL@REV og nevne arbeidskopimålet. Den andre syntaksen kan bli brukt som en snarvei for situasjoner når du sammenligner to forskjellige revisjoner på den samme URLen. Den siste syntaksen viser hvordan arbeidskopiargumentet er valgfritt; hvis det er utelatt, brukes den gjeldende katalogen.
Fletting av forandringer høres enkelt ut, men i praksis kan det bli en hodepine. Problemet er at hvis du gjentatte ganger fletter forandringer fra en gren til en annen, kan du ved en ulykke flette den samme forandringen to ganger. Når dette skjer, vil det noen ganger gå bra. Når ei fil blir patchet vil Subversion vanligvis oppdage at fila inneholder forandringen, og gjør ingenting. Men hvis den allerede eksisterende forandringen er blitt forandret på en eller annen måte, vil du få en konflikt.
Ideelt sett bør versjonskontrollsystemet forhindre forsøket på å legge inn doble forandringer til en gren. Det bør huske automatisk hvilke forandringer en gren allerede har mottatt, og bør være i stand til å liste dem ut for deg. Det bør bruke denne informasjonen til å hjelpe til med å automatisere flettinger så mye som mulig.
Dessverre er ikke Subversion et sånt system. I likhet med CVS lagrer ikke Subversion 1.0 noen informasjon om fletteoperasjoner. Når du legger inn lokale forandringer, har ikke depotet noen idé om hvorvidt disse forandringene kom fra en kjøring av svn merge, eller fra en redigering av filene for hånd.
Hva betyr dette for deg, brukeren? Det betyr at inntil den dagen Subversion får denne funksjonen, må du selv holde rede på fletteinformasjonen. Den beste plassen å gjøre dette er i selve loggmeldingen. Som demonstrert i det tidligere eksempelet, anbefales det at loggmeldingen din nevner et spesifikt revisjonsnummer (eller område av revisjoner) som blir flettet inn i grenen. Senere kan du kjøre svn log for å se over hvilke forandringer forgreningen allerede inneholder. Dette vil la deg varsomt konstruere en etterfølgende svn merge-kommando som ikke vil bli overflødig i forhold til tidligere flettede forandringer.
I den neste seksjonen vil vi vise noen eksempler på denne teknikken i praksis.
Fordi fletting bare resulterer i lokale modifikasjoner, er det vanligvis ikke noen høyrisikooperasjon. Hvis flettingen går galt første gangen, kan du kjøre svn revert på forandringene og prøve igjen.
Men det er derimot mulig at arbeidskopien din allerede inneholder lokale forandringer. Forandringene lagt inn av en fletting vil bli blandet med de du har fra før, og det å kjøre svn revert er ikke lenger et alternativ. De to settene med forandringer kan bli umulig å separere.
I tilfeller som dette vil det være greit å kunne forutsi
eller undersøke forandringer før de skjer.
En enkel måte å gjøre det på er å kjøre svn
diff med de samme argumentene som du planlegger å gi
til svn merge, som vi allerede har vist i
det første eksemplet vårt med fletting.
En annen metode for forhåndsvisning er å angi
--dry-run-valget til flettekommandoen:
$ svn merge --dry-run -r 343:344 http://svn.example.com/repos/calc/trunk U integer.c $ svn status # ingenting blir skrevet ut, arbeidskopien er fortsatt uforandret.
--dry-run-valget gjør egentlig ingen
forandringer i arbeidskopien.
Det viser bare statuskoder som ville
blitt skrevet ut under en virkelig fletting.
Det er nyttig for å få en “høynivå”-oversikt over
den potensielle flettingen for de gangene der kjøring av
svn diff gir alt for mange detaljer.
Akkurat som svn update-kommandoen, legger svn merge inn forandringer i arbeidskopien din. Og derfor er den også i stand til å lage konflikter. Konfliktene produsert av svn merge er imidlertid noen ganger forskjellige, og denne seksjonen forklarer disse forskjellene.
Til å begynne med, tenk deg at arbeidskopien din ikke inneholder noen lokale redigeringer. Når du svn update-er til en spesiell revisjon, vil forandringene sendt fra serveren alltid bli lagt inn på en “renslig” måte i arbeidskopien. Serveren produsererer deltaet ved å sammenligne to trær: Et virtuelt øyeblikksbilde av arbeidskopien din, og revisjonstreet du er er interessert i. Fordi den venstre siden av sammenligningen er nøyaktig lik det du allerede har, er deltaet garantert å korrekt konvertere arbeidskopien din til treet som er på høyre side.
Men svn merge har ingen slike garantier og kan være mye mer kaotisk: Brukeren kan be serveren om å sammenligne alle mulige trær, til og med trær som ikke er relatert til arbeidskopien! Dette betyr at det er et stort potensiale for menneskelige feil. Brukere vil noen ganger sammenligne to gale trær, og dermed lage et delta som ikke kan legges inn på en ren måte. svn merge vil gjøre sitt beste for å legge inn så mye av deltaet som mulig, men noen deler kan være umulige. Akkurat som patch-kommandoen i Unix noen ganger klager over “failed hunks”, vil svn merge klage over “skipped targets”:
$ svn merge -r 1288:1351 http://svn.example.com/repos/branch U foo.c U bar.c Hoppet over savnet mål: «baz.c» U glub.c C glorb.h $
I det forrige eksempelet kan tilfellet være at
baz.c eksisterer både i øyeblikksbildet
av grenen som sammenlignes, og det resulterende deltaet vil
forandre filens innhold, men fila eksisterer ikke i
arbeidskopien.
Hva som enn er tilfelle, betyr “skipped”-meldingen at brukeren mest
sannsynlig sammenligner to gale trær; de er det klassiske
tegnet på en feil gjort av brukeren.
Når dette skjer er det lett å rekursivt omgjøre alle
forandringene gjort under flettingen (svn revert
--recursive), slette eventuelle uversjonerte filer
eller kataloger som ligger igjen etter tilbakestillingen, og
kjøre svn merge med forskjellige
argumenter.
Legg også merke til at det forrige eksempelet viser en
konflikt som skjer i glorb.h.
Vi har allerede fastslått at arbeidskopien ikke har noen
lokale forandringer;
hvordan er det da mulig at en lokal konflikt kan oppstå?
Igjen, fordi brukeren kan bruke svn merge
for å definere og legge til enhver gammel delta til
arbeidskopien, kan denne deltaen inneholde tekstmessige
forandringer som ikke kan legges helt uproblematisk inn i en
arbeidsfil, selv om denne fila ikke har noen lokale
forandringer.
En annen liten forskjell mellom svn
update og svn merge er navnene på
fulltekst-filene som blir opprettet når en konflikt oppstår.
I “Løse konflikter (Flette inn andres forandringer)” så vi at en
oppdatering produserer filene
filnavn.mine,
filnavn.rGAMMELREV og
filnavn.rNYREV.
Men når svn merge produserer en konflikt,
oppretter den tre filer kalt
filnavn.working,
filnavn.left og
filename.right.
I dette tilfellet beskriver terminologien “left”
og “right” hvilken side fila kom fra.
I alle fall, disse forskjellige navnene vil hjelpe deg å
skille mellom filer som er opprettet som følge av en
oppdatering versus filer som er opprettet som resultat av en
fletting.
Når du snakker med en Subversionutvikler kan det hende du hører referanser til begrepet slektskap – ancestry. Dette ordet blir brukt til å beskrive forholdet mellom to objekter i et depot: Hvis de er relaterte til hverandre, vil det ene objektet være en stamfar til det andre.
For eksempel, tenk deg at du legger inn revisjon 100, som
inkluderer en forandring i ei fil kalt
foo.c.
Da er foo.c@99 en “stamfar”
til foo.c@100.
På den annen side, tenk at du legger inn en sletting av
foo.c i revisjon 101, og deretter legger
til en ny fil med det samme navnet i revisjon 102.
I dette tilfellet kan det se ut som om
foo.c@99 og
foo.c@102 er relaterte (de har den samme
filstien), men de er faktisk totalt forskjellige objekter i
depotet.
De deler ingen historie eller “slektskap”.
Grunnen til at vi tar dette opp er for å fremheve en
viktig forskjell mellom svn diff og
svn merge.
Den første kommandoen ignorerer slektskap, mens den sistnevnte
kommandoen er ganske følsom for det.
Hvis du for eksempel ber svn diff om å
sammenligne revisjon 99 og 102 av foo.c,
vil du se linjebaserte forskjeller;
diff-kommandoen sammenligner blindt to
stier.
Men hvis du ber svn merge om å sammenligne
de samme to objektene, vil den oppdage at de er urelaterte og
først prøve å slette den gamle fila og deretter legge til den
nye fila.
Utdataene fra programmet vil indikere en sletting etterfulgt
av en tillegging:
D foo.c A foo.c
De fleste flettinger involverer sammenligning av trær som
er slektsmessig relatert til hverandre, og derfor har
svn merge denne oppførselen som standard.
Men nå og da vil du kanskje bruke
merge-kommandoen til å sammenligne to
urelaterte trær.
For eksempel har du kanskje importert to kildekodetrær som
representerer forskjellige utgivelser av et
programprosjekt (se “Leverandørgrener”).
Hvis du ber svn merge om å sammenligne de
to trærne, vil du se at hele det første treet blir slettet,
fulgt av en tillegging av hele det andre treet!
I disse situasjonene vil du at svn
merge bare gjør en stibasert sammenligning og
ignorerer enhver relasjon mellom filer og kataloger.
Legg til valget --ignore-ancestry til
flettekommandoen, og den vil oppføre seg akkurat som
svn diff.
(Og på motsatt måte vil
--notice-ancestry-valget få svn
diff til å oppføre seg som
merge-kommandoen.
[22] I fremtiden planlegger Subversionprosjektet å bruke (eller finne opp) et utvidet patchformat som beskriver forandringer i trestruktur og egenskaper.