Hopp til innholdet

cjohansen.no

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å:

  1. Kopiere alle filer i kilde-mappe til en midlertidig mappe
  2. Slette alle .svn-filer i den midlertidige mappen
  3. 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(?)

Muligens relatert

2006 - 2012 Christian Johansen Creative Commons Lisens