Kopiere filer mellom svn-arbeidskopier
Å flytte en mappestruktur fra en
Subversion-arbeidskopi ("
working copy") til en annen kan fort by på hodebry fordi .svn-filer og -mapper kolliderer. Lei av dette som jeg er har jeg skrevet et script som "kopierer seg rundt".
Hvorfor?
Du lurer kanskje på hvorfor i all verden jeg skulle ønske å ha de samme filene i to forskjellige svn-kontrollerte prosjekter? Vel, for tiden jobber jeg med en prototype på et nettsted. Prototypen viser alle de unike malene og inneholder
CSS, Javascript, bilder osv. Dette er det kun jeg som jobber med, og jeg har selvfølgelig hele prototypen i
Subversion.
I tillegg utvikles det en applikasjon som er den som faktisk skal online. Her er jeg ansvarlig for grensenittet. Med jevne mellomrom flytter jeg design-filene fra prototypen over i applikasjonen, som holdes i et annet Subversion-repository.
Og der er problemet. Kopierer jeg hele design-mappa fra prototypen over i applikasjonen vil også enkelte .svn-filer bli tuklet med. Dermed starter et helvete med
Subversion som jeg ikke unner noen. Dermed er behovet for et verktøy som kopierer filstrukturen, men utelater .svn-filer etablert.
Løsningen
Etter å ha kikket litt på diverse måter å bruke dir og andre kommandoer under "shellet" i Windows på bestemte jeg meg for å scripte en løsning. Ettersom jeg er relativt gira på
Rails for tida falt det seg naturlig å skrive dette i Ruby.
Kjapp brainstorm
Design-katalogen min for det aktuelle prosjektet er strukturert som: design/del-1 og design/del-2 hvor hver av disse inneholder katalogene css, images og js som igjen har sine underkataloger. design i applikasjonen er strukturert helt likt, så jeg ønsker et script som kan ta imot en kilde, et mål og x antall kataloger som skal kopieres fra kilden til målet.
Altså:
- Kopiere alle filer i
kilde-mappetil en midlertidig mappe - Slette alle
.svn-filer i den midlertidige mappen - Kopiere filene fra den midlertidige mappen over i
mål-mappe
Ok, kanskje noe tungvindt, men etter å ha stusset litt på det ser det ikke ut til at jeg klarer å kopiere alle filer unntatt .svn-filer i en operasjon, så da ble det som dette. Jeg er da tross alt en relativt praktisk kar som er opptatt av å komme videre. Hvis du kan fortelle meg hvordan jeg kan unngå midlertidig mappe-skrittet tas det imot med takk.
Scriptet
Midlertidige filer og kataloger håndteres i Ruby gjennom
Dir::tmpdir.
FileUtils har en mengde fil- og mappe-relaterte metoder som er svært behjelpelige. Sluttresultatet er svn2svn.rb som ser ut som:
#!c:/ruby/bin/ruby
require 'tempfile'
require 'fileutils'
class SvnCopy
def initialize(source, dest, excludes = [])
@source = source
@dest = dest
@tmpfolder = File.join(Dir::tmpdir, 'svn2svn')
@excludes = [File.join('**', '.svn*')].concat(excludes)
end
def copy(*folders)
sources = folders.collect { |folder| File.join(@source, folder) }
# Remove and recreate tmp directory, copy files to tmp
FileUtils.rm_r(@tmpfolder) if File.exists?(@tmpfolder)
FileUtils.mkdir(@tmpfolder)
FileUtils.cp_r(sources, @tmpfolder)
# Remove subversion and other unwanted files
Dir.chdir(@tmpfolder)
@excludes.each { |exclude| FileUtils.rm_r(Dir.glob(exclude)) }
# Copy files to destination
FileUtils.cp_r(Dir.glob('*'), @dest, :verbose => true)
end
end
Dette er en enkel klasse som initialiseres med en kilde-mappe og en mål-mappe. I tillegg kan du sende med ønskede mønstre for filer som skal ekskluderes. I utgangspunktet blir alle .svn*-filer ekskludert.
Deretter kan du kalle copy-metoden gjentatte ganger med en enkelt mappe, eller en liste med mapper som blir kopiert fra kilden til målet. Eksempel på bruk:
svnc = SvnCopy.new('d:\projects\myproject\public\design', 'd:\projects\otherproject\public\design')
svnc.copy('design-1')
svnc.copy('skin1', 'skin2')
Merk måten copy-metoden er definert på:
def copy(*folders)
Dette gjør at Ruby samler alle parameterne til en liste.
"Programmet"
For at dette skal være nyttig bør det kunne kjøres fra kommandolinja. Nederst i filen svn2svn.rb kan du legge til:
if __FILE__ == $0
sc = SvnCopy.new($*[0], $*[1])
sc.copy($*[2..-1])
end
Dette kjører dersom filen kjøres direkte. Da tar den første parameter som kilde, andre som mål, og alle andre parametere som mapper som skal kopieres. Eksempelet over blir da:
C:\>ruby svn2svn.rb d:\projects\myproject\public\design d:\projects\otherproject\public\design design-1 skin1 skin2
Ett skritt videre
Scriptet over fungerer uavhengig av hva slags teknologi du benytter på de to stedene (prototypen og applikasjonen i mitt eksempel). Det er bare en komplisert måte å kopiere filer på.
Min prototype er faktisk en Rails-applikasjon, og dermed falt det seg naturlig å gjøre dette som en rake-oppgave. Jeg la svn2svn.rb i RAILS_ROOT/lib, og dermed opprettet jeg RAILS_ROOT/lib/tasks/design_deploy.rake:
namespace :design do
desc 'Deploy design from prototype to application'
task :deploy do
unless ENV.include?('target')
raise 'Usage: rake design:deploy target=[target]'
end
require 'svn2svn'
source = File.join(RAILS_ROOT, 'public', 'design')
sc = SvnCopy.new(source, ENV['target'])
sc.copy(['base', 'skin-1'])
end
end
Voila! Dermed kan jeg kjøre rake design:deploy target=d:\projects\otherproject\design og designet oppdateres i applikasjonen. Det som er ekstra nyttig med en slik rake-oppgave er at den kan kjøres direkte fra Eclipse (gjennom
RadRails).
Nå venter jeg bare på at noen som er flinkere enn meg med kommandolinje-kopiering, eller Windows-tillegg skal fortelle meg hvordan jeg kunne ha gjort dette med en flott kommando eller lignende. Jeg mistenker også at det finnes smartere glob-mønstre som kanskje kunne gjort dette for meg(?)
Kommentarer
Marius
(http://shortcut.no)
17. januar, 18:59
Dernest: jeg holder _så_ med deg som sliter med .svn-katalogene man ender opp med med Subversion. Ryktene vil ha det til at Subversionfolket vurderer å endre dette til en struktur lignende den Git og Mercurial bruker, der det er _en_ .git/.hg-katalog i toppen av treet (repositoryet) som holder rede på alt for deg.
Men det er bare en av mange, gode grunner til å vurdere noe annet enn Subversion...
Christian
17. januar, 23:24
Marius Mathiesen
(http://shortcut.no)
18. januar, 09:24
Det første punktet er naturen til disse systemene, det at de er distribuerte. Git ble utviklet av Linus Torvalds for å brukes ved utvikling av Linux-kjernen, der tusenvis av utviklere jobber med samme kodebase. Det at de er distribuerte betyr først og fremst at det ikke er noe skille mellom et repository og en working copy, de er alle repositories. Når du committer endringer gjøres det "internt", altså offline og bare i din working copy/repository.
Om du har en mappe med kode og du ønsker å versjonere denne med Git/Mercurial oppretter du først et repository med kommandoen [git|hg] init. Du har da mappa under versjonskontroll, men den inneholder ingenting. Derfor skriver du [git|hg] add fil fil2 for å legge til filer i repositoryet og deretter committer du. Du har nå laget et repository, lagt til noen filer og committet en revisjon.
Deretter er i bunn og grunn ideen at dette repositoryet kan klones av andre, som da får sin utsjekkete versjon av koden, som igjen blir et eget repository. Man kan pull-e og push-e kode mellom disse repositoryene slik man vil, og disse verktøyene har derfor temmelig hårete merge-verktøy som tar høyde for at bokstavelig talt tusenvis av mennesker trenger å integrere med hverandre.
Dette høres kanskje helt anarkistisk ut, men det funker faktisk som en drøm i praksis. Om du vil kan du begynne med å bruke et distribuert versjonskontrollsystem på samme måte som du ville brukt Subversion, ingenting stopper deg fra å ha et sentralt repository som man bruker som integrasjonspunkt for flere utviklere.
Mine to favoritt-egenskaper ved Git/Mercurial (hhv kommandolinjene git og hg) er:
- jeg kan versjonere filer offline. For eksempel er det veldig lett å legge config-filer på en Linuxmaskin under versjonskontroll slik at man kan teste ut forskjellige innstillinger og så angre seg når man har driti på draget.
- jeg kan "branche" helt uten videre når jeg skal bruke en time eller to på en feature jeg ikke vet om jeg kommer til å bruke. I en Subversion-verden måtte jeg laget en branch på serveren og sjekke denne ut for å teste ut ideen, i et distribuert system er branchene i utgangspunktet mine private og innvirker ikke på andre i det hele tatt.
Puh, det var mye tekst på en fredags morgen
Christian
18. januar, 23:28
Jeg kjøper offline-argumentet, men branching kan du da like greit gjøre med Subversion, uten å bry noen andre med det. svn copy etterfulgt av svn switch, og voila, så er du i en ny branch. Trenger ikke å fortelle noen om den bare fordi den er på serveren :)
Det er forresten fullt mulig å bruke Subversion til distribuert versjonskontroll; har du testa SVK (http://svk.bestpractical.com/view/HomePage )?
Marius Mathiesen
(http://shortcut.no/)
19. januar, 11:02
Kikket litt på Svk for en stund siden uten at jeg helt ble overbevist den gangen. For Rubyfolk finnes også Piston [http://piston.rubyforge.org/) som jeg tror gjør noe av det samme.
Ola Christian Gundelsby
(http://www.telenor.no)
22. januar, 17:54
Jeg bruker Windows og har ikke python/perl/whatever installert på arbeidsstasjonen min, og tydde derfor til god gammeldags batch-programmering.
Denne linjen i en .bat-fil vil fjerne alle .svn-filer i katalogen man står i samt alle underkataloger av denne igjen:
FOR /F "tokens=*" %%G IN ('DIR /B /AD /S *.svn*') DO RMDIR /S /Q "%%G"
Christian
22. januar, 19:01
Dette er jo fantastisk, jeg regnet med at det var mulig å gjøre noe sånt, og prøvde det først. Desverre har jeg ikke like stor tålmodighet og skillz med bat-filer som du tydeligvis har :) Takk for tipset!
Miu Miu Pas Cher
(http://www.miumiupascher.com)
12. mars, 02:51
la rivière Cher et la Loire, à environ 65 miles de Miles-Orléans et 125 au sud-ouest de Paris.<a href="http://www.miumiupascher.com/6-miu-miu-2011"title="Miu Miu 2011">Miu Miu 2011</a>
Cette ville est la capitale des Français département de l'Indre et Loire.