Class: SvnWc::RepoAccess

Inherits:
Object
  • Object
show all
Defined in:
lib/svn_wc.rb

Overview

class that provides API to (common) svn operations (working copy of a repo) also exposes the svn ruby bindings directly

It aims to provide (simple) client CLI type behavior, for working directory repository management. in an API

Constant Summary collapse

VERSION =
'0.0.6'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(conf = nil, checkout = false, force = false) ⇒ RepoAccess

initialization three optional parameters

  1. Path to yaml conf file (default used, if none specified)

  2. Do a checkout from remote svn repo (usually, necessary with first time set up only)

  3. Force. Overwrite anything that may be preventing a checkout



144
145
146
147
148
149
150
# File 'lib/svn_wc.rb', line 144

def initialize(conf=nil, checkout=false, force=false)
  set_conf(conf) if conf
  do_checkout(force) if checkout == true

  # instance var of out open repo session
  @ctx = svn_session
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object

‘expose the abstraction’ introduce Delegation, if we don’t define the method pass it on to the ruby bindings.

– (yup, this is probably asking for trouble) ++



161
162
163
# File 'lib/svn_wc.rb', line 161

def method_missing(sym, *args, &block)
  @ctx.send sym, *args, &block
end

Instance Attribute Details

#ctxObject (readonly)

Returns the value of attribute ctx.



172
173
174
# File 'lib/svn_wc.rb', line 172

def ctx
  @ctx
end

#cur_fileObject

– TODO revist these ++



168
169
170
# File 'lib/svn_wc.rb', line 168

def cur_file
  @cur_file
end

#force_checkoutObject

– TODO revist these ++



168
169
170
# File 'lib/svn_wc.rb', line 168

def force_checkout
  @force_checkout
end

#reposObject (readonly)

Returns the value of attribute repos.



172
173
174
# File 'lib/svn_wc.rb', line 172

def repos
  @repos
end

#svn_passObject

– TODO revist these ++



168
169
170
# File 'lib/svn_wc.rb', line 168

def svn_pass
  @svn_pass
end

#svn_repo_config_fileObject

– TODO revist these ++



168
169
170
# File 'lib/svn_wc.rb', line 168

def svn_repo_config_file
  @svn_repo_config_file
end

#svn_repo_config_pathObject

– TODO revist these ++



168
169
170
# File 'lib/svn_wc.rb', line 168

def svn_repo_config_path
  @svn_repo_config_path
end

#svn_repo_masterObject

– TODO revist these ++



168
169
170
# File 'lib/svn_wc.rb', line 168

def svn_repo_master
  @svn_repo_master
end

#svn_repo_working_copyObject

– TODO revist these ++



168
169
170
# File 'lib/svn_wc.rb', line 168

def svn_repo_working_copy
  @svn_repo_working_copy
end

#svn_userObject

– TODO revist these ++



168
169
170
# File 'lib/svn_wc.rb', line 168

def svn_user
  @svn_user
end

Instance Method Details

#add(files = [], recurse = true, force = false, no_ignore = false) ⇒ Object

add entities to the repo

pass a single entry or list of file(s) with fully qualified path, which must exist,

raises RepoAccessError if something goes wrong

– “svn/client.rb” Svn::Client

def add(path, recurse=true, force=false, no_ignore=false)
  Client.add3(path, recurse, force, no_ignore, self)
end

++

Raises:

  • (ArgumentError)


284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/svn_wc.rb', line 284

def add(files=[], recurse=true, force=false, no_ignore=false)

  # TODO make sure args are what is expected for all methods
  raise ArgumentError, 'files is empty' unless files

  svn_session() do |svn|
    begin
      files.each do |ef|
         svn.add(ef, recurse, force, no_ignore)
      end
    #rescue Svn::Error::ENTRY_EXISTS, 
    #       Svn::Error::AuthnNoProvider,
    #       #Svn::Error::WcNotDirectory,
    #       Svn::Error::SvnError => e
    rescue Exception => excp
      raise RepoAccessError, "Add Failed: #{excp.message}"
    end
  end
end

#checkoutObject Also known as: co

checkout

create a local working copy of a remote svn repo (creates dir if not exist) raises RepoAccessError if something goes wrong



229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/svn_wc.rb', line 229

