Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Version Control with Subversion
Prev Home Next


Version Control with Subversion - Using the APIs - Using Languages Other than C and C++

Using Languages Other than C and C++

If you are interested in using the Subversion libraries in conjunction with something other than a C program—say a Python or Perl script—Subversion has some support for this via the Simplified Wrapper and Interface Generator (SWIG). The SWIG bindings for Subversion are located in subversion/bindings/swig and whilst still maturing, they are in a usable state. These bindings allow you to call Subversion API functions indirectly, using wrappers that translate the datatypes native to your scripting language into the datatypes needed by Subversion's C libraries.

There is an obvious benefit to accessing the Subversion APIs via a language binding—simplicity. Generally speaking, languages such as Python and Perl are much more flexible and easy to use than C or C++. The sort of high-level datatypes and context-driven type checking provided by these languages are often better at handling information that comes from users. As you know, humans are proficient at botching up input to a program, and scripting languages tend to handle that misinformation more gracefully. Of course, often that flexibility comes at the cost of performance. That is why using a tightly-optimized, C-based interface and library suite, combined with a powerful, flexible binding language, is so appealing.

Let's look at a sample program that uses Subversion's Python SWIG bindings to recursively crawl the youngest repository revision, and print the various paths reached during the crawl.

Example 8.2. Using the Repository Layer with Python

#!/usr/bin/python

"""Crawl a repository, printing versioned object path names."""

import sys
import os.path
import svn.fs, svn.core, svn.repos

def crawl_filesystem_dir(root, directory, pool):
    """Recursively crawl DIRECTORY under ROOT in the filesystem, and return
    a list of all the paths at or below DIRECTORY.  Use POOL for all 
    allocations."""

    # Print the name of this path.
    print directory + "/"
    
    # Get the directory entries for DIRECTORY.
    entries = svn.fs.svn_fs_dir_entries(root, directory, pool)

    # Use an iteration subpool.
    subpool = svn.core.svn_pool_create(pool)

    # Loop over the entries.
    names = entries.keys()
    for name in names:
        # Clear the iteration subpool.
        svn.core.svn_pool_clear(subpool)

        # Calculate the entry's full path.
        full_path = directory + '/' + name

        # If the entry is a directory, recurse.  The recursion will return
        # a list with the entry and all its children, which we will add to
        # our running list of paths.
        if svn.fs.svn_fs_is_dir(root, full_path, subpool):
            crawl_filesystem_dir(root, full_path, subpool)
        else:
            # Else it's a file, so print its path here.
            print full_path

    # Destroy the iteration subpool.
    svn.core.svn_pool_destroy(subpool)

def crawl_youngest(pool, repos_path):
    """Open the repository at REPOS_PATH, and recursively crawl its
    youngest revision."""
    
    # Open the repository at REPOS_PATH, and get a reference to its
    # versioning filesystem.
    repos_obj = svn.repos.svn_repos_open(repos_path, pool)
    fs_obj = svn.repos.svn_repos_fs(repos_obj)

    # Query the current youngest revision.
    youngest_rev = svn.fs.svn_fs_youngest_rev(fs_obj, pool)
    
    # Open a root object representing the youngest (HEAD) revision.
    root_obj = svn.fs.svn_fs_revision_root(fs_obj, youngest_rev, pool)

    # Do the recursive crawl.
    crawl_filesystem_dir(root_obj, "", pool)
    
if __name__ == "__main__":
    # Check for sane usage.
    if len(sys.argv) != 2:
        sys.stderr.write("Usage: %s REPOS_PATH\n"
                         % (os.path.basename(sys.argv[0])))
        sys.exit(1)

    # Canonicalize (enough for Subversion, at least) the repository path.
    repos_path = os.path.normpath(sys.argv[1])
    if repos_path == '.': 
        repos_path = ''

    # Call the app-wrapper, which takes care of APR initialization/shutdown
    # and the creation and cleanup of our top-level memory pool.
    svn.core.run_app(crawl_youngest, repos_path)

