Accessing SVN through Ruby/DL

J

Jim Morris

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 ----
 
T

Takaaki Tateishi

Jim said:
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).

Thank you for your effort,
I will put the sample program on the following site.

http://rubyforge.org/projects/dlcookbook

Thanks,
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top