def checkout
  begin
    svn_session() do |ctx|
       ctx.checkout(@svn_repo_master, @svn_repo_working_copy)
    end
  #rescue Svn::Error::RaLocalReposOpenFailed,
  #       Svn::Error::FsAlreadyExists,
  #rescue Errno::EACCES => e
  rescue Exception => err
    raise RepoAccessError, err.message
  end
end

#commit(files = [], msg = '') ⇒ Object Also known as: ci

commit entities to the repository

params single or list of files (full relative path (to repo root) needed)

optional message

raises RepoAccessError if something goes wrong returns the revision of the commmit



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/svn_wc.rb', line 340

def commit(files=[], msg='')
  if files and files.empty? or files.nil? then files = self.svn_repo_working_copy end

  rev = ''
  svn_session(msg) do |svn|
    begin
      rev = svn.commit(files).revision
    #rescue Svn::Error::AuthnNoProvider,
    #       #Svn::Error::WcNotDirectory,
    #       Svn::Error::IllegalTarget,
    #       #Svn::Error::EntryNotFound => e
    #       Exception => e
    rescue Exception => err
      raise RepoAccessError, "Commit Failed: #{err.message}"
    end
  end
  rev
end

#delete(files = [], recurs = false) ⇒ Object Also known as: rm

delete entities from the repository

pass single entity or list of files with fully qualified path, which must exist,

raises RepoAccessError if something goes wrong



313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/svn_wc.rb', line 313

def delete(files=[], recurs=false)
  svn_session() do |svn|
    begin
      svn.delete(files)
    #rescue Svn::Error::AuthnNoProvider,
    #       #Svn::Error::WcNotDirectory,
    #       Svn::Error::ClientModified,
    #       Svn::Error::SvnError => e
    rescue Exception => err
      raise RepoAccessError, "Delete Failed: #{err.message}"
    end
  end
end

#diff(file = '', rev1 = '', rev2 = '') ⇒ Object

By Default compares current working directory file with ‘HEAD’ in repository (NOTE: does not yet support diff to previous revisions) – TODO support diffing previous revisions ++

Raises:

  • (ArgumentError)


831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
# File 'lib/svn_wc.rb', line 831

def diff(file='', rev1='', rev2='')
  raise ArgumentError, 'file list empty or nil' unless file and file.size

  raise RepoAccessError, "Diff requires an absolute path to a file" \
     unless File.exists? file

  # can also use new (updated) svn.status(f)[0][:repo_rev]
  rev = info(file)[:rev] 
  out_file = Tempfile.new("svn_diff")
  err_file = Tempfile.new("svn_diff")
  svn_session() do |svn|
    begin
      svn.diff([], file, rev, file, "WORKING", out_file.path, err_file.path)
    rescue Exception => e
           #Svn::Error::EntryNotFound => e
      raise RepoAccessError, "Diff Failed: #{e.message}"
    end
  end
  out_file.readlines
end

#do_checkout(force = false) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/svn_wc.rb', line 193

def do_checkout(force=false)
  if @svn_repo_working_copy.nil? 
    raise RepoAccessError, 'conf file not loaded! - Fatal Error' 
  end
  ## do checkout if not exists at specified local path

  if force or @force_checkout
    begin
      FileUtils.rm_rf @svn_repo_working_copy
      FileUtils.mkdir_p @svn_repo_working_copy
    rescue Errno::EACCES => err
      raise RepoAccessError, err.message
    end
  else
    if File.directory? @svn_repo_working_copy
      raise RepoAccessError, 'target local directory  ' << \
      "[#{@svn_repo_working_copy}] exists, please remove" << \
      'or specify another directory'
    end
    begin
      FileUtils.mkdir_p @svn_repo_working_copy
    rescue Errno::EACCES => err
      raise RepoAccessError, err.message
    end
  end

  checkout
end

#info(file = nil) ⇒ Object

get detailed repository info about a specific file or (by default) the entire repository – TODO - document all the params available from this command ++



750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
# File 'lib/svn_wc.rb', line 750

