Section 11.4. The Subversion API


11.4. The Subversion API

Subversion clients interact with a Subversion repository by linking against the Subversion client libraries, which provide a comprehensive API for manipulating the repository. By using the Subversion APIs, you can write complex applications to provide new tools capable of providing new functionality, wrapping old functionality in a manner more conducive to your process, or automating complex tasks. There are even bindings for the API available in a number of different languages (currently, C, C++, Java, Perl, and Python).

The Subversion libraries are a large and full-featured set of interfaces that could probably fill an entire book of their own. So, instead of going into a long-winded (read: boring) discussion on how to call this function or initialize that data structure, I'll instead whet your appetite by diving in and showing you a small example program.

11.4.1. svntag

The example program is called svntag. Subversion's tagging via copies can be confusing for some users who are coming from a CVS background. To make the transition from CVS to Subversion easier for them, this program automatically creates a "tag" by copying the trunk into the /tags directory, while requiring only a tag name from the user.

Of course, this particular example could be accomplished much more easily by just writing a script that wraps the svn copy command, but it serves its purpose here of illustrating the Subversion API. To better serve as an example, it is also somewhat incomplete in its error checking and hardcodes a few pieces of information that should never be hardcoded in a production application (such as the base URL for the repository).

 #include <unistd.h> #include <svn_client.h> #include <svn_config.h> #include <svn_pools.h> #include <svn_cmdline.h> /* Define some global structs */ svn_client_ctx_t*       context; svn_opt_revision_t      revision; /* Define some path strings */ const char*             baseURL = "http://svn.mydomain.com/testrepos/"; const char*             tagsDir = "tags/"; const char*             trunkDir = "trunk"; const char*             tagname; char*                   destURL; /* The base string for tags */ const char*             logBase = "Created new tag: "; /* Creates the commit log for the tagging */ svn_error_t* getCommitLog(const char** log_msg, const char** tmp_file,                           apr_array_header_t* commit_items,                           void* baton, apr_pool_t* pool) {    /* Fill the commit log */    *log_msg = apr_psprintf(pool, "%s%s", logBase, tagname);    return SVN_NO_ERROR; } /* Initialize the Subversion API context */ svn_error_t* initializeContext(apr_pool_t* pool) {    /* Create a new context */    SVN_ERR(svn_client_create_context(&context, pool));    /* Get the configuration data structure for the context */    SVN_ERR(svn_config_get_config(&(context->config), NULL, pool));    /* Set the callback function for setting the commit log */    context->log_msg_func = getCommitLog;    context->log_msg_baton = NULL;    return SVN_NO_ERROR; } /* Parse the command line */ int parseCmdLine(int argc, char** argv, apr_pool_t* pool) {    if(argc != 2) {       printf("Usage: svntag TAGNAME\n");       return -1;    }    /* Set the tag name */    tagname = argv[1];    /* Construct the destination URL */    destURL = apr_psprintf(pool, "%s%s%s", baseURL, tagsDir, tagname);    return 0; } int main(int argc, char** argv) {    apr_pool_t* pool;    /* Perform command-line application initializations */    svn_cmdline_init("svntag", stderr);    /* Initialize the memory pool */    pool = svn_pool_create(NULL);    /* Parse the command line */    if(parseCmdLine(argc, argv, pool) < 0) return -1;    /* Initialize the Subversion API */    SVN_INT_ERR(initializeContext(pool));    /* Set the revision */    revision.kind = svn_opt_revision_head;    /* Perform the copy */    {       svn_client_commit_info_t* commitInfo;       char* trunkURL;       trunkURL = apr_psprintf(pool, "%s%s", baseURL, trunkDir);       SVN_INT_ERR(svn_client_copy(&commitInfo,                                   trunkURL,                                   &revision,                                   destURL,                                   context,                                   pool));     }     return 0; } 

That's it. That's the whole program. I'll explain how to compile it in a little while, but first, let's look at what each part of the program does, and why.

Initial Includes and Defines

At the beginning of the program, you see a number of includes, as well as the definitions for several global variables. Let's start by looking at the includes.

 #include <unistd.h> #include <svn_client.h> #include <svn_config.h> #include <svn_pools.h> #include <svn_cmdline.h> 

The Subversion API consists of a large number of header files that segregate the API into different subsets of functionality. For example, if you need to deal with contextual diffing, you would include svn_diff.h. If you you need to deal with authentication to a repository, include svn_auth.h. In this program, as you can see, we've included four Subversion API header files (plus the common unistd.h header). Three of these, svn_cmdline.h, svn_config.h, and svn_pools.h, provide common functionality that is needed in almost every application. The third, svn_cmdline.h, provides the interface to the libsvn_client library, which includes functions for implementing the familiar Subversion client commands.

After the header includes, you see a number of global variable defines.

 /* Define some global structs */ svn_client_ctx_t*       context; svn_opt_revision_t      revision; /* Define some path strings */ const char*             baseURL = "http://svn.mydomain.com/testrepos/"; const char*             tagsDir = "tags/"; const char*             trunkDir = "trunk"; const char*             tagname; char*                   destURL; /* The base string for tags */ const char*             logBase = "Created new tag: "; 

