Som det ofte er når man utvikler programvare, er dataene du har under versjonskontroll ofte nært knyttet til, eller avhengig av, noen andres data. Vanligvis vil behovene til prosjektet ditt tilsi at du holder deg mest mulig oppdatert som mulig med dataene som du får fra denne eksterne enheten uten å ofre stabiliteten til ditt eget prosjekt. Denne situasjonen oppstår hele tiden – alle steder der informasjonen generert av en gruppe har en direkte effekt på hva som er generert av en annen gruppe.
For eksempel, programvareutviklere kan utvikle en programpakke som bruker tredjeparts biblioteker. Subversion har akkurat et slikt forhold til Apache Portable Runtime library (se “The Apache Portable Runtime Library”). Kildekoden til Subversion avhenger av APR-biblioteket for alle behov innen portabilitet. I tidligere faser av utviklingen til Subversion, fulgte prosjektet brukergrensesittet for APR veldig nøye, og brukte alltid den helt nyeste koden til biblioteket. Så som både APR og Subversion har modnet, prøver Subversion å synkronisere med kun velprøvde, stabile utgaver av APR-biblioteket.“”
Hvis prosjektet ditt nå består av noen adres informasjon, er det flere måter du kan prøve å synkronisere denne informasjonen med din egen. Det mest smertefulle kan være å gi muntlige eller nedskrevne instruksjoner til alle bidragsyterne for prosjektet, og fortelle dem at de må være sikre på at de har de absolutt riktige versjonene av denne tredjepartsinformasjonen. Hvis tredjepartsinformasjonen lagres i et Subversion-depot, kan du også bruke Subversions externals-definisjon for å effektivt “nagle fast” spesifikke versjoner av den informasjonen til en spesiell plassering i din egen arbeidskopi (se “Externals Definitions”).
Men noen ganger vil du vedlikeholde spesialtilpassede versjoner av tredjeparts programvare i ditt eget versjonskontrollsystem. Hvis vi går tilbake til programutviklingseksempelet, kan det være nødvendig for programmerere å gjøre forandringer i tredjepartsbiblioteket for eget bruk. Disse forandringene kan inneholde ny funksjonalitet eller filrettinger, internt vedlikeholdt bare til de blir del av en offisiell utgivelse av tredjepartsbiblioteket. Eller forandringene blir kanskje aldri sendt tilbake til vedlikeholderne av biblioteket, men fungerer bare som hjemmeskrudde løsninger for å få biblioteket til å dekke behovene enda bedre.
Nå står du ovenfor en interessant situasjon. Prosjektet ditt kunne lagret spesialforandringene i tredjepartsdataene som noe helt annet, som å bruke patchfiler eller fullstendige alternative versjoner av filer og kataloger. Men dette vedlikeholdet har en tendens til å bli en hodepine som krever en eller annen mekanisme som du bruker til å legge inn dine spesialforandringer i tredjepartsdataene. Det vil også være nødvendig å regenerere disse forandringene med hver eneste etterfølgende versjon av tredjepartsdataene som du følger med på.
Løsningen på dette problemet er å bruke leverandørgrener (engelsk: vendor branches). En leverandørgren er et katalogtre i deitt eget versjonskontrollsystem som inneholder informasjon utgitt av en tredjepart eller leverandør. Hver versjon av leverandørens data som du bestemmer det for å legge inn i prosjektet ditt, kalles vendor drop.
Leverandørgrener gir to fordeler. For det første, ved å lagre den nåværende versjonen av leverandørdataene i ditt eget versjonskontrollsystem, trenger medlemmene i prosjektet ditt aldri å lure på om de har den riktige versjonen som en del av de vanlige arbeidskopioppdateringene. For det andre, fordi dataene er i ditt eget Subversiondepot, kan du lagre forandringene dine direkte i dem – du trenger ikke lengre å ha en automatisert (eller enda verre, manuell) metode for å legge inn tilpasningene dine.
Dette er måten vedlikehold av leverandørgrener vanligvis
fungerer på.
Du lager en katalog på toppnivå (som for eksempel
/vendor) for å lagre leverandørgrenene.
Deretter importerer du tredjepartskoden inn i en underkatalog i
denne toppkatalogen.
Så kopierer du denne underkatalogen inn på grenen der
hovedutviklingen foregår (for eksempel
/trunk på den riktige plasseringen.
Du gjør alltid dine lokale forandringer på grenen der
hovedutviklingen foregår.
For hver ny utgivelse av koden du følger med på, legger du den
inn på leverandørgrenen og fletter forandringene inn i
/trunk og ordner eventuelle konflikter som
måtte oppstå mellom dine lokale forandringer og de offisielle
forandringene.
Kanskje vil et eksempel hjelpe på å klarne opp i
fremgangsmåten.
Vi bruker en situasjon der utviklingsteamet lager et
regneprogram som lenker mot et tredjeparts bibliotek for
kompleks aritmetikk, libcomplex.
Vi starter med den innledende opprettelsen av leverandørgrenen og den første importen av
leverandørutgivelsen.
Vi kaller leverandørgrenen libcomplex, og
leverandørutgivelsene går i en underkatalog i
leverandørgrenen kalt current.
Og siden svn import oppretter alle de
mellomliggende foreldrekataloger som vi trenger, kan vi faktisk
gjøre begge disse stegene med en enkelt kommando.
$ svn import /sti/til/libcomplex-1.0 \
http://svn.example.com/repos/vendor/libcomplex/current \
-m 'Importerer innledende 1.0-versjon'
…
Vi har nå den nåværende versjonen av libcomplex-kildekoden i
/vendor/libcomplex/current.
Nå merker vi denne versjonen (se “Merker
(“tags”)” og deretter kopierer den inn
på hovedutviklingsgrenen.
Kopien vår vil opprette en ny katalog kalt
libcomplex i den eksisterende
prosjektkatalogen calc.
Det er i denne kopien av leverandørdataene vi vil gjøre
forandringene.
$ svn copy http://svn.example.com/repos/vendor/libcomplex/current \
http://svn.example.com/repos/vendor/libcomplex/1.0 \
-m 'Merker libcomplex-1.0'
…
$ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0 \
http://svn.example.com/repos/calc/libcomplex \
-m 'Legger libcomplex-1.0 inn på hovedgrenen'
…
Vi henter ut prosjektets hovedgren – som nå inneholder en kopi av den første leverandørutgivelsen – og vi setter i gang med å tilpasse libcomplex-koden. Før vi vet ordet av det, er den modifiserte versjonen av libcomplex fullstendig integrert i kalkulatorprogrammet.[26]
Noen uker senere slipper utviklerne av libcomplex en ny versjon av biblioteket – versjon 1.1 – som inneholder trivelig funksjonalitet som vi har lyst på. Vi vil oppgradere til denne nye versjonen, men uten å miste forandringene vi gjorde i den eksisterende versjonen. Det vi egentlig vil gjøre, er å erstatte den nåværende utviklingsversjonen med en kopi av libcomplex 1.1 og deretter legge inn forandringene våre i den nye versjonen. Men vi gjør det faktisk den andre veien, legger inn forandringene i libcomplex mellom versjon 1.0 og 1.1 i den modifiserte kopien av den.
For å gjennomføre denne oppgraderingen, henter vi ut en kopi
av leverandørgrenen og erstatter koden i
current-katalogen med den nye koden for
libcomplex 1.1.
Vi kopierer bokstavelig talt nye filer på toppen av eksisterende
filer, kanskje ved å pakke ut .tar.gz-fila
oppå de eksisterende filene og katalogene.
Meningen med dette er å få
current-katalogen til å inneholde kun koden
fra libcomplex 1.1, og å forsikre seg om at all denne koden er
under versjonskontroll.
Og en annen ting var at vi ville at dette skulle skje uten å
forstyrre versjonskontrollhistorien alt for mye.
Etter å ha erstattet 1.0-koden med 1.1, vil svn
status vise filer med lokale forandringer sammen med
filer som kanskje er uversjonert eller mangler.
Hvis vi gjorde det vi skulle, er de uversjonerte filene bare de
nye filene introdusert i 1.1-versjonen av libcomplex – vi kjører
svn add på disse for å få dem under
versjonskontroll.
De manglende filene er filer som var i 1.0, men ikke i 1.1, og
på disse stiene kjører vi svn delete.
Helt til slutt, når current-arbeidskopien
kun inneholder koden for libcomplex 1.1, legger vi inn
forandringene vi gjorde for å få den til å se ut som den
gjør.
Nå inneholder current-grenen den nye
leverandørversjonen.
Vi merker den nye versjonen (på den samme måten vi tidligere
merket 1.0-versjonen) og fletter deretter forandringene mellom
merket for den forrige versjonen og den nye gjeldende versjonen
inn i hovedutviklingsgrenen.
$ cd arbeidskopier/calc
$ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0 \
http://svn.example.com/repos/vendor/libcomplex/current \
libcomplex
… # Rydd opp i alle konfliktene mellom deres og våre forandringer
$ svn commit -m 'merging libcomplex-1.1 into the main branch'
…
I enkle sitasjoner vil den nye versjonen av tredjepartsdataene se ut som, fra et fil og katalog-syn, akkurat om den forrige versjonen. Ingen av filene i libcomplex vil være slettet, ha fått nytt navn eller blitt flyttet til andre plasseringer – den nye versionen vil bare inneholde tekstmessige forandringer i forhold til den forrige. I en perfekt verden ville våre forandringer legge seg inn i den nye versjonen uten antydning til komplikasjoner eller konflikter.
Men det er ikke alltid så enkelt, og faktisk er det ganske vanlig for kildekodefiler å bli flyttet rundt mellom programutgivelser. Dette kompliserer prosessen som går ut på å forsikre seg om at våre forandringer fortsatt er gyldige for den nye versjonen av koden, noe som kan ende med den situasjonen at vi manuelt må gjenopprette våre forandringer i den nye versjonen. Når Subversion vet om historien til en gitt kildefil – inkludert alle dens tidligere plasseringer – er prosessen med å flette inn den nye versjonen av biblioteket ganske enkel. Men vi er ansvarlig for å fortelle Subversion hvordan kildekodelayouten forandret seg fra leverandørversjon til leverandørversjon.
Vendor drops that contain more than a few deletes, additions and moves complicate the process of upgrading to each successive version of the third-party data. So Subversion supplies the svn_load_dirs.pl script to assist with this process. This script automates the importing steps we mentioned in the general vendor branch management procedure to make sure that mistakes are minimized. You will still be responsible for using the merge commands to merge the new versions of the third-party data into your main development branch, but svn_load_dirs.pl can help you more quickly and easily arrive at that stage.
In short, svn_load_dirs.pl is an enhancement to svn import that has several important characteristics:
It can be run at any point in time to bring an existing directory in the repository to exactly match an external directory, performing all the necessary adds and deletes, and optionally performing moves, too.
It takes care of complicated series of operations between which Subversion requires an intermediate commit—such as before renaming a file or directory twice.
It will optionally tag the newly imported directory.
It will optionally add arbitrary properties to files and directories that match a regular expression.
svn_load_dirs.pl takes three mandatory arguments. The first argument is the URL to the base Subversion directory to work in. This argument is followed by the URL—relative to the first argument—into which the current vendor drop will be imported. Finally, the third argument is the local directory to import. Using our previous example, a typical run of svn_load_dirs.pl might look like:
$ svn_load_dirs.pl http://svn.example.com/repos/vendor/libcomplex \
current \
/path/to/libcomplex-1.1
…
You can indicate that you'd like
svn_load_dirs.pl to tag the new vendor drop
by passing the -t command-line option and
specifying a tag name. This tag is another URL relative to
the first program argument.
$ svn_load_dirs.pl -t libcomplex-1.1 \
http://svn.example.com/repos/vendor/libcomplex \
current \
/path/to/libcomplex-1.1
…
When you run svn_load_dirs.pl, it
examines the contents of your existing “current”
vendor drop, and compares them with the proposed new vendor
drop. In the trivial case, there will be no files that are in
one version and not the other, and the script will perform the
new import without incident. If, however, there are
discrepancies in the file layouts between versions,
svn_load_dirs.pl will prompt you for how
you would like to resolve those differences. For example, you
will have the opportunity to tell the script that you know
that the file math.c in version 1.0 of
libcomplex was renamed to arithmetic.c in
libcomplex 1.1. Any discrepancies not explained by moves
are treated as regular additions and deletions.
The script also accepts a separate configuration file for
setting properties on files and directories matching a regular
expression that are added to the
repository. This configuration file is specified to
svn_load_dirs.pl using the
-p command-line option. Each line of the
configuration file is a whitespace-delimited set of two or
four values: a Perl-style regular expression to match the
added path against, a control keyword (either
break or cont), and then
optionally a property name and value.
\.png$ break svn:mime-type image/png \.jpe?g$ break svn:mime-type image/jpeg \.m3u$ cont svn:mime-type audio/x-mpegurl \.m3u$ break svn:eol-style LF .* break svn:eol-style native
For each added path, the configured property changes whose
regular expression matches the path are applied in order,
unless the control specification is break
(which means that no more property changes should be applied
to that path). If the control specification is
cont—an abbreviation for
continue—then matching will continue
with the next line of the configuration file.
Any whitespace in the regular expression, property name,
or property value must be surrounded by either single or
double quote characters. You can escape quote characters that
are not used for wrapping whitespace by preceding them with a
backslash (\) character. The backslash
escapes only quotes when parsing the configuration file, so do
not protect any other characters beyond what is necessary for
the regular expression.