def info(file=nil)
  wc_path = self.svn_repo_working_copy
  wc_path = file if file and file.class == String

  r_info = Hash.new
  type_info = %w(
                 last_changed_author last_changed_rev
                 last_changed_date conflict_old
                 repos_root_url repos_root_URL
                 copyfrom_rev copyfrom_url conflict_wrk 
                 conflict_new has_wc_info repos_UUID
                 checksum prop_time text_time prejfile
                 schedule taguri lock rev dup url URL
                ) # changelist depth size tree_conflict working_size

  begin
    @ctx.info(wc_path) do |path, type|
      type_info.each do |t_info|
        r_info[:"#{t_info}"] = type.method(:"#{t_info}").call
      end
    end
  #rescue Svn::Error::WcNotDirectory => e
  #       #Svn::Error::RaIllegalUrl,
  #       #Svn::Error::EntryNotFound,
  #       #Svn::Error::RaIllegalUrl,
  #       #Svn::Error::WC_NOT_DIRECTORY
  #       #Svn::Error::WcNotDirectory => e
  rescue Exception => e
    raise RepoAccessError, "cant get info: #{e.message}"
  end
  r_info
 
end

#list(wc_path = self.svn_repo_working_copy, rev = 'head', verbose = nil, depth = 'infinity') ⇒ Object Also known as: ls

list (ls)

list all entries at (passed) dir level in repo use repo root if not specified

no repo/file info is returned, just a list of files, with abs_path

optional



591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/svn_wc.rb', line 591

def list(wc_path=self.svn_repo_working_copy, rev='head', 
                 verbose=nil, depth='infinity')
  paths = []
  svn_session() do |svn|

    begin
      svn.list(wc_path, rev, verbose, depth) do |path, dirent, lock, abs_path|
        #paths.push(path.empty? ? abs_path : File.join(abs_path, path))
        f_rec = Hash.new
        f_rec[:entry] = path
        f_rec[:last_changed_rev] = dirent.created_rev
        paths.push f_rec
      end
    #rescue Svn::Error::AuthnNoProvider,
    #       #Svn::Error::WcNotDirectory,
    #       Svn::Error::FS_NO_SUCH_REVISION,
    #       #Svn::Error::EntryNotFound => e
    #       Exception => e
    rescue Exception => e
      raise RepoAccessError, "List Failed: #{e.message}"
    end
  end

  paths

end

#list_entries(dir = self.svn_repo_working_copy, file = nil, verbose = false) ⇒ Object

Get list of all entries at (passed) dir level in repo use repo root if nothing passed

params [String, String, String] optional params, defaults to repo root

if file passed, get specifics on file, else get
into on all in dir path passed
3rd arg is verbose flag, if set to true, lot's
more info is returned about the object

returns [Array] list of entries in svn repository



643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/svn_wc.rb', line 643

def list_entries(dir=self.svn_repo_working_copy, file=nil, verbose=false)

  @entry_list, @show, @verbose = [], true, verbose
   
  Svn::Wc::AdmAccess.open(nil, dir, false, 5) do |adm|
    @adm = adm
    if file.nil?
      #also see walk_entries (in svn bindings) has callback
      adm.read_entries.keys.sort.each { |ef|
        next unless ef.length >= 1
        svn_entry = File.join(dir, ef)
         svn_entry
      }
    else
      (file)
    end
  end
  #XXX do we want nil or empty on no entries, choosing empty for now
  #@entry_list unless @entry_list.empty?
  @entry_list
end

#propset(type, files, dir_path = self.svn_repo_working_copy) ⇒ Object

currently supports type=‘ignore’ only – TODO support other propset’s ; also propget ++

Raises:



856
857
858
859
860
861
862
863
864
865
866
867
868
869
# File 'lib/svn_wc.rb', line 856

def propset(type, files, dir_path=self.svn_repo_working_copy)
  raise RepoAccessError, 'currently, "ignore" is the only supported propset' \
         unless type == 'ignore'

  svn_session() do |svn|
    files.each do |ef|
      begin
        svn.propset(Svn::Core::PROP_IGNORE, ef, dir_path)
      rescue Exception => e #Svn::Error::EntryNotFound => e
        raise RepoAccessError, "Propset (Ignore) Failed: #{e.message}"
      end
    end
  end
end

#revert(file_path = '') ⇒ Object

discard working copy changes, get current repository entry



821
822
823
824
# File 'lib/svn_wc.rb', line 821

def revert(file_path='')
  if file_path.empty? then file_path = self.svn_repo_working_copy end
  svn_session() { |svn| svn.revert(file_path) }
