Adding Version Control to Your Application with Subversion

Garrett Rooney

CollabNet

What are we going to do?

Who am I?

Who are You?

Before we start...

Some places to go for more information

The Big Picture

The Subversion Architecture

What does that mean to you?

Building the System on Unix

Like any other autoconf + make based application

$ tar zxvf subversion-1.3.2.tar.gz
$ cd subversion-1.3.2
$ ./configure --prefix=/usr/local/subversion
$ make
$ # optionally, make check to run the tests
$ make install

Dependency Fun!

Building on Windows

Tips for Building on Windows

The Language Bindings

Building The Bindings

Differences between the Bindings

Tips for Using the Bindings

An Evolving API

Foundation Libraries

Apache Portable Runtime

Memory Pools

Proper Use of Pools

const char *find_interesting_string(apr_pool_t *pool) {
    const char *interesting = NULL, *temp;
    apr_pool_t *subpool;
    int i;

    apr_pool_create (&subpool, pool);

    for (i = 0; i < 100; ++i) {
        apr_pool_clear(subpool);
        function_that_uses_temp_memory(&temp, subpool);
        if (retval_is_interesting(temp)) {
            interesting = apr_pstrdup(pool, temp);
            break;
        }
    }

    apr_pool_destroy(subpool);

    return interesting;
}

Error Handling

Good APR Error Handling

apr_status_t some_function(apr_pool_t *pool) {
    apr_status_t err;
    apr_file_t *fp;

    err = apr_file_open(&fp, "the-file-name", APR_READ,
                        APR_OS_DEFAULT, pool);
    if (APR_STATUS_IS_SUCCESS(err)) {
        /* read from the file... */
    } else if (APR_STATUS_IS_ENOENT(err)) {
        /* ok, the file wasn't found.  do something useful
         * here. */
        return err;
    } else {
        return err;
    }

    return APR_SUCCESS;
}

Subversion Errors

Good Subversion Error Handling

svn_error_t *contrived_example(apr_pool_t *pool)
{
  apr_pool_t *subpool;
  int count, idx;

  SVN_ERR(how_many(&count, pool));

  subpool = svn_pool_create(pool);

  for (idx = 0; idx < count; ++idx) {
    svn_error_t *err;

    svn_pool_clear(subpool);

    err = can_fail(apr_psprintf(subpool, "file-%d", idx), subpool);
    if (err && APR_STATUS_IS_ENOENT(err->apr_err))
      svn_error_clear(err);
    else if (err)
      return err;
    else
      break;
  }

  svn_pool_destroy(subpool);

  return SVN_NO_ERROR;
}

Strings

Path Management

Character Encoding

Streams

Example: Reading from stream

svn_error_t *cat(apr_file_t *file, apr_pool_t *pool) {
  svn_stream_t *stream = svn_stream_from_aprfile2(file, FALSE, pool);
  apr_pool_t *subpool = svn_pool_create(pool);
  svn_boolean_t eof = FALSE;

  while (! eof) {
    svn_stringbuf_t *line;
    svn_pool_clear(subpool);
    SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, subpool));
    printf("%s\n", line->data);
  }

  svn_pool_destroy(subpool);

  return SVN_NO_ERROR;
}

Things to Note

Other Streams

Three Kinds of Deltas

Binary Deltas

Example: Calculating Binary Diffs

source_text = "abcde"
target_text = "abXde"

stream = Svn::Delta::TextDeltaStream.new(source, target)

apply_source = StringIO.new(source_text)
apply_result = StringIO.new("")

handler, digest = Svn::Delta.apply(apply_source, apply_result)
handler.send(stream)

apply_result.rewind
print apply_result.read

So What Did It Do?

Textual Diffs

Example: diff -u

Digression: What's a Baton?

Tree Deltas

Editors

Example Editor Operations

Using An Editor

Wrapping Editors

Example: An Editor Drive

===================================================================
--- subversion/libsvn_client/diff.c     (revision 20226)
+++ subversion/libsvn_client/diff.c     (working copy)
@@ -40,6 +40,8 @@
 #include "client.h"
 #include <assert.h>

