[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Accessing SVN through Ruby/DL

From: Jim Morris <morris_at_wolfman.com>
Date: 2005-06-08 04:28:21 CEST

I was following an 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 ----

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@subversion.tigris.org
For additional commands, e-mail: users-help@subversion.tigris.org
Received on Wed Jun 8 04:36:10 2005

This is an archived mail posted to the Subversion Users mailing list.