LinuxDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


Using the Subversion Client API, Part 1
Pages: 1, 2

So How Do I Use This Stuff?

All right, enough background, on to the actual Subversion client API.



Subversion is broken into several libraries. From the client developer's point of view, the most important is libsvn_client, which holds the functions used to implement the various commands you've seen in the command line client. This library is a wrapper around the underlying libraries which manage access to the working copy (your checked out copy of the contents of the repository) and to the repository (via a number of possible paths).

Some Common Characteristics

All the functions in this library share some common characteristics. First, they all assume textual data (paths, URLs, raw text like a log entry or a username, etc.) is UTF-8 encoded and uses newlines for line endings. This provides consistency between clients and avoids the need for cumbersome and unnecessary locale and line ending data tagging. To convert your data into UTF-8, use the functions in svn_utf.h. For the purposes of this article, we will assume all input is in ASCII and will avoid conversion to UTF-8.

Second, each function takes an apr_pool_t pointer to use for memory allocation.

Third, each function takes a pointer to a structure called svn_client_ctx_t. This "client context" serves as a container for several different things that are used across many libsvn_client functions. For example, all the Subversion functions that commit a change to the repository require the client to provide them with a log message. To do this, they use a callback function and baton that are stored in the client context. Similarly, many of the functions need to provide progress notification to the calling application and, eventually, to the user. The library functions call a notification callback-baton pair that are passed in via the context. The client context also caches configuration options, so the libraries don't need to read them in whenever they require them.

To use the rest of libsvn_client, you will have to fill in a bare minimum of the context. Here's an example of how to do it:

svn_error_t *
set_up_client_ctx (svn_client_ctx_t **ctx, apr_pool_t *pool)
{
  /* allocate our context, using apr_pcalloc to ensure it is zeroed out. */
  *ctx = apr_pcalloc (pool, sizeof (*ctx));

  /* read in the client's config options. */
  SVN_ERR (svn_config_get_config(&(*ctx)->config, pool));

  /* set up an authentication baton.  details of how to use libsvn_auth are 
   * beyond the scope of this article, but for more details on its use you 
   * can read the code for the subversion command line client and the 
   * comments in svn_auth.h. */
  {
    svn_auth_baton_t *auth_baton;

    apr_array_header_t *providers
      = apr_array_make (pool, 1, sizeof (svn_auth_provider_object_t *));

    svn_auth_provider_object_t *username_wc_provider 
      = apr_pcalloc (pool, sizeof(*username_wc_provider));

    svn_wc_get_username_provider 
      (&(username_wc_provider->vtable),
       &(username_wc_provider->provider_baton), pool);

    *(svn_auth_provider_object_t **)apr_array_push (providers) 
      = username_wc_provider;

    svn_auth_open (&auth_baton, providers, pool);

    (*ctx)->auth_baton = auth_baton;
  }

  return SVN_NO_ERROR;
}

The comments in svn_client.h provide more details on the contents of svn_client_ctx_t.

Compiling and Linking

All you need now to start using the Subversion libraries are a few details on how to compile and link against them. For all the examples in this article, you will have to link against libsvn_client-1, libsvn_auth-1, and libsvn_subr-1. The header files are located in $(PREFIX)/include/subversion-1, where $(PREFIX) is either the path you specified for --prefix when configuring Subversion or /usr/local by default. You should also include the output of svn-config --includes --cflags --libs in your compile and link lines.

Eventually, you should be able to forget about manually including Subversion's include and libs, allowing svn-config to take care of the details. But for now you will have to do it yourself. Here's the Makefile I used when preparing this article, which should get you far enough along to get things working.

PREFIX=/Users/rooneg/Hacking/article

CC=cc

CFLAGS=`$(PREFIX)/bin/svn-config --cflags` -Wall

INCLUDES=`$(PREFIX)/bin/svn-config --includes` -I$(PREFIX)/include/subversion-1

LIBS=`$(PREFIX)/bin/svn-config --libs` -L$(PREFIX)/lib -lsvn_subr-1 \
     -lsvn_auth-1 -lsvn_client-1 -lsvn_wc-1

.c.o:
        $(CC) $(CFLAGS) $(INCLUDES) -c $<

basic-client: basic-client.o
        $(CC) $(CFLAGS) $(LIBS) basic-client.o -o $@

clean:
        rm -rf *.o
        rm -rf basic-client

A Practical Example

Now you're ready to write an actual application that uses libsvn_client. Let's say your company makes a web application. You store everything in a Subversion repository and can simply check out some parts of your source onto the web server and things just work. Suppose also that several of your web developers are unskilled in using version control tools. To simplify life for them, you're writing an application to let them deploy a site from the tree to the server, query what versions of each file they have there, and update to new versions from the repository.

You will need to use at least three functions from libsvn_client. svn_client_checkout deploys the site for the first time to a new server. svn_client_status checks the versions deployed on a given server. svn_client_update deploys new versions of the site to an existing install on the server. We'll look at each function in turn.

As you might guess, svn_client_checkout implements the svn checkout command. It takes as arguments the URL to check out from the repository, a path that will become the root of the new working copy it creates, and a number that indicates which revision of the URL you want to check out. There's also a boolean flag that indicates if the checkout should recurse into subdirectories inside the URL. Besides these specific arguments, the normal libsvn_client function arguments apply; a client context and a pool. If you want to provide feedback to your user as the checkout takes place, you can provide a notification callback and baton inside the context to be called each time something happens. Here's an example of how your application could use it:

