[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] dedicated 'svn export' editor

From: Ben Collins-Sussman <sussman_at_collab.net>
Date: 2003-04-07 20:14:32 CEST

I was feeling playful on Sunday morning, so I whipped up a cute little
editor for 'svn export'. It just dumps data to disk -- no .svn
accounting at all. My timings indicate that it's about 4x faster than
our current strategy of 'svn checkout; remove all .svn dirs'.

I'm not committing it for three reasons:

  * it's not yet verifying checksums, when it easily could.

  * it's not yet creating files with the proper EOL style, when it
    easily could cache properties, notice the 'svn:eol-style' prop,
    and call svn_subst_copy_and_translate().

  * it doesn't do any keyword substitution. This is a showstopper,
    and I have no idea how to get around this. At the moment, the
    only reason our update/checkout editor is able to do keyword
    substitution is because it can pull keyword values (last-author,
    last-changed-rev, etc.) out of .svn/entries. :-(

Anyway, I'll post it here, in case somebody else wants to "pick up
this ball" and run with it some more. I'll also attach the patch to
an enhancement issue.

--------------------------------------------------------------------

Here is a new "mini" export editor which simply dumps data to disk --
no working copy bookkeeping is done at all, no .svn dirs. This is
about 4x faster than running 'svn checkout; remove all .svn dirs.'

Note that this editor should be verifying final checksums, but isn't yet.

Note that this editor should alse be caching props, and calling
svn_subst_copy_and_translate() to ensure proper EOL endings.

Note that this editor ought to do keyword translation as well, but has
no .svn/entries files (!) from which to draw values. :-(

* libsvn_client/client.h (svn_client__get_export_editor): declare.

* libsvn_client/export.c (svn_client__get_export_editor, open_root,
  add_directory, add_file, close_file, window_handler,
  apply_textdelta, struct edit_baton, struct file_baton, struct
  dir_baton, struct handler_baton): A "mini" editor that simply dumps
  data to disk.

  (svn_client_export): fetch the export editor, pass it to RA->do_checkout().

Index: subversion/libsvn_client/export.c
===================================================================
--- subversion/libsvn_client/export.c (revision 5572)
+++ subversion/libsvn_client/export.c (working copy)
@@ -191,16 +191,36 @@
 {
   if (svn_path_is_url (from))
     {
- /* export directly from the repository by doing a checkout first. */
- SVN_ERR (svn_client_checkout (from,
- to,
- revision,
- TRUE,
- ctx,
- pool));
+ const char *URL;
+ svn_revnum_t revnum;
+ void *ra_baton, *session;
+ svn_ra_plugin_t *ra_lib;
+ void *edit_baton;
+ const svn_delta_editor_t *export_editor;
 
- /* walk over the wc and remove the administrative directories. */
- SVN_ERR (svn_client__remove_admin_dirs (to, ctx, pool));
+ SVN_ERR (svn_client__get_export_editor (&export_editor, &edit_baton,
+ to, ctx, pool));
+
+ URL = svn_path_canonicalize (from, pool);
+
+ if (revision->kind == svn_opt_revision_number)
+ revnum = revision->value.number;
+ else
+ revnum = SVN_INVALID_REVNUM;
+
+ SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
+ SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, URL, pool));
+
+ SVN_ERR (svn_client__open_ra_session (&session, ra_lib, URL, NULL,
+ NULL, NULL, FALSE, TRUE,
+ ctx, pool));
+
+ /* Tell RA to do a checkout of REVISION; if we pass an invalid
+ revnum, that means RA will fetch the latest revision. */
+ SVN_ERR (ra_lib->do_checkout (session, revnum,
+ TRUE, /* recurse */
+ export_editor, edit_baton, pool));
+
     }
   else
     {
@@ -210,3 +230,256 @@
 
   return SVN_NO_ERROR;
 }
+
+
+/* ---------------------------------------------------------------------- */
+
+/*** A dedicated 'export' editor. ***/
+
+
+struct edit_baton
+{
+ const char *root_path;
+
+ svn_wc_notify_func_t notify_func;
+ void *notify_baton;
+};
+
+
+struct dir_baton
+{
+ struct edit_baton *edit_baton;
+ struct dir_baton *parent_dir_baton;
+};
+
+
+struct file_baton
+{
+ struct dir_baton *parent_dir_baton;
+
+ const char *path;
+ const char *tmppath;
+};
+
+
+struct handler_baton
+{
+ apr_file_t *source;
+ apr_file_t *dest;
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+ apr_pool_t *pool;
+ struct file_baton *fb;
+};
+
+
+
+/* Just ensure that the main export directory exists. */
+static svn_error_t *
+open_root (void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *db = apr_pcalloc (pool, sizeof(*db));
+ svn_node_kind_t kind;
+
+ db->parent_dir_baton = NULL;
+ db->edit_baton = edit_baton;
+
+ SVN_ERR (svn_io_check_path (eb->root_path, &kind, pool));
+ if (kind != svn_node_none && kind != svn_node_dir)
+ return svn_error_create (APR_ENOTDIR, NULL, eb->root_path);
+
+ SVN_ERR (svn_io_dir_make (eb->root_path, APR_OS_DEFAULT, pool));
+
+ if (db->edit_baton->notify_func)
+ (*db->edit_baton->notify_func) (db->edit_baton->notify_baton,
+ eb->root_path,
+ svn_wc_notify_update_add,
+ svn_node_dir,
+ NULL,
+ svn_wc_notify_state_unknown,
+ svn_wc_notify_state_unknown,
+ SVN_INVALID_REVNUM);
+
+ *root_baton = db;
+ return SVN_NO_ERROR;
+}
+
+
+/* Ensure the directory exists, and send feedback. */
+static svn_error_t *
+add_directory (const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ struct dir_baton *db = apr_pcalloc (pool, sizeof(*db));
+ struct dir_baton *parent = parent_baton;
+ svn_node_kind_t kind;
+ const char *full_path = svn_path_join (parent->edit_baton->root_path,
+ path, pool);
+
+ db->parent_dir_baton = parent;
+ db->edit_baton = parent->edit_baton;
+
+ SVN_ERR (svn_io_check_path (full_path, &kind, pool));
+ if (kind != svn_node_none && kind != svn_node_dir)
+ return svn_error_create (APR_ENOTDIR, NULL, full_path);
+
+ SVN_ERR (svn_io_dir_make (full_path, APR_OS_DEFAULT, pool));
+
+ if (db->edit_baton->notify_func)
+ (*db->edit_baton->notify_func) (db->edit_baton->notify_baton,
+ full_path,
+ svn_wc_notify_update_add,
+ svn_node_dir,
+ NULL,
+ svn_wc_notify_state_unknown,
+ svn_wc_notify_state_unknown,
+ SVN_INVALID_REVNUM);
+
+ *baton = db;
+ return SVN_NO_ERROR;
+}
+
+
+/* Build a file baton. */
+static svn_error_t *
+add_file (const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ struct dir_baton *parent = parent_baton;
+ struct file_baton *fb = apr_pcalloc (pool, sizeof(*fb));
+ svn_node_kind_t kind;
+ const char *full_path = svn_path_join (parent->edit_baton->root_path,
+ path, pool);
+
+ fb->parent_dir_baton = parent;
+ fb->path = full_path;
+
+ SVN_ERR (svn_io_check_path (full_path, &kind, pool));
+ if (kind != svn_node_none && kind != svn_node_file)
+ return svn_error_create (APR_EEXIST, NULL, full_path);
+
+ *baton = fb;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+window_handler (svn_txdelta_window_t *window, void *baton)
+{
+ struct handler_baton *hb = baton;
+ struct file_baton *fb = hb->fb;
+ svn_error_t *err;
+
+ err = hb->apply_handler (window, hb->apply_baton);
+ if (window != NULL && err == SVN_NO_ERROR)
+ return err;
+
+ if (err != SVN_NO_ERROR)
+ {
+ /* We failed to apply the patch; clean up the temporary file. */
+ apr_file_remove (fb->tmppath, hb->pool);
+ }
+
+ return err;
+}
+
+
+
+/* Write incoming data into the tmpfile stream */
+static svn_error_t *
+apply_textdelta (void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ apr_pool_t *handler_pool = pool;
+ struct handler_baton *hb = apr_palloc (handler_pool, sizeof (*hb));
+ apr_file_t *tmp_file = NULL;
+
+ SVN_ERR (svn_io_open_unique_file (&tmp_file, &(fb->tmppath),
+ fb->path, ".tmp", FALSE, handler_pool));
+
+ hb->pool = handler_pool;
+ hb->fb = fb;
+ hb->source = NULL;
+ hb->dest = tmp_file;
+
+ svn_txdelta_apply (svn_stream_from_aprfile (hb->source, handler_pool),
+ svn_stream_from_aprfile (hb->dest, handler_pool),
+ NULL, NULL, handler_pool,
+ &hb->apply_handler, &hb->apply_baton);
+
+ *handler_baton = hb;
+ *handler = window_handler;
+ return SVN_NO_ERROR;
+}
+
+
+/* Move the tmpfile to file, and send feedback. */
+static svn_error_t *
+close_file (void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct dir_baton *db = fb->parent_dir_baton;
+
+ if (fb->tmppath)
+ {
+ SVN_ERR (svn_io_file_rename (fb->tmppath, fb->path, pool));
+
+ if (db->edit_baton->notify_func)
+ (*db->edit_baton->notify_func) (db->edit_baton->notify_baton,
+ fb->path,
+ svn_wc_notify_update_add,
+ svn_node_file,
+ NULL,
+ svn_wc_notify_state_unknown,
+ svn_wc_notify_state_unknown,
+ SVN_INVALID_REVNUM);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_client__get_export_editor (const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *root_path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = apr_pcalloc (pool, sizeof (*eb));
+ svn_delta_editor_t *export_editor = svn_delta_default_editor (pool);
+
+ eb->root_path = apr_pstrdup (pool, root_path);
+ eb->notify_func = ctx->notify_func;
+ eb->notify_baton = ctx->notify_baton;
+
+ export_editor->open_root = open_root;
+ export_editor->add_directory = add_directory;
+ export_editor->add_file = add_file;
+ export_editor->apply_textdelta = apply_textdelta;
+ export_editor->close_file = close_file;
+
+ *edit_baton = eb;
+ *editor = export_editor;
+
+ return SVN_NO_ERROR;
+}
Index: subversion/libsvn_client/client.h
===================================================================
--- subversion/libsvn_client/client.h (revision 5572)
+++ subversion/libsvn_client/client.h (working copy)
@@ -214,11 +214,6 @@
 
 /*** Export ***/
 
-/* ### Note: someday svn_client_export() won't just do a checkout and
- call this function. Instead, we'll probably write a really
- lightweight 'export editor' which dumps data to disk without doing
- any administrative accounting or logging. */
-
 /* Recursively walk over working copy DIR, removing all administrative
    areas. Use CTX for cancellation checks. */
 svn_error_t * svn_client__remove_admin_dirs (const char *dir,
@@ -226,6 +221,17 @@
                                              apr_pool_t *pool);
 
 
+/* Set *EDITOR and *EDIT_BATON to an editor (allocated in POOL) that
+ simply dumps data to disk, with no working copy administrative dirs
+ or bookkeeping. Create the directory ROOT_PATH, and dump the
+ entire tree within. Use CTX (if non-NULL) for sending feedback. */
+svn_error_t * svn_client__get_export_editor (const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *root_path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
 /* ---------------------------------------------------------------- */
 
 /*** Export ***/

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Mon Apr 7 20:15:46 2003

This is an archived mail posted to the Subversion Dev mailing list.