From: Jim Morris Date: 2005-06-08T11:26:38+09:00 Subject: Accessing SVN through Ruby/DL I was following the old thread regarding accessing SVN through Ruby, using SWIG or DL. I cobbled together the following to see how hard it would be to do this in DL. I implemented the svn_client_status2 function as that is one API not implemented in the current SWIG/Ruby binding to SVN (and it was the one I needed). I did both a command line parsing version and the Ruby/DL calling /usr/local/lib/libsvn_client-1.so. The latter was far easier to deal with even though the DL stuff is not really well documented and examples are far and few between, I hope this can be added to the samples, as it involves callbacks, structures and pointer types. (I can send the command line parsing version if anyone is interested). I suspect this Ruby/DL version will work on win32 given the correct path to the equivalent .DLL Anyway there is a huge amount of effort required to get the Ruby/DL to the same point that the Swig/Ruby bindings are currently at, but this example may get someone started if they are so inclined. The one thing I have tried to do with this approach is to make it more ruby'ish, by hiding the SVN pool and context stuff in the class SvnClient, and using a Proc Block for the callback. I'm interested in any feedback on this approach as I suspect I will be playing with Ruby/DL a lot more. Also any improvements and/or suggestions on how to handle the large number of enums the c version of SVN uses. --- snip Svnrb.rb ---- require 'dl/import' require 'dl/struct' module Svnrb extend DL::Importable # NOTE you may have to change this path depending on where your svn is installed # On win32 it will need to point to the relevant .DLL dlload "/usr/local/lib/libsvn_client-1.so" #,"/usr/local/lib/libsvn_subr-1.so" typealias("apr_pool_t*", "void*") typealias("apr_status_t", "int") # define some convenient structures used by SVN Svn_opt_revision_t= struct [ "int kind", "int revision" ] Svn_error_t= struct [ "int apr_err", "char *message", "void *child", "apr_pool_t *pool", "char *file", "long line" ] # used where we pass a pointer to a long which gets modified in the call # and where we need to actually read the modified value within Ruby LongArg= struct [ "long val" ] Svn_status_t = struct [ "void *entry", "int text_status", "int prop_status", "int locked", "int copied", "int switched", "int repos_text_status", "int repos_prop_status", "void *repos_lock" ] # an experimental way to match standard SVN status enum with the value # Could also use Constants here Svn_wc_status_kind= { # does not exist "svn_wc_status_none" => 1, # is not a versioned thing in this wc "svn_wc_status_unversioned" => 2, # exists, but uninteresting. "svn_wc_status_normal" => 3, # is scheduled for addition "svn_wc_status_added" => 4, # under v.c., but is missing "svn_wc_status_missing" => 5, # scheduled for deletion "svn_wc_status_deleted" => 6, # was deleted and then re-added "svn_wc_status_replaced" => 7, # text or props have been modified "svn_wc_status_modified" => 8, # local mods received repos mods "svn_wc_status_merged" => 9, # local mods received conflicting repos mods "svn_wc_status_conflicted" => 10, # resource marked as ignored "svn_wc_status_ignored" => 11, # an unversioned resource is in the way of the versioned resource "svn_wc_status_obstructed" => 12, # an unversioned path populated by an svn:external property "svn_wc_status_external" => 13, # directory doesn't contain a complete entries list "svn_wc_status_incomplete" => 14 } # the functions in various svn libraries we call in this example extern "int svn_cmdline_init(char *, void*)" extern "apr_pool_t *svn_pool_create_ex(apr_pool_t *, void *)" extern "void *svn_config_ensure(char *, apr_pool_t *)" extern "void *svn_client_create_context(void **, apr_pool_t *)" extern "void *svn_stream_for_stdout(void **, apr_pool_t *)" extern "void *svn_client_cat(void *, char *, Svn_opt_revision_t *, void *, apr_pool_t *)" extern "void *svn_client_status2(int *, char *, Svn_opt_revision_t *, void *, void *, int, int, int, int, int, void *, apr_pool_t *)" # a wrapper around access to the SVN Client library, to make it more "ruby-like" to the user class SvnClient @ctx= nil # active context @pool= nil # active pool # this initializes the svn library and gets the contect and pool for use in other calls def initialize(name) err= Svnrb::svn_cmdline_init(name, nil) raise "svn_cmdline_init failed" if err != 0 @pool= Svnrb::svn_pool_create_ex(nil, nil) raise "svn_pool_create_ex" if @pool == nil err= Svnrb::svn_config_ensure("", @pool) raise "svn_config_ensure failed" if err != nil; # effectively void *, passed in as void **, will get the pointer to the context # Ruby does not need to every read this value, it just passes it through to subsequent calls # ditto for the pool tctx= DL.malloc(DL.sizeof('P')) err = Svnrb::svn_client_create_context(tctx, @pool); raise "svn_client_create_context failed" if err != nil @ctx= tctx.ptr # this becomes the contect to use for other calls end # gets the stdout stream for use in cat def getStdoutStream sto= DL.malloc(DL.sizeof('P')) # long * err= Svnrb::svn_stream_for_stdout(sto, @pool) sto.ptr end # implements the client cat call # rev is -1 to get HEAD, and a rev number for any other revision def cat(stream, path, rev) revision= Svn_opt_revision_t.malloc if rev >= 0 revision.kind= 1 # revision # revision.revision= rev else revision.kind= 7 # HEAD end err= Svnrb::svn_client_cat(stream, path, revision, @ctx, @pool) if err != nil terr= Svn_error_t.new(err) raise "svn_client_cat failed: (#{terr.apr_err}) #{terr.message}" end end # call sthe Client Status2 function # url is the WC path # rev is -1 to get HEAD, and a rev number for any other revision # proc is the proc method callback for each resource which will get two parameters: path and status # status is a structure of type Svn_status_t # returns the current Youngest revision in the Repository def status(url, rev, &proc) resrev= LongArg.malloc revision= Svn_opt_revision_t.malloc if rev >= 0 revision.kind= 1 # revision # revision.revision= rev else revision.kind= 7 # HEAD end # process callback and call the supplied Proc mycb= DL.callback('0PSP'){ |baton,path,pstatus| if pstatus status= Svn_status_t.new(pstatus) proc.call(path, status) end } err= Svnrb::svn_client_status2(resrev, url, revision, mycb, nil, 1, 1, 1, 0, 1, @ctx, @pool) resrev.val end end end if $0 == __FILE__ # test it, NOTE these test are specific to paths in my WC, yours may vary svn= Svnrb::SvnClient.new("testsvn") #outStream= svn.getStdoutStream #svn.cat(outStream, "/home/morris/work/perl/vcvs.pl", -1) #svn.cat(outStream, "/home/morris/work/perl/.dddd", 7) # get the status of the specified WC path, gets the HEAD revision, and only prints out the status of versioned resources rev= svn.status("/home/morris/work/perl", -1) { |path, status| print path, "-> ", status.text_status, " - ", status.prop_status, "\n" if status.text_status != Svnrb::Svn_wc_status_kind["svn_wc_status_unversioned"] } print "rev= ", rev, "\n" end --- end snip ----