The first two of these are structures from the Subversion API that probably make no sense to you. That's okay; ignore them for now. I'll discuss their purpose later, when I show where they are actually used. After the structures are a bunch of strings. These are used for storing the URLs given to the Subversion API, as well as the log message for inclusion with each tagging.

Memory Pools

Before we continue with our program, let's take a minute to talk about memory pools. The Subversion libraries are built atop the Apache Portable Runtime (APR) project. APR is a library designed to give a portable interface to programs, which then communicates with the platform-specific interfaces of different operating systems. This library is the reason why Subversion is runnable on such a wide array of different platforms. One of APR's more complex interfaces is its memory pool system. To allow memory to be allocated and disposed of cleanly and efficiently, APR allows programs to create a memory pool that provides a dynamically allocated block of memory. Programs can then use that memory as they see fit, and then deallocate the entire block at an appropriate time. Additionally, APR allows blocks of memory to be chained together (and intelligently shared behind the scenes). Chained memory pools can then be cleared individually or all at once (a very powerful feature).

The Subversion libraries make extensive use of APR memory pools, and expose them frequently in the Subversion API. To make their creation a little bit easier in the context of Subversion, though, the Subversion API wraps the APR memory pool manipulation functions with its own versions. In the case of our program, we'll use the svn_pool_create() function, which acts as a wrapper to the apr_pool_create_ex. I'll explain its use in a little more detail when I discuss the main() function in a little while.

Initializing the Client Context

Moving to the list, we'll skip over the getCommitLog() function (for now) and look at the next function, initializeContext().

 svn_error_t* initializeContext(apr_pool_t* pool) {    /* Create a new context */    SVN_ERR(svn_client_create_context(&context, pool));    /* Get the configuration data structure for the context */    SVN_ERR(svn_config_get_config(&(context->config), NULL, pool));    /* Set the callback function for setting the commit log */    context->log_msg_func = getCommitLog;    context->log_msg_baton = NULL;    return NULL; } 

The Subversion svn_client_ctx_t structure is used to store context for a client program's repository access session. This includes callback functions that are used by various client command functions, as well as configuration information. Contexts also include the concept of a baton, which is used to pass state information to the various callback functions (each callback function has an associated baton). The client context structure is defined as follows.

 typedef struct svn_client_ctx_t {    svn_auth_baton_t *           auth_baton;    svn_wc_notify_func_t         notify_func;    void *                       notify_baton;    svn_client_get_commit_log_t  log_msg_func;    void *                       log_msg_baton;    apr_hash_t *                 config;    svn_cancel_func_t            cancel_func;    void *                       cancel_baton; } svn_client_ctx_t; 

Looking back to the initializeContext() function, you'll see that it starts off by calling svn_client_create_context(). This takes our empty context pointer and allocates a new context structure for us to use. Notice how it also takes a pointer to our memory pool, which it uses for allocating the actual context.

You'll also notice that the whole svn_client_create_context() function call is wrapped with the SVN_ERR macro. This is a convenience macro that is used for checking the return value of the enclosed function for an error. If svn_client_create_context() returns a non-null svn_error_t pointer, the SVN_ERR macro will return the error value from the current function.

Next, we make a call to svn_config_get_config() and pass a pointer to the config field in our context, along with our memory pool. This function is used to load standard Subversion configuration information from the standard Subversion configuration files. If we had passed a string with the path to a directory (instead of NULL) as the second argument, svn_config_get_config() would have loaded the configuration from that directory instead of the standard system-wide and user-specific sources.

Finally, the last remaining item we need to initialize in our context is the log message callback function. This function is called whenever Subversion needs to obtain a log message for a commit. In this case, we set it up to point to getCommitLog(), which I will discuss in the next section. Because our commit log function doesn't need any state propagated from one call to the next, we can set the log message baton to NULL.

Setting the Commit Log

Let's go back up a little ways and look at the getCommitLog() function that we skipped over earlier.

 svn_error_t* getCommitLog(const char** log_msg, const char** tmp_file,                           apr_array_header_t* commit_items,                           void* baton, apr_pool_t* pool) {    /* Fill the commit log */    *log_msg = apr_psprintf(pool, "%s%s", logBase, tagname);    return SVN_NO_ERROR; } 

The getCommitLog() function is responsible for supplying a log message to the Subversion client library backend, on demand. The function is never directly called by our program, but instead, a pointer to it is set in the client context structure and called internally by various other client library functions. When this function is called, it receives pointers to two unallocated strings (which it is responsible for filling). It also receives an array consisting of some or all of the items being committed, a pointer to the commit log baton that was set for the client context, and a pointer to the pool that should be used for all allocations.

The two unallocated strings that the commit log function is responsible for allocating are the log message (log_msg) and a path indicating a temporary file that contains the log message (tmp_file). If there is no temporary file, the tmp_file parameter can be set to NULL. Similarly, if you want to cancel the commit, you can set the log_msg variable to NULL.

