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.3'

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)



159
160
161
# File 'lib/svn_wc.rb', line 159

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

Instance Attribute Details

#ctxObject (readonly)

Returns the value of attribute ctx.



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

def ctx
  @ctx
end

#cur_fileObject

– TODO revist these ++



166
167
168
# File 'lib/svn_wc.rb', line 166

def cur_file
  @cur_file
end

#reposObject (readonly)

Returns the value of attribute repos.



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

def repos
  @repos
end

#svn_passObject

– TODO revist these ++



166
167
168
# File 'lib/svn_wc.rb', line 166

def svn_pass
  @svn_pass
end

#svn_repo_config_fileObject

– TODO revist these ++



166
167
168
# File 'lib/svn_wc.rb', line 166

def svn_repo_config_file
  @svn_repo_config_file
end

#svn_repo_config_pathObject

– TODO revist these ++



166
167
168
# File 'lib/svn_wc.rb', line 166

def svn_repo_config_path
  @svn_repo_config_path
end

#svn_repo_masterObject

– TODO revist these ++



166
167
168
# File 'lib/svn_wc.rb', line 166

def svn_repo_master
  @svn_repo_master
end

#svn_repo_working_copyObject

– TODO revist these ++



166
167
168
# File 'lib/svn_wc.rb', line 166

def svn_repo_working_copy
  @svn_repo_working_copy
end

#svn_userObject

– TODO revist these ++



166
167
168
# File 'lib/svn_wc.rb', line 166

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)


279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/svn_wc.rb', line 279

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



224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/svn_wc.rb', line 224

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



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/svn_wc.rb', line 335

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



308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/svn_wc.rb', line 308

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)


846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
# File 'lib/svn_wc.rb', line 846

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



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/svn_wc.rb', line 189

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
    begin
      #FileUtils.rm_rf @svn_repo_working_copy
      FileUtils.mkdir_p @svn_repo_working_copy, :force => true
    rescue
    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 = '') ⇒ Object

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



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
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
# File 'lib/svn_wc.rb', line 753

def info(file='')
  if file and not (file.empty? or file.nil? or file.class != String)
    wc_path = file
  else
    wc_path = self.svn_repo_working_copy
  end

  r_info = Hash.new
  begin
    @ctx.info(wc_path) do |path, type|
      r_info[:last_changed_author] = type.last_changed_author
      r_info[:last_changed_rev]  = type.last_changed_rev
      r_info[:last_changed_date] = type.last_changed_date
      r_info[:conflict_old]    = type.conflict_old
      #r_info[:tree_conflict]   = type.tree_conflict
      r_info[:repos_root_url]  = type.repos_root_url
      r_info[:repos_root_URL]  = type.repos_root_URL
      r_info[:copyfrom_rev]    = type.copyfrom_rev
      r_info[:copyfrom_url]    = type.copyfrom_url
      #r_info[:working_size]    = type.working_size
      r_info[:conflict_wrk]    = type.conflict_wrk
      r_info[:conflict_new]    = type.conflict_new
      r_info[:has_wc_info]     = type.has_wc_info
      r_info[:repos_UUID]      = type.repos_UUID
      r_info[:checksum]        = type.checksum
      r_info[:prop_time], r_info[:text_time] = type.prop_time, type.text_time
      r_info[:prejfile],  r_info[:schedule]  = type.prejfile, type.schedule
      r_info[:taguri], r_info[:lock] = type.taguri, type.lock
      r_info[:rev], r_info[:dup]     = type.rev, type.dup
      r_info[:url], r_info[:URL]     = type.url, type.URL
      #r_info[:changelist]  = type.changelist
      #r_info[:depth], r_info[:size] = type.depth, type.size
    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



564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
# File 'lib/svn_wc.rb', line 564

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.empty? ? abs_path : File.join(abs_path, 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



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
# File 'lib/svn_wc.rb', line 616

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 # why this check and not file.exists?
        f_path = File.join(dir, ef)
        if File.file? f_path
          (f_path)
        elsif File.directory? f_path
          _walk_entries(f_path)
        end
      }
    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:



871
872
873
874
875
876
877
878
879
880
881
882
883
884
# File 'lib/svn_wc.rb', line 871

def propset(type, files, dir_path=self.svn_repo_working_copy)
  raise RepoAccessError, '"ignore" is 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



836
837
838
839
# File 'lib/svn_wc.rb', line 836

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



174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/svn_wc.rb', line 174

def set_conf(conf)
  begin
    conf = load_conf(conf)
    @svn_user              = conf['svn_user']
    @svn_pass              = conf['svn_pass']
    @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:



922
923
924
925
# File 'lib/svn_wc.rb', line 922

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)


455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/svn_wc.rb', line 455

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



891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
# File 'lib/svn_wc.rb', line 891

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



367
368
369
370
371
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
# File 'lib/svn_wc.rb', line 367

def update(paths=[])

  if paths.empty? then paths = self.svn_repo_working_copy 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
    #       Exception => e
    rescue Exception => err
      raise RepoAccessError, "Update Failed: #{err.message}"
    end
  end

  _post_update_entries

  return rev, @modified_entries

end