end

#set_conf(conf) ⇒ Object

set config file with abs path



177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/svn_wc.rb', line 177

def set_conf(conf)
  begin
    conf = load_conf(conf)
    @svn_user              = conf['svn_user']
    @svn_pass              = conf['svn_pass']
    @force_checkout        = conf['force_checkout']
    @svn_repo_master       = conf['svn_repo_master']
    @svn_repo_working_copy = conf['svn_repo_working_copy']
    @svn_repo_config_path  = conf['svn_repo_config_path']
    Svn::Core::Config.ensure(@svn_repo_config_path)
  rescue Exception => e
    raise RepoAccessError, 'errors loading conf file'
  end
end

#setup_auth_baton(auth_baton) ⇒ Object

:nodoc:



907
908
909
910
# File 'lib/svn_wc.rb', line 907

def setup_auth_baton(auth_baton) # :nodoc:
  auth_baton[Svn::Core::AUTH_PARAM_CONFIG_DIR] = @svn_repo_config_path
  auth_baton[Svn::Core::AUTH_PARAM_DEFAULT_USERNAME] = @svn_user
end

#status(path = '') ⇒ Object Also known as: stat

get status on dir/file path.

if nothing passed, does repo root

– TODO/XXX add optional param to return results as a data structure (current behavior) or as a puts ‘M’ File (like the CLI version, have the latter as the default, this avoids the awkward s.status(file)[:status] notation one could just say: s.status file and get the list displayed on stdout ++

Raises:

  • (ArgumentError)


480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/svn_wc.rb', line 480

def status(path='')

  raise ArgumentError, 'path not a String' if ! (path or path.class == String)

  if path and path.empty? then path = self.svn_repo_working_copy end

  status_info = Hash.new

  if File.file?(path)
    # is single file path
    file = path
    status_info = do_status(File.dirname(path), file)
  elsif File.directory?(path)
    status_info = do_status(path) 
  else
    raise RepoAccessError, "Arg is not a file or directory"
  end
 status_info

end

#svn_session(commit_msg = String.new) ⇒ Object



876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
# File 'lib/svn_wc.rb', line 876

def svn_session(commit_msg = String.new) # :nodoc:
  ctx = Svn::Client::Context.new

  # Function for commit messages
  ctx.set_log_msg_func do |items|
    [true, commit_msg]
  end

  # don't fail on non CA signed ssl server
  ctx.add_ssl_server_trust_file_provider

  setup_auth_baton(ctx.auth_baton)
  ctx.add_username_provider

  # username and password
  ctx.add_simple_prompt_provider(0) do |cred, realm, username, may_save|
    cred.username = @svn_user
    cred.password = @svn_pass
    cred.may_save = false
  end

  return ctx unless block_given?

  begin
    yield ctx
  #ensure
  #  warning!?
  #  ctx.destroy
  end
end

#update(paths = []) ⇒ Object Also known as: up

update local working copy with most recent (remote) repo version (does not resolve conflict - or alert or anything at the moment)

if nothing passed, does repo root

params optional: single or list of files (full relative path (to repo root) needed)

raises RepoAccessError if something goes wrong

alias up



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/svn_wc.rb', line 372

def update(paths=[])

  if paths.empty? 
    paths = self.svn_repo_working_copy
  else
    @limit_to_dir_path = paths
  end

  #XXX update is a bummer, just returns the rev num, not affected files
  #(svn command line up, also returns altered/new files - mimic that)
  # hence our inplace hack (_pre/_post update_entries)
  #
  # unfortunetly, we cant use 'Repos',  only works on local filesystem repo
  # (NOT remote)
  #p Svn::Repos.open(@svn_repo_master) # Svn::Repos.open('/tmp/svnrepo')
  _pre_update_entries

  rev = String.new
  svn_session() do |svn|
    begin
      #p svn.status paths
      rev = svn.update(paths, nil, 'infinity')
    #rescue Svn::Error::AuthnNoProvider, 
    #       #Svn::Error::FS_NO_SUCH_REVISION,
    #       #Svn::Error::WcNotDirectory,
    #       #Svn::Error::EntryNotFound => e
    rescue Exception => err
      raise RepoAccessError, "Update Failed: #{err.message}"
    end
  end

  _post_update_entries

  return rev, @modified_entries

end