In the case of our getCommitLog() function, though, we just want to set a simple log message that gives the name of the tag that was created. So, we use the apr_psprintf() function to generate our log message, and set log_msg to point to it. The apr_psprintf() function works the same as an sprintf(), but takes a pointer to the memory pool to allocate the string.

The Main Program

Finally, we come to the main function of our program. This is where the previous functions are all tied together. It is also where the actual copying of the trunk directory occurs.

 int main(int argc, char** argv) {    apr_pool_t* pool;    /* Perform command-line application initializations */    svn_cmdline_init("svntag", stderr);    /* Initialize the memory pool */    pool = svn_pool_create(NULL);    /* Parse the command line */    if(parseCmdLine(argc, argv) < 0) return -1;    /* Initialize the Subversion API */    SVN_INT_ERR(initializeContext(pool));    /* Set the revision */    revision.kind = svn_opt_revision_head;    /* Perform the copy */    {       svn_client_commit_info_t* commitInfo;       char* trunkURL;       trunkURL = apr_psprintf(pool, "%s%s", baseURL, trunkDir);       SVN_INT_ERR(svn_client_copy(&commitInfo,                                   trunkURL,                                   &revision,                                   destURL,                                   context,                                   pool));   }   return 0; } 

At the top of the main() function, you'll see a pointer of type apr_pool_t declared. This is the main memory pool for our program, and it is passed around to every other function that needs it.

After the memory pool is declared, the first thing our main() function does is call the svn_cmdline_init() function, which performs initializations for the underlying Subversion library, specific to a command-line program. After that, we'll initialize our memory pool with a call to svn_pool_create(). Normally, svn_pool_create() takes a pointer to the pool's parent pool, but because this is our base memory pool, we have no parent, and pass NULL instead.

The next part of the main() function parses the program's command-line parameters. The parseCmdLine() function is one local to our program that extracts the tag name from the command parameters. The function itself is self-explanatory enough that I won't go into it specifically.

Following the command-line parsing, we call the initializeContext() function that was discussed earlier. You'll notice that the initializeContext() function call is wrapped by the SVN_INT_ERR() macro. This is a convenience macro supplied by the Subversion API that checks the return result of the function for an error. If an error occurs, it outputs the error to stderr and returns EXIT_FAILURE.

The revision structure is set to point to the HEAD revision (because our program always copies from HEAD).

Finally, the actual copy of the directory is performed. This is done using the function svn_client_copy(), which takes the path to be copied from, a pointer to the revision, the destination URL, the context that we set up earlier, and our memory pool. It also takes a pointer to an svn_client_commit_info_t structure, which will be filled with some information about the commit that occurred.

Compiling the Program

Our program needs to be compiled with a few references to the Subversion libraries, as well as the APR libraries and the Neon library. The best way to get these libraries is through the svn-config program. In some versions of Subversion, though, the output of svn-config is broken and won't produce valid values for passing directly to gcc. Instead, you may have to run svn-config separately and copy the valid parameters by hand (or if you're using a Makefile, write a filter to strip out the invalid values). In this case, I compiled the preceding application using the following command lines. You'll note, though, that I had to also add -lsvn_client-1 to the command line for linking, even though it is not included by svn-config -libs output.

[View full width]

$ svn-config --cflags -g -O2 -march=athlon -fomit-frame-pointer -pthread -DNEON_ZLIB - DNEON_SSL $ svn-config --includes -I/usr/include/subversion-1 -I/usr/include/neon @SVN_DB_INCLUDES@ - I/usr/include/apache2 -I/usr/include/apache2 $ gcc -g -O2 -march=athlon -fomit-frame-pointer -pthread -DNEON_ZLIB -DNEON_SSL -Wall -I /usr/include/subversion-1 -I/usr/include/neon -I/usr/include/apache2 -I/usr/include/apache2 -c svntag.c $ svn-config --libs -lneon -lz -lssl -lcrypto -ldl -lxml2 -lz -lpthread -lm -L/usr/lib - laprutil-0 -lgdbm -ldb-4.1 -lexpat -L/usr/lib -lapr-0 -lrt -lm - lcrypt -lnsl -lpthread -ldl $ gcc -g -O2 -march=athlon -fomit-frame-pointer -pthread -DNEON_ZLIB -DNEON_SSL -Wall -lneon -lz -lssl -lcrypto -ldl -lxml2 -lz -lpthread -lm -L/usr/lib -laprutil-0 -lgdbm -ldb-4.1 -lexpat -L/usr/lib - lapr-0 -lrt -lm -lcrypt -lnsl -lpthread -ldl -lsvn_client-1 svntag.o -o svntag



    Subversion Version Control. Using The Subversion Version Control System in Development Projects
    Subversion Version Control. Using The Subversion Version Control System in Development Projects
    ISBN: 131855182
    EAN: N/A
    Year: 2005
    Pages: 132

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net