+#include "../libsvn_delta/debug_editor.h"
+
 #include "svn_private_config.h"

 /*
@@ -2065,6 +2067,10 @@
            ctx->cancel_func, ctx->cancel_baton,
            &diff_editor, &diff_edit_baton, pool));

+  SVN_ERR(svn_delta__get_debug_editor(&diff_editor, &diff_edit_baton,
+                                      diff_editor, diff_edit_baton,
+                                      pool));
+
   /* We want to switch our txn into URL2 */
   SVN_ERR(svn_ra_do_diff2
           (drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,

Example: An Editor Drive

$ svn diff -r 13:14 http://svn.collab.net/repos/svn/trunk > /dev/null
set_target_revision : 15
open_root : 14
 change_dir_prop : svn:wc:ra_dav:version-url
 open_directory : 'www':14
  change_dir_prop : svn:wc:ra_dav:version-url
  open_file : 'www/index.html':14
   change_file_prop : svn:wc:ra_dav:version-url
   apply_textdelta : a01ce64bbb32748a3a93ccc97404f408
   change_file_prop : svn:entry:committed-rev
   change_file_prop : svn:entry:committed-date
   change_file_prop : svn:entry:last-author
  close_file : d1ddd0260510e9f014c2716d06835362
  change_dir_prop : svn:entry:committed-rev
  change_dir_prop : svn:entry:committed-date
  change_dir_prop : svn:entry:last-author
 close_directory
 change_dir_prop : svn:entry:committed-rev
 change_dir_prop : svn:entry:committed-date
 change_dir_prop : svn:entry:last-author
close_directory
close_edit

The Subversion Filesystem

Roots

Nodes

A Filesystem

A Filesystem

Revisions

An Array Of Filesystems

An Array Of Filesystems

The Cool Part

A Filesystem DAG

 ---------  ---------       ---------          ---------
 | rev 0 |<-| rev 1 |<------| rev 2 |<---------| rev 3 |
 ---------  ---------       ---------          ---------
              | ---------    | ---------           | ---------
              \-| trunk |<---\-| trunk |<----------\-| trunk |
              | ---------    | ---------      \    | ---------
              |              |   | ---------  |    |
              |              |   \-| foo.c |  |    |
              |              |     ---------  |    |
              | ---------    |  --------      |    | ---------
              \-| tags  |<---\--| tags |<-----|----\-| tags  |
                ---------       --------      |      ---------
                                              |        | ---------------
                                               \-------\-| version_0.1 |
                                                         ---------------

Reading Contents

Example: Recursive ls

svn_error_t *ls(svn_fs_root_t *root, const char *path,
                apr_pool_t *pool) {
  svn_node_kind_t kind;

  SVN_ERR(svn_fs_check_path(&kind, root, path, pool));

  if (kind == svn_node_file) {
    printf("%s\n", path);
  } else if (kind == svn_node_dir) {
    apr_hash_index_t *hi;
    apr_hash_t *entries;
    apr_pool_t *subpool;

    printf("%s/\n", path);

    SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));

    subpool = svn_pool_create(pool);

    for (hi = apr_hash_first(entries, pool); hi;
         hi = apr_hash_next(hi)) {
      const void *key;
      void *val;

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

      SVN_ERR(ls(root, key, subpool));
    }

    svn_pool_destroy(subpool);
  }

  return SVN_NO_ERROR;
}

Things To Note

Traversing History

Example: History

Making Changes

Example: Initial Repos Layout

my $repos = SVN::Repos::create("/path/to/fs", undef, undef, undef,
                               undef);

my $txn = $repos->fs->begin_txn($fs->youngest_rev);

$txn->root->make_dir("trunk");
$txn->root->make_dir("tags");
$txn->root->make_dir("branches");

$txn->root->make_file('README');

my $stream = $txn->root->apply_text('README', undef);
print $stream <<EOF;
This repository now has trunk, tags, and branches directories
EOF

close $stream;

$txn->commit;

Things to Note

Digression: Pools in Perl

Example: Pools in Perl

use SVN::Core;

# create a root pool and set it as default pool for later use
my $pool = SVN::Pool->new_default;

sub something {
  # create a subpool of the current default pool
  my $subpool = SVN::Pool->new_default_sub;

  for (1..10) {
    $subpool->clear;

    # some svn operations...
  }

  # $subpool gets destroyed and the previous default pool
  # is restored when $subpool's lexical scope ends
}

Repository Layer

Hook Scripts

Commit Hooks

Lock Hooks

Revprop Change Hooks

Example: CIA post-commit Pinger

Hook Script Tips

Retrieving Deltas

Example: svnlook.py

Dump/Load

What's a Dumpfile?

Example: A Dumpfile

