Using the Subversion Client API, Part 2
Pages: 1, 2
So And So Did What?
Now that we've moved into the realm of editing files in the working
copy, we'll have to account for how that will interact with
svn_client_update. If you have uncommitted changes in
the tree and you update, conflicts can occur. When this happens,
Subversion will leave three extra versions of the file in your working
copy: the base version from which you started, your modified version,
and the new version from the repository. The file you had edited will
also have conflict markers inserted into it showing where the conflict
occurred. Once you have resolved the conflict manually--by removing
the conflict markers and leaving the file in its final state--call
svn_client_resolve to tell Subversion that the conflict
has been resolved. This will remove the other three versions of the
file and Subversion will then allow you to commit your changes.
svn_client_resolve is quite simple, so let's look at an
example.
void
resolve_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 ("resolving %s\n", path);
}
void
resolve_conflict (const char *path,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
ctx->notify_func = resolve_notification_callback;
svn_error_t *err = svn_client_resolve (path, FALSE, ctx, pool);
if (err)
handle_error (err);
}
And You Thought You Could Never Commit To Anything
Now that you can edit files in your working copy, view diffs, and
revert unwanted changes, you'll need to commit the changes to the
repository for safekeeping. To do this, call
svn_client_commit. As you'd expect,
svn_client_commit uses some callback functions and batons
from the client context. In addition to the standard notification
callback, it uses a log message callback which fetches a log message
for the commit from the client application. In the svn
command line client, this function starts your $EDITOR
and returns what you write there.
The next example assumes you have the log entry before you call
svn_client_commit. Pass in your log entry as
log_msg_baton and have the callback just return it. To
make things fancier, use the tmp_file or
commit_items parameters. tmp_file holds the
name of a file that contains the log message. This file will be
deleted when the commit completes but will remain if the commit fails.
The user will not lose the log message. commit_items
parameter holds information about each item that is being committed.
It's useful for composing a default form for your log message.
svn_error_t *
commit_log_callback (const char **log_msg,
const char **tmp_file,
apr_array_header_t *commit_items,
void *baton,
apr_pool_t *pool)
{
*tmp_file = NULL;
*log_msg = baton;
return SVN_NO_ERROR;
}
With that callback you can now commit a change to the repository.
svn_client_commit introduces a few new concepts.
Besides returning a svn_error_t to indicate an error, it
also takes a svn_client_commit_info_t ** which it will
fill in with the results of the commit. Since this function can take
multiple different targets, we pass in an apr_array_header_t
* that holds an array of const char * paths to
items to commit. The rest of the arguments are typical of
libsvn_client: a boolean that controls recursing into
directories, a client context, and a pool for memory allocation.
Here's an example of how this all works.
void
commit_item (const char *item,
const char *log_entry,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
apr_array_header_t *targets = apr_array_make (pool, 1, sizeof (char *));
svn_client_commit_info_t *commit_info;
svn_error_t *err;
/* yeah, i think this looks kind of nasty too... */
(*((const char **) apr_array_push (targets))) = item;
ctx->log_msg_func = commit_log_callback;
/* this cast is just because log_entry is const and the baton isn't. */
ctx->log_msg_baton = (void *) log_entry;
err = svn_client_commit (&commit_info,
targets,
TRUE,
ctx,
pool);
if (err)
handle_error (err);
printf ("revision %" SVN_REVNUM_T_FMT " committed at %s by %s\n",
commit_info->revision,
commit_info->date,
commit_info->author);
}
Hey, New Stuff!
This gives your client the ability to make changes to an existing
file and commit them to the repository. Eventually they'll want to
add new items, so you'll need to use svn_client_add.
This is another stereotypical libsvn_client function. It
takes a path to an item (file or directory) in the working copy, a
flag to indicate if it should recurse, a client context, and a pool.
When it succeeds, the item is scheduled for addition during the next
commit. Showing an example for this function is pointless, so just
look at the one for svn_client_revert and replace
revert with add. It's really that
simple.
svn_client_add does have a quirk, though. Subversion
tries to guess at the MIME type of the file as you add it. While it
does a pretty good job of figuring out when something is a text file
and when it isn't, it doesn't yet try to determine anything else.
This means that if you add a PNG image, the svn:mime-type
property will be set to application/octet-stream, which
is all well and good for Subversion, but probably isn't what you need.
With this MIME type, mod_dav_svn won't know enough to
serve the file, so you won't be able to easily view it in a web
browser. To make that work, you need to use
svn_client_propset to set the svn:mime-type
to something more appropriate (image/png in this case).
Here's some example code that shows how to do that:
void
set_mime_type_to_png (const char *target,
apr_pool_t *pool)
{
static const svn_string_t propval = { "image/png", 10 };
svn_error_t *err = svn_client_propset ("svn:mime-type",
&propval,
target,
FALSE,
pool);
if (err)
handle_error (err);
}
Just Get Rid Of It!
Eventually your user is going to want to remove a file from the
repository, so you'll need to use svn_client_delete.
Again, this function can work either on a local working copy or a
(possibly remote) repository. Its signature resembles that of
svn_client_commit. If you're using it on a repository
directly, pass it a svn_client_commit_info_t **, to get
back information about the commit it performs and the url of the item
in the repository. The log message callback and baton in the client
context will be used to get the log message for the commit, and the
context's authentication baton will be used to authenticate.
If you're deleting an item from a working copy, pass the path to
the item on disk. You can also pass an svn_wc_adm_access_t
*, in which case Subversion will use its existing directory
lock, or NULL to open a new lock. force is
a flag to indicate that Subversion should delete the item even if it
is locally modified or unversioned, which normally results in an
error. Let's take a look at how you would use
svn_client_delete to schedule a file for deletion.
void
delete_item (const char *target,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_error_t *err = svn_client_delete (NULL, /* this isn't a commit */
target,
NULL, /* let svn open a new lock */
FALSE, /* don't force it */
ctx,
pool);
if (err)
handle_error (err);
}
No, Put It Over There!
The remaining, relevant libsvn_client functions are
svn_client_copy and svn_client_move. Since
the ability to rename a file while keeping its revision history intact
is one of Subversion's selling points over CVS, a good client needs
this feature. Both functions have the same signature. They take as
arguments a svn_client_commit_info_t ** (used to get
information about the commit that is performed if you use them on the
repository directly, just like in svn_client_commit); a
src_path and src_revision, which identify
the path (or url) for the source file and its revision; a
dst_path, which indicates the destination path or url; an
svn_wc_adm_access_t * (which can be NULL, like in
svn_client_delete); and, finally, a client context and a
pool. Let's see one last example, which copies a file within a
working copy. This could just as easily move a file using
svn_client_move, since they are the same from the point
of view of the calling code.
void
copy_file (const char *source,
const char *dest,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_opt_revision_t source_rev = { 0 };
svn_error_t *err;
err = svn_client_copy (NULL,
source,
&source_rev,
dest,
NULL,
ctx,
pool);
if (err)
handle_error (err);
}
Conclusion
You're probably starting to notice that all the
libsvn_client functions feel pretty similar. That's
intentional. They reuse the same patterns. Once you've mastered one
function that commits a change to the repository, you'll be able to
use the rest with little trouble.
Even though we've gone over most of the functions in the
libsvn_client API, your client isn't perfectly complete.
There are still a number of interesting functions left, and your users
will eventually clamor for them. The source code to the existing
clients (especially the command line client distributed with the
Subversion source tree) is your the best guide, along with the
svn_client.h header file. Once you figure out what else
you'd like your client to do, dig in and get to hacking.
Garrett Rooney is a software developer at FactSet Research Systems, where he works on real-time market data.
Return to ONLamp.com.