This same program in C would need to deal with custom datatypes (such as those provided by the APR library) for representing the hash of entries and the list of paths, but Python has hashes (called “dictionaries”) and lists as built-in datatypes, and provides a rich collection of functions for operating on those types. So SWIG (with the help of some customizations in Subversion's language bindings layer) takes care of mapping those custom datatypes into the native datatypes of the target language. This provides a more intuitive interface for users of that language.

The Subversion Python bindings can be used for working copy operations, too. In the previous section of this chapter, we mentioned the libsvn_client interface, and how it exists for the sole purpose of simplifying the process of writing a Subversion client. The following is a brief example of how that library can be accessed via the SWIG bindings to recreate a scaled-down version of the svn status command.

Example 8.3. A Python Status Crawler

#!/usr/bin/env python

"""Crawl a working copy directory, printing status information."""

import sys
import os.path
import getopt
import svn.core, svn.client, svn.wc

def generate_status_code(status):
    """Translate a status value into a single-character status code,
    using the same logic as the Subversion command-line client."""

    if status == svn.wc.svn_wc_status_none:
        return ' '
    if status == svn.wc.svn_wc_status_normal:
        return ' '
    if status == svn.wc.svn_wc_status_added:
        return 'A'
    if status == svn.wc.svn_wc_status_missing:
        return '!'
    if status == svn.wc.svn_wc_status_incomplete:
        return '!'
    if status == svn.wc.svn_wc_status_deleted:
        return 'D'
    if status == svn.wc.svn_wc_status_replaced:
        return 'R'
    if status == svn.wc.svn_wc_status_modified:
        return 'M'
    if status == svn.wc.svn_wc_status_merged:
        return 'G'
    if status == svn.wc.svn_wc_status_conflicted:
        return 'C'
    if status == svn.wc.svn_wc_status_obstructed:
        return '~'
    if status == svn.wc.svn_wc_status_ignored:
        return 'I'
    if status == svn.wc.svn_wc_status_external:
        return 'X'
    if status == svn.wc.svn_wc_status_unversioned:
        return '?'
    return '?'

def do_status(pool, wc_path, verbose):
    # Calculate the length of the input working copy path.
    wc_path_len = len(wc_path)

    # Build a client context baton.
    ctx = svn.client.svn_client_ctx_t()

    def _status_callback(path, status, root_path_len=wc_path_len):
        """A callback function for svn_client_status."""

        # Print the path, minus the bit that overlaps with the root of
        # the status crawl
        text_status = generate_status_code(status.text_status)
        prop_status = generate_status_code(status.prop_status)
        print '%s%s  %s' % (text_status, prop_status, path[wc_path_len + 1:])
        
    # Do the status crawl, using _status_callback() as our callback function.
    svn.client.svn_client_status(wc_path, None, _status_callback,
                                 1, verbose, 0, 0, ctx, pool)

def usage_and_exit(errorcode):
    """Print usage message, and exit with ERRORCODE."""
    stream = errorcode and sys.stderr or sys.stdout
    stream.write("""Usage: %s OPTIONS WC-PATH
Options:
  --help, -h    : Show this usage message
  --verbose, -v : Show all statuses, even uninteresting ones
""" % (os.path.basename(sys.argv[0])))
    sys.exit(errorcode)
    
if __name__ == '__main__':
    # Parse command-line options.
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hv", ["help", "verbose"])
    except getopt.GetoptError:
        usage_and_exit(1)
    verbose = 0
    for opt, arg in opts:
        if opt in ("-h", "--help"):
            usage_and_exit(0)
        if opt in ("-v", "--verbose"):
            verbose = 1
    if len(args) != 1:
        usage_and_exit(2)
            
    # Canonicalize (enough for Subversion, at least) the working copy path.
    wc_path = os.path.normpath(args[0])
    if wc_path == '.': 
        wc_path = ''

    # Call the app-wrapper, which takes care of APR initialization/shutdown
    # and the creation and cleanup of our top-level memory pool.
    svn.core.run_app(do_status, wc_path, verbose)

Subversion's language bindings unfortunately tend to lack the level of attention given to the core Subversion modules. However, there have been significant efforts towards creating functional bindings for Python, Perl, and Ruby. To some extent, the work done preparing the SWIG interface files for these languages is reusable in efforts to generate bindings for other languages supported by SWIG (which includes versions of C#, Guile, Java, MzScheme, OCaml, PHP, Tcl, and others). However, some extra programming is required to compensate for complex APIs that SWIG needs some help interfacing with. For more information on SWIG itself, see the project's website at https://www.swig.org/.



[43] Subversion uses ANSI system calls and datatypes as much as possible.

[44] Neon and Berkeley DB are examples of such libraries.


[an error occurred while processing this directive]
Version Control with Subversion
Prev Home Next

 
 
  Published under the terms of the Creative Commons License Design by Interspire