Example: Author Renaming

RA Layers

Using the RA Layer

Reporters and Editors

Example: Multiple Copies/Moves/Deletes

Example: Editing a file

Example: svnedit.c

Example: ls over RA

svn_error_t list(svn_ra_session_t *session, const char *dir,
                        svn_revnum_t rev, apr_pool_t *pool) {
  apr_pool_t *subpool = svn_pool_create(pool);
  apr_hash_index_t *hi;
  apr_hash_t *dents;

  SVN_ERR(svn_ra_get_dir2(session, dir, rev, SVN_DIRENT_ALL, &dents,
                          pool));
  for (hi = apr_hash_first(dents, pool); hi; hi = apr_hash_next(hi)) {
    const char *name, *fpath;
    svn_dirent_t *dent;

    svn_pool_clear(subpool);

    apr_hash_this(hi, &name, APR_HASH_KEY_STRING, &dent);

    fpath = apr_psprintf(subpool, "%s/%s", dir, name);

    printf("%s%s\n", fpath, dent->kind == svn_node_dir ? "/" : "");

    if (dent->kind == svn_node_dir)
      SVN_ERR(list(session, fpath, rev, subpool));
  }

  return SVN_NO_ERROR;
}

Digression: Peg Revisions

Getting Logs

Example: Getting Log Data

svn_error_t *reciever(void *baton, apr_hash_t *changed_paths,
                      svn_revnum_t revision, const char *author,
                      const char *date, const char *message,
                      apr_pool_t *pool) {
  printf("%s committed %ld on %s\n", author, revnum, date);
  return SVN_NO_ERROR;
}

svn_error_t *print_log(svn_ra_session_t *session, const char *path,
                       svn_revnum_t start, svn_revnum_t end,
                       apr_pool_t *pool) {
  apr_array_header_t *paths = apr_array_make(pool, 10, sizeof(char *));

  APR_ARRAY_PUSH(paths, const char *) = path;

  return svn_ra_get_log(session, paths, start, end, 0, FALSE, TRUE,
                        receiver, NULL, pool);
}

RA Editors

Example: Viewing a diff

svn_error_t *do_diff(svn_ra_session_t *session,
                     svn_revnum_t start, svn_revnum_t end,
                     apr_pool_t *pool) {
  svn_ra_reporter2_t *reporter;
  svn_delta_editor_t *editor = svn_delta_default_editor(pool);
  void *edit_baton, *report_baton;
  const char *url;

  svn_delta__get_debug_editor(&editor, &edit_baton, editor, NULL,
                              pool);

  SVN_ERR(svn_ra_get_repos_root(session, &url, pool));

  SVN_ERR(svn_ra_do_diff2(session, &reporter, &report_baton, end, "",
                          TRUE, FALSE, TRUE, url, editor, edit_baton,
                          pool));

  SVN_ERR(reporter->set_path(report_baton, "", start, FALSE, NULL,
                             pool));
  SVN_ERR(reporter->finish_report(report_baton, pool));

  return SVN_NO_ERROR;
}

The Working Copy

Digression: entries formats

Access Batons

Entries

Getting useful information

svn_error_t *print_info(const char *path, apr_pool_t *pool) {
  svn_wc_adm_access_t *adm_access;
  svn_wc_entry_t *entry;

  SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, path, FALSE, 0, NULL,
                           NULL, pool));

  SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, pool));

  printf("name: %s\n", entry->name);
  printf("revision: %ld\n", entry->revision);
  printf("url: %s\n", entry->url);
  printf("author: %s\n", entry->cmt_author);
  printf("commit date: %s\n", entry->cmt_date);
  printf("last changed rev: %ld\n", entry->cmt_rev);

  return SVN_NO_ERROR;
}

Other Working Copy Ops

The Client Library

Client Contexts

Example: minimal_client.c

Example: Viewing Repos Contents

require 'svn/client'

client = Svn::Client::Context.new

def list_dir(url)
  client.list(url, "HEAD") do |path, dirent, lock, abs_path|
    if dirent.directory?
      print "#{path}/\n"
      list_dir("#{url}/#{path}", "HEAD")
    else
      print "#{path}\n"
    end
  end
end

list_dir("http://svn.collab.net/repos/svn/")

Apache Modules

Example: mod_dontdothat.c

Example: mod_authz_svn.c

Good Examples

C API

Java API

Python API

Perl API

Ruby API

Any Questions?

Any Questions?