void
deploy_notification_callback (void *baton,
                              const char *path,
                              svn_wc_notify_action_t action,
                              svn_node_kind_t kind,
                              const char *mime_type,
                              svn_wc_notify_state_t content_state,
                              svn_wc_notify_state_t prop_state,
                              svn_revnum_t revision)
{
  printf ("deploying %s\n", path);
}

void
deploy_new_site (const char *repos_url,
                 const char *target_path,
		 svn_client_ctx_t *ctx,
                 apr_pool_t *pool)
{
  svn_opt_revision_t revision = { 0 };
  svn_error_t *err;

  /* set up our notification callback.  our callback doesn't use a baton, so 
   * we can just leave that blank. */
  ctx->notify_func = deploy_notification_callback;

  /* grab the most recent version of the website. */
  revision.kind = svn_opt_revision_head;

  err = svn_client_checkout (repos_url,
                             target_path,
                             &revision,
                             TRUE, /* yes, we want to recurse into the URL */
                             ctx,
                             pool);
  if (err)
    handle_error (err);
  else
    printf ("deployment succeeded.\n");
}

Now that your application can deploy a new website, it needs to be able to query the deployed version to find out which versions of each file are there. This needs svn_client_status, the routine that implements the core of the svn status command. svn_client_status is a bit more complicated than svn_client_checkout, as there are more variations.

If the "descend" argument is TRUE, it recurses down a path in a working copy, filling in an apr_hash_t with keys that contain each entry's path and values that are svn_wc_status_ts. Otherwise, it just reads the entries in the top level of the directory structure.

To check the status of the entry in the working copy against that of the repository, you can pass TRUE as the "update" flag. The svn_wc_status_ts in the hash will have their repos_text_status and repos_prop_status members filled in appropriately. This will also fill in the youngest argument with the number of the most current revision in the repository.

Use the "get_all" argument to switch between fetching all entries in the working copy in the hash or only the "interesting" entries (either locally modified or out of date compared to the repository). If you don't want the svn:ignore property to control which entries are seen, pass TRUE for the 'no_ignore' argument. As with svn_client_checkout, any notification callback will be called, along with the context's notification baton, for each entry placed in the hash. The following example uses svn_client_status to print the revision numbers of everything in the deployed site.

void
print_revisions (const char *deployed_site,
                 svn_client_ctx_t *ctx,
                 apr_pool_t *pool)
{
  apr_hash_t *statuses;
  svn_error_t *err;

  err = svn_client_status (&statuses,
                           NULL,
                           deployed_site,
                           TRUE,  /* descend into subdirs */
                           TRUE,  /* get all entries */
                           FALSE, /* don't hit repos for out of dateness info */
                           FALSE, /* respect svn:ignore */
                           ctx,
                           pool);
  if (err)
    {
      handle_error (err);
      return;
    }

  /* loop over the hash entries and print them out */
  {
    apr_hash_index_t *hi;

    for (hi = apr_hash_first (pool, statuses); hi; hi = apr_hash_next (hi))
      {
        const svn_wc_status_t *status;
        const char *path;
        const void *key;
        void *value;

        apr_hash_this (hi, &key, NULL, &value);

        status = value;
        path = key;

        if (status->entry)
          printf ("%s is at revision %" SVN_REVNUM_T_FMT "\n",
                  path, status->entry->revision);
        else
          printf ("%s is not under revision control\n", path);
      }
  }
}

The final feature for your application is the ability to update a deployed site to a newer version, using svn update. As you might suspect, the libsvn_client function for this is svn_client_update. Fortunately, this is much simpler than svn_client_status. Pass it the path to the deployed site, an svn_opt_revision_t identifying the version to which to update, and a flag to allow or disallow recursion into subdirectories. As usual, it takes pool and context arguments, and any notification callback in the context will be called for each updated entry. Let's see how this can be used to update our deployed website to the latest revision in the repository.

void
update_notification_callback (void *baton,
                              const char *path,
			      svn_wc_notify_action_t action,
			      svn_node_kind_t kind,
			      const char *mime_type,
			      svn_wc_notify_state_t content_state,
			      svn_wc_notify_state_t prop_state,
			      svn_revnum_t revision)
{
  if (action == svn_wc_notify_update_completed)
    printf ("Updated %s to revision %" SVN_REVNUM_T_FMT "\n", path, revision);
}

void
update_deployed_site (const char *deployed_site,
                      svn_client_ctx_t *ctx,
                      apr_pool_t *pool)
{
  svn_opt_revision_t revision = { 0 };

  revision.kind = svn_opt_revision_head;

  ctx->notify_func = update_notification_callback;

  err = svn_client_update (deployed_site, *revision, TRUE, ctx, pool);
  if (err)
    {
      handle_error (err);
      return;
    }
}

Conclusion

There you have it, a simple set of functions that take the existing functionality of libsvn_client and apply it to your specific problem. Due to the design of Subversion, you can do this much more flexibly than by wrapping up an existing command line client. My next article will look at how to extend this to provide the ability to edit the deployed files and to commit the changes back into the repository, using the rest of libsvn_client.

Garrett Rooney is a software developer at FactSet Research Systems, where he works on real-time market data.


Return to the Linux DevCenter.




Linux Online Certification

Linux/Unix System Administration Certificate Series
Linux/Unix System Administration Certificate Series — This course series targets both beginning and intermediate Linux/Unix users who want to acquire advanced system administration skills, and to back those skills up with a Certificate from the University of Illinois Office of Continuing Education.

Enroll today!


Linux Resources
  • Linux Online
  • The Linux FAQ
  • linux.java.net
  • Linux Kernel Archives
  • Kernel Traffic
  • DistroWatch.com


  • Sponsored by: