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

Re: [PATCH] Issue #443: post-commit hook script (error) output lost

From: Madan U Sreenivasan <madan_at_collab.net>
Date: 2005-05-17 08:16:27 CEST

On Mon, 2005-05-16 at 21:27, kfogel@collab.net wrote:
> "Madan US" <madan@collab.net> writes:
> > Version 2 to accomodate
> > new versions of ABI functions/data structures
> > change of commit flow of mkdir, rm, import etc., to accomodate post-commit
> > hook's stderr
> >
> > does NOT contain
> > xml related changes suggested on the ml
>
> Also does not contain a log message... :-)
that was intentional... didnt want to write a 200 line log message until
the patch is acceptable.... this was more like a review
submission...since the patch is loooong.... :-)
>
> -Karl
>
> > Regards,
> > Madan.
> >
> >
> > Index: subversion/libsvn_ra/wrapper_template.h
> > ===================================================================
> > --- subversion/libsvn_ra/wrapper_template.h (revision 14704)
> > +++ subversion/libsvn_ra/wrapper_template.h (working copy)
> > @@ -99,7 +99,7 @@
> > **editor,
> > void **edit_baton,
> > const char *log_msg,
> > - svn_commit_callback_t callback,
> > + svn_commit_callback2_t callback,
> > void *callback_baton,
> > apr_pool_t *pool)
> > {
> > Index: subversion/libsvn_ra/ra_loader.c
> > ===================================================================
> > --- subversion/libsvn_ra/ra_loader.c (revision 14704)
> > +++ subversion/libsvn_ra/ra_loader.c (working copy)
> > @@ -337,6 +337,23 @@
> > lock_tokens, keep_locks, pool);
> > }
> >
> > +/** since v1.3
> > + */
> > +svn_error_t *svn_ra_get_commit_editor2 (svn_ra_session_t *session,
> > + const svn_delta_editor_t **editor,
> > + void **edit_baton,
> > + const char *log_msg,
> > + svn_commit_callback2_t callback,
> > + void *callback_baton,
> > + apr_hash_t *lock_tokens,
> > + svn_boolean_t keep_locks,
> > + apr_pool_t *pool)
> > +{
> > + return session->vtable->get_commit_editor (session, editor, edit_baton,
> > + log_msg, callback, callback_baton,
> > + lock_tokens, keep_locks, pool);
> > +}
> > +
> > svn_error_t *svn_ra_get_file (svn_ra_session_t *session,
> > const char *path,
> > svn_revnum_t revision,
> > Index: subversion/libsvn_ra/ra_loader.h
> > ===================================================================
> > --- subversion/libsvn_ra/ra_loader.h (revision 14704)
> > +++ subversion/libsvn_ra/ra_loader.h (working copy)
> > @@ -80,7 +80,7 @@
> > const svn_delta_editor_t **editor,
> > void **edit_baton,
> > const char *log_msg,
> > - svn_commit_callback_t callback,
> > + svn_commit_callback2_t callback,
> > void *callback_baton,
> > apr_hash_t *lock_tokens,
> > svn_boolean_t keep_locks,
> > Index: subversion/include/svn_repos.h
> > ===================================================================
> > --- subversion/include/svn_repos.h (revision 14704)
> > +++ subversion/include/svn_repos.h (working copy)
> > @@ -607,6 +607,20 @@
> > svn_commit_callback_t callback,
> > void *callback_baton,
> > apr_pool_t *pool);
> > +/** Since 1.3
> > + * uses the svn_commit_callback2_t callback
> > + */
> > +svn_error_t *svn_repos_get_commit_editor3 (const svn_delta_editor_t **editor,
> > + void **edit_baton,
> > + svn_repos_t *repos,
> > + svn_fs_txn_t *txn,
> > + const char *repos_url,
> > + const char *base_path,
> > + const char *user,
> > + const char *log_msg,
> > + svn_commit_callback2_t callback,
> > + void *callback_baton,
> > + apr_pool_t *pool);
> >
> >
> > /**
> > Index: subversion/include/svn_types.h
> > ===================================================================
> > --- subversion/include/svn_types.h (revision 14704)
> > +++ subversion/include/svn_types.h (working copy)
> > @@ -351,6 +351,17 @@
> > const char *author,
> > void *baton);
> >
> > +/** @since New in 1.3.
> > + *
> > + * v2. to accomodate post-commit error
> > + */
> > +typedef svn_error_t * (*svn_commit_callback2_t) (
> > + svn_revnum_t new_revision,
> > + const char *date,
> > + const char *author,
> > + const char *post_commit_err,
> > + void *baton);
> > +
> >
> > /** The maximum amount we (ideally) hold in memory at a time when
> > * processing a stream of data.
> > Index: subversion/include/svn_client.h
> > ===================================================================
> > --- subversion/include/svn_client.h (revision 14704)
> > +++ subversion/include/svn_client.h (working copy)
> > @@ -277,7 +277,27 @@
> >
> > } svn_client_commit_info_t;
> >
> > +/** @since New in 1.3.
> > + * Information about commits passed back to client from this module.
> > + * v2. to accomodate post-commit hook's stderr
> > + */
> > +typedef struct svn_client_commit_info2_t
> > +{
> > + /** just-committed revision. */
> > + svn_revnum_t revision;
> >
> > + /** server-side date of the commit. */
> > + const char *date;
> > +
> > + /** author of the commit. */
> > + const char *author;
> > +
> > + /** post-commit hook's stderr. */
> > + const char *post_commit_err;
> > +
> > +} svn_client_commit_info2_t;
> > +
> > +
> > /** @{
> > * State flags for use with the @c svn_client_commit_item_t structure
> > *
> > @@ -678,6 +698,14 @@
> > svn_client_ctx_t *ctx,
> > apr_pool_t *pool);
> >
> > +/** since v1.3
> > + */
> > +svn_error_t *
> > +svn_client_mkdir2 (svn_client_commit_info2_t **commit_info,
> > + const apr_array_header_t *paths,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool);
> > +
> >
> > /** Delete items from a repository or working copy.
> > *
> > @@ -714,6 +742,14 @@
> > svn_client_ctx_t *ctx,
> > apr_pool_t *pool);
> >
> > +/** since v1.3
> > + */
> > +svn_error_t *
> > +svn_client_delete2 (svn_client_commit_info2_t **commit_info,
> > + const apr_array_header_t *paths,
> > + svn_boolean_t force,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool);
> >
> > /** Import file or directory @a path into repository directory @a url at
> > * head, authenticating with the authentication baton cached in @a ctx,
> > @@ -751,6 +787,15 @@
> > * option. However, doing so is a bit involved, and we don't need it
> > * right now.
> > */
> > +/** since v1.3
> > + */
> > +svn_error_t *svn_client_import2 (svn_client_commit_info2_t **commit_info,
> > + const char *path,
> > + const char *url,
> > + svn_boolean_t nonrecursive,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool);
> > +
> > svn_error_t *svn_client_import (svn_client_commit_info_t **commit_info,
> > const char *path,
> > const char *url,
> > @@ -759,6 +804,17 @@
> > apr_pool_t *pool);
> >
> >
> > +/** @since New in 1.3.
> > + * using svn_client_commit_info2_t
> > + */
> > +svn_error_t *
> > +svn_client_commit3 (svn_client_commit_info2_t **commit_info,
> > + const apr_array_header_t *targets,
> > + svn_boolean_t recurse,
> > + svn_boolean_t keep_locks,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool);
> > +
> > /** @since New in 1.2.
> > *
> > * Commit files or directories into repository, authenticating with
> > @@ -1347,7 +1403,17 @@
> > svn_client_ctx_t *ctx,
> > apr_pool_t *pool);
> >
> > +/** since v1.3
> > + */
> > +svn_error_t *
> > +svn_client_copy2 (svn_client_commit_info2_t **commit_info,
> > + const char *src_path,
> > + const svn_opt_revision_t *src_revision,
> > + const char *dst_path,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool);
> >
> > +
> > /**
> > * @since New in 1.2.
> > *
> > @@ -1393,7 +1459,19 @@
> > *
> > * ### Is this really true? What about svn_wc_notify_commit_replaced()? ###
> > */
> > +
> > +/** since v1.3
> > + * using svn_client_commit_info2_t
> > + */
> > svn_error_t *
> > +svn_client_move3 (svn_client_commit_info2_t **commit_info,
> > + const char *src_path,
> > + const char *dst_path,
> > + svn_boolean_t force,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool);
> > +
> > +svn_error_t *
> > svn_client_move2 (svn_client_commit_info_t **commit_info,
> > const char *src_path,
> > const char *dst_path,
> > Index: subversion/include/svn_ra.h
> > ===================================================================
> > --- subversion/include/svn_ra.h (revision 14704)
> > +++ subversion/include/svn_ra.h (working copy)
> > @@ -506,6 +506,16 @@
> > svn_boolean_t keep_locks,
> > apr_pool_t *pool);
> >
> > +svn_error_t *svn_ra_get_commit_editor2 (svn_ra_session_t *session,
> > + const svn_delta_editor_t **editor,
> > + void **edit_baton,
> > + const char *log_msg,
> > + svn_commit_callback2_t callback,
> > + void *callback_baton,
> > + apr_hash_t *lock_tokens,
> > + svn_boolean_t keep_locks,
> > + apr_pool_t *pool);
> > +
> > /**
> > * @since New in 1.2.
> > *
> > @@ -1124,7 +1134,7 @@
> > const svn_delta_editor_t **editor,
> > void **edit_baton,
> > const char *log_msg,
> > - svn_commit_callback_t callback,
> > + svn_commit_callback2_t callback,
> > void *callback_baton,
> > apr_pool_t *pool);
> >
> > Index: subversion/libsvn_ra_local/ra_plugin.c
> > ===================================================================
> > --- subversion/libsvn_ra_local/ra_plugin.c (revision 14704)
> > +++ subversion/libsvn_ra_local/ra_plugin.c (working copy)
> > @@ -408,7 +408,7 @@
> > const char *fs_path; /* fs-path part of split session URL */
> > apr_hash_t *lock_tokens; /* tokens to unlock, if any */
> > apr_pool_t *pool; /* pool for scratch work */
> > - svn_commit_callback_t callback; /* the original callback */
> > + svn_commit_callback2_t callback; /* the original callback */
> > void *callback_baton; /* the original callback's baton */
> > };
> >
> > @@ -420,6 +420,7 @@
> > deltify_etc (svn_revnum_t new_revision,
> > const char *date,
> > const char *author,
> > + const char *post_commit_err,
> > void *baton)
> > {
> > struct deltify_etc_baton *db = baton;
> > @@ -430,7 +431,8 @@
> > /* Invoke the original callback first, in case someone's waiting to
> > know the revision number so they can go off and annotate an
> > issue or something. */
> > - err1 = (*db->callback) (new_revision, date, author, db->callback_baton);
> > + err1 = (*db->callback) (new_revision, date, author,
> > + post_commit_err, db->callback_baton);
> >
> > /* Maybe unlock the paths. */
> > if (db->lock_tokens)
> > @@ -478,7 +480,7 @@
> > const svn_delta_editor_t **editor,
> > void **edit_baton,
> > const char *log_msg,
> > - svn_commit_callback_t callback,
> > + svn_commit_callback2_t callback,
> > void *callback_baton,
> > apr_hash_t *lock_tokens,
> > svn_boolean_t keep_locks,
> > @@ -525,7 +527,7 @@
> > }
> >
> > /* Get the repos commit-editor */
> > - SVN_ERR (svn_repos_get_commit_editor2
> > + SVN_ERR (svn_repos_get_commit_editor3
> > (editor, edit_baton, sess_baton->repos, NULL,
> > svn_path_uri_decode (sess_baton->repos_url, pool),
> > sess_baton->fs_path,
> > Index: subversion/libsvn_client/delete.c
> > ===================================================================
> > --- subversion/libsvn_client/delete.c (revision 14704)
> > +++ subversion/libsvn_client/delete.c (working copy)
> > @@ -109,6 +109,106 @@
> >
> >
> > static svn_error_t *
> > +delete_urls2 (svn_client_commit_info2_t **commit_info,
> > + const apr_array_header_t *paths,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + svn_ra_session_t *ra_session;
> > + const svn_delta_editor_t *editor;
> > + void *edit_baton;
> > + void *commit_baton;
> > + const char *log_msg;
> > + svn_node_kind_t kind;
> > + apr_array_header_t *targets;
> > + svn_error_t *err;
> > + const char *common;
> > + int i;
> > + apr_pool_t *subpool = svn_pool_create (pool);
> > +
> > + /* Condense our list of deletion targets. */
> > + SVN_ERR (svn_path_condense_targets (&common, &targets, paths, TRUE, pool));
> > + if (! targets->nelts)
> > + {
> > + const char *bname;
> > + svn_path_split (common, &common, &bname, pool);
> > + APR_ARRAY_PUSH (targets, const char *) = bname;
> > + }
> > +
> > + /* Create new commit items and add them to the array. */
> > + if (ctx->log_msg_func)
> > + {
> > + svn_client_commit_item_t *item;
> > + const char *tmp_file;
> > + apr_array_header_t *commit_items
> > + = apr_array_make (pool, targets->nelts, sizeof (item));
> > +
> > + for (i = 0; i < targets->nelts; i++)
> > + {
> > + const char *path = APR_ARRAY_IDX (targets, i, const char *);
> > + item = apr_pcalloc (pool, sizeof (*item));
> > + item->url = svn_path_join (common, path, pool);
> > + item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
> > + APR_ARRAY_PUSH (commit_items, svn_client_commit_item_t *) = item;
> > + }
> > + SVN_ERR ((*ctx->log_msg_func) (&log_msg, &tmp_file, commit_items,
> > + ctx->log_msg_baton, pool));
> > + if (! log_msg)
> > + return SVN_NO_ERROR;
> > + }
> > + else
> > + log_msg = "";
> > +
> > + /* Open an RA session for the URL. Note that we don't have a local
> > + directory, nor a place to put temp files. */
> > + SVN_ERR (svn_client__open_ra_session (&ra_session, common, NULL,
> > + NULL, NULL, FALSE, TRUE,
> > + ctx, pool));
> > +
> > + /* Verify that each thing to be deleted actually exists (to prevent
> > + the creation of a revision that has no changes, since the
> > + filesystem allows for no-op deletes). */
> > + for (i = 0; i < targets->nelts; i++)
> > + {
> > + const char *path = APR_ARRAY_IDX (targets, i, const char *);
> > + svn_pool_clear (subpool);
> > + path = svn_path_uri_decode (path, pool);
> > + APR_ARRAY_IDX (targets, i, const char *) = path;
> > + SVN_ERR (svn_ra_check_path (ra_session, path, SVN_INVALID_REVNUM,
> > + &kind, subpool));
> > + if (kind == svn_node_none)
> > + return svn_error_createf (SVN_ERR_FS_NOT_FOUND, NULL,
> > + "URL '%s' does not exist",
> > + svn_path_local_style (path, pool));
> > + }
> > + svn_pool_destroy (subpool);
> > +
> > + /* Fetch RA commit editor */
> > + SVN_ERR (svn_client__commit_get_baton2 (&commit_baton, commit_info, pool));
> > + SVN_ERR (svn_ra_get_commit_editor2 (ra_session, &editor, &edit_baton,
> > + log_msg, svn_client__commit_callback2,
> > + commit_baton,
> > + NULL, TRUE, /* No lock tokens */
> > + pool));
> > +
> > + /* Call the path-based editor driver. */
> > + err = svn_delta_path_driver (editor, edit_baton, SVN_INVALID_REVNUM,
> > + targets, path_driver_cb_func,
> > + (void *)editor, pool);
> > + if (err)
> > + {
> > + /* At least try to abort the edit (and fs txn) before throwing err. */
> > + svn_error_clear (editor->abort_edit (edit_baton, pool));
> > + return err;
> > + }
> > +
> > + /* Close the edit. */
> > + SVN_ERR (editor->close_edit (edit_baton, pool));
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +static svn_error_t *
> > delete_urls (svn_client_commit_info_t **commit_info,
> > const apr_array_header_t *paths,
> > svn_client_ctx_t *ctx,
> > @@ -233,6 +333,55 @@
> >
> >
> > svn_error_t *
> > +svn_client_delete2 (svn_client_commit_info2_t **commit_info,
> > + const apr_array_header_t *paths,
> > + svn_boolean_t force,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + if (! paths->nelts)
> > + return SVN_NO_ERROR;
> > +
> > + if (svn_path_is_url (APR_ARRAY_IDX (paths, 0, const char *)))
> > + {
> > + SVN_ERR (delete_urls2 (commit_info, paths, ctx, pool));
> > + }
> > + else
> > + {
> > + apr_pool_t *subpool = svn_pool_create (pool);
> > + int i;
> > +
> > + for (i = 0; i < paths->nelts; i++)
> > + {
> > + svn_wc_adm_access_t *adm_access;
> > + const char *path = APR_ARRAY_IDX (paths, i, const char *);
> > + const char *parent_path;
> > +
> > + svn_pool_clear (subpool);
> > + parent_path = svn_path_dirname (path, subpool);
> > +
> > + /* See if the user wants us to stop. */
> > + if (ctx->cancel_func)
> > + SVN_ERR (ctx->cancel_func (ctx->cancel_baton));
> > +
> > + /* Let the working copy library handle the PATH. */
> > + SVN_ERR (svn_wc_adm_open3 (&adm_access, NULL, parent_path,
> > + TRUE, 0, ctx->cancel_func,
> > + ctx->cancel_baton, subpool));
> > + SVN_ERR (svn_client__wc_delete (path, adm_access, force,
> > + FALSE,
> > + ctx->notify_func2,
> > + ctx->notify_baton2,
> > + ctx, subpool));
> > + SVN_ERR (svn_wc_adm_close (adm_access));
> > + }
> > + svn_pool_destroy (subpool);
> > + }
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +svn_error_t *
> > svn_client_delete (svn_client_commit_info_t **commit_info,
> > const apr_array_header_t *paths,
> > svn_boolean_t force,
> > Index: subversion/libsvn_client/client.h
> > ===================================================================
> > --- subversion/libsvn_client/client.h (revision 14704)
> > +++ subversion/libsvn_client/client.h (working copy)
> > @@ -248,6 +248,10 @@
> > svn_client_commit_info_t **info,
> > apr_pool_t *pool);
> >
> > +svn_error_t *svn_client__commit_get_baton2 (void **baton,
> > + svn_client_commit_info2_t **info,
> > + apr_pool_t *pool);
> > +
> > /* The commit_callback function for storing svn_client_commit_info_t
> > pointed by commit_baton. If the commit_info supplied by get_baton
> > points to NULL after close_edit, it means the commit is a no-op.
> > @@ -257,6 +261,15 @@
> > const char *author,
> > void *baton);
> >
> > +/** @since New in 1.3.
> > + * v2. To accomodate post-commit hook's stderr
> > + */
> > +svn_error_t *svn_client__commit_callback2 (svn_revnum_t revision,
> > + const char *date,
> > + const char *author,
> > + const char *post_commit_err,
> > + void *baton);
> > +
> > /* ---------------------------------------------------------------- */
> >
> > /*** Status ***/
> > Index: subversion/libsvn_client/copy.c
> > ===================================================================
> > --- subversion/libsvn_client/copy.c (revision 14704)
> > +++ subversion/libsvn_client/copy.c (working copy)
> > @@ -269,7 +269,227 @@
> > return SVN_NO_ERROR;
> > }
> >
> > +/** since v1.3
> > + */
> > +static svn_error_t *
> > +repos_to_repos_copy2 (svn_client_commit_info2_t **commit_info,
> > + const char *src_url,
> > + const svn_opt_revision_t *src_revision,
> > + const char *dst_url,
> > + svn_client_ctx_t *ctx,
> > + svn_boolean_t is_move,
> > + apr_pool_t *pool)
> > +{
> > + apr_array_header_t *paths = apr_array_make (pool, 2, sizeof (const char *));
> > + const char *top_url, *src_rel, *dst_rel, *message;
> > + svn_revnum_t youngest;
> > + svn_ra_session_t *ra_session;
> > + svn_node_kind_t src_kind, dst_kind;
> > + const svn_delta_editor_t *editor;
> > + void *edit_baton;
> > + void *commit_baton;
> > + svn_revnum_t src_revnum;
> > + svn_boolean_t resurrection = FALSE;
> > + struct path_driver_cb_baton cb_baton;
> > + svn_error_t *err;
> >
> > + /* We have to open our session to the longest path common to both
> > + SRC_URL and DST_URL in the repository so we can do existence
> > + checks on both paths, and so we can operate on both paths in the
> > + case of a move. */
> > + top_url = svn_path_get_longest_ancestor (src_url, dst_url, pool);
> > +
> > + /* Special edge-case! (issue #683) If you're resurrecting a
> > + deleted item like this: 'svn cp -rN src_URL dst_URL', then it's
> > + possible for src_URL == dst_URL == top_url. In this situation,
> > + we want to open an RA session to the *parent* of all three. */
> > + if (strcmp (src_url, dst_url) == 0)
> > + {
> > + resurrection = TRUE;
> > + top_url = svn_path_dirname (top_url, pool);
> > + }
> > +
> > + /* Get the portions of the SRC and DST URLs that are relative to
> > + TOP_URL, and URI-decode those sections. */
> > + src_rel = svn_path_is_child (top_url, src_url, pool);
> > + if (src_rel)
> > + src_rel = svn_path_uri_decode (src_rel, pool);
> > + else
> > + src_rel = "";
> > +
> > + dst_rel = svn_path_is_child (top_url, dst_url, pool);
> > + if (dst_rel)
> > + dst_rel = svn_path_uri_decode (dst_rel, pool);
> > + else
> > + dst_rel = "";
> > +
> > + /* We can't move something into itself, period. */
> > + if (svn_path_is_empty (src_rel) && is_move)
> > + return svn_error_createf (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
> > + _("Cannot move URL '%s' into itself"), src_url);
> > +
> > + /* Open an RA session for the URL. Note that we don't have a local
> > + directory, nor a place to put temp files. */
> > + err = svn_client__open_ra_session (&ra_session, top_url,
> > + NULL,
> > + NULL, NULL, FALSE, TRUE,
> > + ctx, pool);
> > +
> > + /* If the two URLs appear not to be in the same repository, then
> > + top_url will be empty and the call to svn_ra_open()
> > + above will have failed. Below we check for that, and propagate a
> > + descriptive error back to the user.
> > +
> > + Ideally, we'd contact the repositories and compare their UUIDs to
> > + determine whether or not src and dst are in the same repository,
> > + instead of depending on an essentially textual comparison.
> > + However, it is simpler to assume that if someone is using the
> > + same repository, then they will use the same hostname/path to
> > + refer to it both times. Conversely, if the repositories are
> > + different, then they can't share a non-empty prefix, so top_url
> > + would still be "" and svn_ra_get_library() would still error.
> > + Thus we can get this check without extra network turnarounds to
> > + fetch the UUIDs.
> > + */
> > + if (err)
> > + {
> > + if ((err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
> > + && ((top_url == NULL) || (top_url[0] == '\0')))
> > + {
> > + return svn_error_createf
> > + (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
> > + _("Source and dest appear not to be in the same repository "
> > + "(src: '%s'; dst: '%s')"),
> > + src_url, dst_url);
> > + }
> > + else
> > + return err;
> > + }
> > +
> > + /* Pass NULL for the path, to ensure error if trying to get a
> > + revision based on the working copy. */
> > + SVN_ERR (svn_client__get_revision_number
> > + (&src_revnum, ra_session, src_revision, NULL, pool));
> > +
> > + /* Fetch the youngest revision. */
> > + SVN_ERR (svn_ra_get_latest_revnum (ra_session, &youngest, pool));
> > +
> > + /* Use YOUNGEST for copyfrom args if not provided. */
> > + if (! SVN_IS_VALID_REVNUM (src_revnum))
> > + src_revnum = youngest;
> > +
> > + /* Verify that SRC_URL exists in the repository. */
> > + SVN_ERR (svn_ra_check_path (ra_session, src_rel, src_revnum, &src_kind,
> > + pool));
> > + if (src_kind == svn_node_none)
> > + return svn_error_createf
> > + (SVN_ERR_FS_NOT_FOUND, NULL,
> > + _("Path '%s' does not exist in revision %ld"),
> > + src_url, src_revnum);
> > +
> > + /* Figure out the basename that will result from this operation. */
> > + SVN_ERR (svn_ra_check_path (ra_session, dst_rel, youngest, &dst_kind, pool));
> > + if (dst_kind == svn_node_none)
> > + {
> > + /* do nothing */
> > + }
> > + else if (dst_kind == svn_node_file)
> > + {
> > + /* We disallow the overwriting of files. */
> > + return svn_error_createf (SVN_ERR_FS_ALREADY_EXISTS, NULL,
> > + _("Path '%s' already exists"), dst_rel);
> > + }
> > + else if (dst_kind == svn_node_dir)
> > + {
> > + /* As a matter of client-side policy, we prevent overwriting any
> > + pre-existing directory. So we append src_url's basename to
> > + dst_rel, and see if that already exists. */
> > + svn_node_kind_t attempt_kind;
> > + const char *bname;
> > +
> > + bname = svn_path_uri_decode (svn_path_basename (src_url, pool), pool);
> > + dst_rel = svn_path_join (dst_rel, bname, pool);
> > + SVN_ERR (svn_ra_check_path (ra_session, dst_rel, youngest,
> > + &attempt_kind, pool));
> > + if (attempt_kind != svn_node_none)
> > + return svn_error_createf (SVN_ERR_FS_ALREADY_EXISTS, NULL,
> > + _("Path '%s' already exists"), dst_rel);
> > + }
> > + else
> > + {
> > + return svn_error_createf (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
> > + _("Unrecognized node kind of '%s'"), dst_url);
> > + }
> > +
> > + /* Create a new commit item and add it to the array. */
> > + if (ctx->log_msg_func)
> > + {
> > + svn_client_commit_item_t *item;
> > + const char *tmp_file;
> > + apr_array_header_t *commit_items
> > + = apr_array_make (pool, 2, sizeof (item));
> > +
> > + item = apr_pcalloc (pool, sizeof (*item));
> > + item->url = svn_path_join (top_url, dst_rel, pool);
> > + item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
> > + (*((svn_client_commit_item_t **) apr_array_push (commit_items))) = item;
> > + if (is_move && (! resurrection))
> > + {
> > + item = apr_pcalloc (pool, sizeof (*item));
> > + item->url = svn_path_join (top_url, src_rel, pool);
> > + item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
> > + (*((svn_client_commit_item_t **) apr_array_push (commit_items))) =
> > + item;
> > + }
> > + SVN_ERR ((*ctx->log_msg_func) (&message, &tmp_file, commit_items,
> > + ctx->log_msg_baton, pool));
> > + if (! message)
> > + return SVN_NO_ERROR;
> > + }
> > + else
> > + message = "";
> > +
> > +
> > + /* Fetch RA commit editor. */
> > + SVN_ERR (svn_client__commit_get_baton2 (&commit_baton, commit_info, pool));
> > + SVN_ERR (svn_ra_get_commit_editor2 (ra_session, &editor, &edit_baton, message,
> > + svn_client__commit_callback2,
> > + commit_baton,
> > + NULL, TRUE, /* No lock tokens */
> > + pool));
> > +
> > + /* Setup our PATHS for the path-based editor drive. */
> > + APR_ARRAY_PUSH (paths, const char *) = dst_rel;
> > + if (is_move && (! resurrection))
> > + APR_ARRAY_PUSH (paths, const char *) = src_rel;
> > +
> > + /* Setup the callback baton. */
> > + cb_baton.editor = editor;
> > + cb_baton.edit_baton = edit_baton;
> > + cb_baton.src_kind = src_kind;
> > + cb_baton.src_url = src_url;
> > + cb_baton.src_path = src_rel;
> > + cb_baton.dst_path = dst_rel;
> > + cb_baton.is_move = is_move;
> > + cb_baton.src_revnum = src_revnum;
> > + cb_baton.resurrection = resurrection;
> > +
> > + /* Call the path-based editor driver. */
> > + err = svn_delta_path_driver (editor, edit_baton, youngest, paths,
> > + path_driver_cb_func, &cb_baton, pool);
> > + if (err)
> > + {
> > + /* At least try to abort the edit (and fs txn) before throwing err. */
> > + svn_error_clear (editor->abort_edit (edit_baton, pool));
> > + return err;
> > + }
> > +
> > + /* Close the edit. */
> > + SVN_ERR (editor->close_edit (edit_baton, pool));
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > static svn_error_t *
> > repos_to_repos_copy (svn_client_commit_info_t **commit_info,
> > const char *src_url,
> > @@ -744,7 +964,172 @@
> > return reconcile_errors (cmt_err, unlock_err, cleanup_err, pool);
> > }
> >
> > +static svn_error_t *
> > +wc_to_repos_copy2 (svn_client_commit_info2_t **commit_info,
> > + const char *src_path,
> > + const char *dst_url,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + const char *anchor, *target, *base_name, *message;
> > + svn_ra_session_t *ra_session;
> > + const svn_delta_editor_t *editor;
> > + void *edit_baton;
> > + svn_node_kind_t src_kind, dst_kind;
> > + void *commit_baton;
> > + apr_hash_t *committables, *tempfiles = NULL;
> > + svn_wc_adm_access_t *adm_access, *dir_access;
> > + apr_array_header_t *commit_items;
> > + svn_error_t *cmt_err = SVN_NO_ERROR;
> > + svn_error_t *unlock_err = SVN_NO_ERROR;
> > + svn_error_t *cleanup_err = SVN_NO_ERROR;
> > + svn_boolean_t commit_in_progress = FALSE;
> > + const char *base_path;
> > + const char *base_url;
> >
> > + /* The commit process uses absolute paths, so we need to open the access
> > + baton using absolute paths, and so we really need to use absolute
> > + paths everywhere. */
> > + SVN_ERR (svn_path_get_absolute (&base_path, src_path, pool));
> > +
> > + SVN_ERR (svn_wc_adm_probe_open3 (&adm_access, NULL, base_path,
> > + FALSE, -1, ctx->cancel_func,
> > + ctx->cancel_baton, pool));
> > +
> > + /* Split the DST_URL into an anchor and target. */
> > + svn_path_split (dst_url, &anchor, &target, pool);
> > +
> > + /* Open an RA session for the anchor URL. */
> > + SVN_ERR (svn_client__open_ra_session (&ra_session, anchor,
> > + svn_wc_adm_access_path (adm_access),
> > + adm_access, NULL, TRUE, TRUE,
> > + ctx, pool));
> > +
> > + /* Figure out the basename that will result from this operation. */
> > + SVN_ERR (svn_ra_check_path (ra_session, svn_path_uri_decode (target, pool),
> > + SVN_INVALID_REVNUM, &dst_kind, pool));
> > +
> > + /* BASE_URL defaults to DST_URL. */
> > + base_url = apr_pstrdup (pool, dst_url);
> > + if (dst_kind == svn_node_none)
> > + {
> > + /* DST_URL doesn't exist under its parent URL, so the URL we
> > + will be creating is DST_URL. */
> > + }
> > + else if (dst_kind == svn_node_dir)
> > + {
> > + /* DST_URL is an existing directory URL. The URL we will be
> > + creating, then, is DST_URL+BASENAME. */
> > + svn_path_split (base_path, NULL, &base_name, pool);
> > + base_url = svn_path_url_add_component (base_url, base_name, pool);
> > + }
> > + else
> > + {
> > + /* DST_URL is an existing file, which can't be overwritten or
> > + used as a container, so error out. */
> > + return svn_error_createf (SVN_ERR_FS_ALREADY_EXISTS, NULL,
> > + _("File '%s' already exists"), dst_url);
> > + }
> > +
> > + /* Create a new commit item and add it to the array. */
> > + if (ctx->log_msg_func)
> > + {
> > + svn_client_commit_item_t *item;
> > + const char *tmp_file;
> > +
> > + commit_items = apr_array_make (pool, 1, sizeof (item));
> > + item = apr_pcalloc (pool, sizeof (*item));
> > + item->url = base_url;
> > + item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
> > + (*((svn_client_commit_item_t **) apr_array_push (commit_items))) = item;
> > +
> > + SVN_ERR ((*ctx->log_msg_func) (&message, &tmp_file, commit_items,
> > + ctx->log_msg_baton, pool));
> > + if (! message)
> > + return SVN_NO_ERROR;
> > + }
> > + else
> > + message = "";
> > +
> > + /* Crawl the working copy for commit items. */
> > + SVN_ERR (svn_io_check_path (base_path, &src_kind, pool));
> > + if (src_kind == svn_node_dir)
> > + SVN_ERR (svn_wc_adm_retrieve (&dir_access, adm_access, base_path, pool));
> > + else
> > + dir_access = adm_access;
> > + if ((cmt_err = svn_client__get_copy_committables (&committables,
> > + base_url,
> > + base_path,
> > + dir_access,
> > + ctx,
> > + pool)))
> > + goto cleanup;
> > +
> > + /* ### todo: There should be only one hash entry, which currently
> > + has a hacked name until we have the entries files storing
> > + canonical repository URLs. Then, the hacked name can go away and
> > + be replaced with a entry->repos (or whereever the entry's
> > + canonical repos URL is stored). */
> > + if (! ((commit_items = apr_hash_get (committables,
> > + SVN_CLIENT__SINGLE_REPOS_NAME,
> > + APR_HASH_KEY_STRING))))
> > + goto cleanup;
> > +
> > + /* Sort and condense our COMMIT_ITEMS. */
> > + if ((cmt_err = svn_client__condense_commit_items (&base_url,
> > + commit_items,
> > + pool)))
> > + goto cleanup;
> > +
> > + /* Open an RA session to BASE_URL. */
> > + if ((cmt_err = svn_client__open_ra_session (&ra_session, base_url,
> > + NULL, NULL, commit_items,
> > + FALSE, FALSE,
> > + ctx, pool)))
> > + goto cleanup;
> > +
> > + /* Fetch RA commit editor. */
> > + SVN_ERR (svn_client__commit_get_baton2 (&commit_baton, commit_info, pool));
> > + if ((cmt_err = svn_ra_get_commit_editor2 (ra_session, &editor, &edit_baton,
> > + message,
> > + svn_client__commit_callback2,
> > + commit_baton,
> > + NULL, TRUE, /* No lock tokens */
> > + pool)))
> > + goto cleanup;
> > +
> > + /* Make a note that we have a commit-in-progress. */
> > + commit_in_progress = TRUE;
> > +
> > + /* Perform the commit. */
> > + cmt_err = svn_client__do_commit (base_url, commit_items, adm_access,
> > + editor, edit_baton,
> > + 0, /* ### any notify_path_offset needed? */
> > + &tempfiles, ctx, pool);
> > +
> > + commit_in_progress = FALSE;
> > +
> > + /* Sleep to ensure timestamp integrity. */
> > + svn_sleep_for_timestamps ();
> > +
> > + cleanup:
> > + /* Abort the commit if it is still in progress. */
> > + if (commit_in_progress)
> > + svn_error_clear (editor->abort_edit (edit_baton, pool));
> > +
> > + /* It's only a read lock, so unlocking is harmless. */
> > + unlock_err = svn_wc_adm_close (adm_access);
> > +
> > + /* Remove any outstanding temporary text-base files. */
> > + if (tempfiles)
> > + cleanup_err = remove_tmpfiles (tempfiles,
> > + ctx->cancel_func, ctx->cancel_baton,
> > + pool);
> > +
> > + return reconcile_errors (cmt_err, unlock_err, cleanup_err, pool);
> > +}
> > +
> > +
> > static svn_error_t *
> > repos_to_wc_copy (const char *src_url,
> > const svn_opt_revision_t *src_revision,
> > @@ -1005,6 +1390,114 @@
> >
> >
> > static svn_error_t *
> > +setup_copy2 (svn_client_commit_info2_t **commit_info,
> > + const char *src_path,
> > + const svn_opt_revision_t *src_revision,
> > + const char *dst_path,
> > + svn_boolean_t is_move,
> > + svn_boolean_t force,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + svn_boolean_t src_is_url, dst_is_url;
> > +
> > + /* Are either of our paths URLs? */
> > + src_is_url = svn_path_is_url (src_path);
> > + dst_is_url = svn_path_is_url (dst_path);
> > +
> > + if (!src_is_url && !dst_is_url
> > + && svn_path_is_child (src_path, dst_path, pool))
> > + return svn_error_createf
> > + (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
> > + _("Cannot copy path '%s' into its own child '%s'"),
> > + svn_path_local_style (src_path, pool),
> > + svn_path_local_style (dst_path, pool));
> > +
> > + if (is_move)
> > + {
> > + if (src_is_url == dst_is_url)
> > + {
> > + if (strcmp (src_path, dst_path) == 0)
> > + return svn_error_createf
> > + (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
> > + _("Cannot move path '%s' into itself"),
> > + svn_path_local_style (src_path, pool));
> > + }
> > + else
> > + {
> > + /* Disallow moves between the working copy and the repository. */
> > + return svn_error_create
> > + (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
> > + _("No support for repos <--> working copy moves"));
> > + }
> > + }
> > + else
> > + {
> > + if (!src_is_url)
> > + {
> > + if (src_revision->kind != svn_opt_revision_unspecified
> > + && src_revision->kind != svn_opt_revision_working)
> > + {
> > + /* We can convert the working copy path to a URL based on the
> > + entries file. */
> > + svn_wc_adm_access_t *adm_access; /* ### FIXME local */
> > + const svn_wc_entry_t *entry;
> > + SVN_ERR (svn_wc_adm_probe_open3 (&adm_access, NULL,
> > + src_path, FALSE, 0,
> > + ctx->cancel_func,
> > + ctx->cancel_baton,
> > + pool));
> > + SVN_ERR (svn_wc_entry (&entry, src_path, adm_access, FALSE,
> > + pool));
> > + SVN_ERR (svn_wc_adm_close (adm_access));
> > +
> > + if (! entry)
> > + return svn_error_createf
> > + (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
> > + _("'%s' is not under version control"),
> > + svn_path_local_style (src_path, pool));
> > +
> > + if (! entry->url)
> > + return svn_error_createf
> > + (SVN_ERR_ENTRY_MISSING_URL, NULL,
> > + _("'%s' does not seem to have a URL associated with it"),
> > + svn_path_local_style (src_path, pool));
> > +
> > + src_path = entry->url;
> > + src_is_url = TRUE;
> > + }
> > + }
> > + }
> > +
> > + /* Now, call the right handler for the operation. */
> > + if ((! src_is_url) && (! dst_is_url))
> > + {
> > + SVN_ERR (wc_to_wc_copy (src_path, dst_path,
> > + is_move, force,
> > + ctx,
> > + pool));
> > + }
> > + else if ((! src_is_url) && (dst_is_url))
> > + {
> > + SVN_ERR (wc_to_repos_copy2 (commit_info, src_path, dst_path,
> > + ctx, pool));
> > + }
> > + else if ((src_is_url) && (! dst_is_url))
> > + {
> > + SVN_ERR (repos_to_wc_copy (src_path, src_revision,
> > + dst_path, ctx,
> > + pool));
> > + }
> > + else
> > + {
> > + SVN_ERR (repos_to_repos_copy2 (commit_info, src_path, src_revision,
> > + dst_path, ctx, is_move, pool));
> > + }
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +static svn_error_t *
> > setup_copy (svn_client_commit_info_t **commit_info,
> > const char *src_path,
> > const svn_opt_revision_t *src_revision,
> > @@ -1132,8 +1625,47 @@
> > pool);
> > }
> >
> > +/** since v1.3
> > + */
> > +svn_error_t *
> > +svn_client_copy2 (svn_client_commit_info2_t **commit_info,
> > + const char *src_path,
> > + const svn_opt_revision_t *src_revision,
> > + const char *dst_path,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + return setup_copy2 (commit_info,
> > + src_path, src_revision, dst_path,
> > + FALSE /* is_move */,
> > + TRUE /* force, set to avoid deletion check */,
> > + ctx,
> > + pool);
> > +}
> >
> > +
> > +/** since 1.3
> > + */
> > svn_error_t *
> > +svn_client_move3 (svn_client_commit_info2_t **commit_info,
> > + const char *src_path,
> > + const char *dst_path,
> > + svn_boolean_t force,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + const svn_opt_revision_t src_revision
> > + = { svn_opt_revision_unspecified, { 0 } };
> > +
> > + return setup_copy2 (commit_info,
> > + src_path, &src_revision, dst_path,
> > + TRUE /* is_move */,
> > + force,
> > + ctx,
> > + pool);
> > +}
> > +
> > +svn_error_t *
> > svn_client_move2 (svn_client_commit_info_t **commit_info,
> > const char *src_path,
> > const char *dst_path,
> > Index: subversion/libsvn_client/commit_util.c
> > ===================================================================
> > --- subversion/libsvn_client/commit_util.c (revision 14704)
> > +++ subversion/libsvn_client/commit_util.c (working copy)
> > @@ -1309,6 +1309,14 @@
> > apr_pool_t *pool;
> > };
> >
> > +/** @since New in 1.3.
> > + * Using svn_client_commit_info2_t
> > + */
> > +struct commit_baton2 {
> > + svn_client_commit_info2_t **info;
> > + apr_pool_t *pool;
> > +};
> > +
> > svn_error_t *svn_client__commit_get_baton (void **baton,
> > svn_client_commit_info_t **info,
> > apr_pool_t *pool)
> > @@ -1321,6 +1329,20 @@
> > return SVN_NO_ERROR;
> > }
> >
> > +/** since v1.3
> > + */
> > +svn_error_t *svn_client__commit_get_baton2 (void **baton,
> > + svn_client_commit_info2_t **info,
> > + apr_pool_t *pool)
> > +{
> > + struct commit_baton2 *cb = apr_pcalloc (pool, sizeof (*cb));
> > + cb->info = info;
> > + cb->pool = pool;
> > + *baton = cb;
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > svn_error_t *svn_client__commit_callback (svn_revnum_t revision,
> > const char *date,
> > const char *author,
> > @@ -1337,7 +1359,25 @@
> > return SVN_NO_ERROR;
> > }
> >
> > +svn_error_t *svn_client__commit_callback2 (svn_revnum_t revision,
> > + const char *date,
> > + const char *author,
> > + const char *post_commit_err,
> > + void *baton)
> > +{
> > + struct commit_baton2 *cb = baton;
> > + svn_client_commit_info2_t **info = cb->info;
> >
> > + *info = apr_palloc (cb->pool, sizeof (**info));
> > + (*info)->date = date ? apr_pstrdup (cb->pool, date) : NULL;
> > + (*info)->author = author ? apr_pstrdup (cb->pool, author) : NULL;
> > + (*info)->revision = revision;
> > + (*info)->post_commit_err = post_commit_err ? apr_pstrdup (cb->pool, post_commit_err) : NULL;
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +
> > #ifdef SVN_CLIENT_COMMIT_DEBUG
> >
> > /*** Temporary test editor ***/
> > Index: subversion/libsvn_client/commit.c
> > ===================================================================
> > --- subversion/libsvn_client/commit.c (revision 14704)
> > +++ subversion/libsvn_client/commit.c (working copy)
> > @@ -606,16 +606,226 @@
> >
> > /* Fetch RA commit editor. */
> > SVN_ERR (svn_client__commit_get_baton (&commit_baton, commit_info, pool));
> > - return svn_ra_get_commit_editor (*ra_session, editor, edit_baton, log_msg,
> > - svn_client__commit_callback,
> > - commit_baton, lock_tokens, keep_locks,
> > - pool);
> > + return svn_ra_get_commit_editor2 (*ra_session, editor, edit_baton, log_msg,
> > + svn_client__commit_callback2,
> > + commit_baton, lock_tokens, keep_locks,
> > + pool);
> > }
> >
> > +/** since v1.3
> > + */
> > +static svn_error_t *
> > +get_ra_editor2 (svn_ra_session_t **ra_session,
> > + svn_revnum_t *latest_rev,
> > + const svn_delta_editor_t **editor,
> > + void **edit_baton,
> > + svn_client_ctx_t *ctx,
> > + const char *base_url,
> > + const char *base_dir,
> > + svn_wc_adm_access_t *base_access,
> > + const char *log_msg,
> > + apr_array_header_t *commit_items,
> > + svn_client_commit_info2_t **commit_info,
> > + svn_boolean_t is_commit,
> > + apr_hash_t *lock_tokens,
> > + svn_boolean_t keep_locks,
> > + apr_pool_t *pool)
> > +{
> > + void *commit_baton;
> > +
> > + /* Open an RA session to URL. */
> > + SVN_ERR (svn_client__open_ra_session (ra_session,
> > + base_url, base_dir, base_access,
> > + commit_items,
> > + is_commit, !is_commit,
> > + ctx, pool));
> > +
> > + /* If this is an import (aka, not a commit), we need to verify that
> > + our repository URL exists. */
> > + if (! is_commit)
> > + {
> > + svn_node_kind_t kind;
> > +
> > + SVN_ERR (svn_ra_check_path (*ra_session, "", SVN_INVALID_REVNUM,
> > + &kind, pool));
> > + if (kind == svn_node_none)
> > + return svn_error_createf (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
> > + _("Path '%s' does not exist"),
> > + base_url);
> > + }
> > +
> > + /* Fetch the latest revision if requested. */
> > + if (latest_rev)
> > + SVN_ERR (svn_ra_get_latest_revnum (*ra_session, latest_rev, pool));
> > +
> > + /* Fetch RA commit editor. */
> > + SVN_ERR (svn_client__commit_get_baton2 (&commit_baton, commit_info, pool));
> > + return svn_ra_get_commit_editor2 (*ra_session, editor, edit_baton, log_msg,
> > + svn_client__commit_callback2,
> > + commit_baton, lock_tokens, keep_locks,
> > + pool);
> > +}
> > +
> >
> > /*** Public Interfaces. ***/
> >
> > svn_error_t *
> > +svn_client_import2 (svn_client_commit_info2_t **commit_info,
> > + const char *path,
> > + const char *url,
> > + svn_boolean_t nonrecursive,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + svn_error_t *err = SVN_NO_ERROR;
> > + const char *log_msg = "";
> > + const svn_delta_editor_t *editor;
> > + void *edit_baton;
> > + svn_ra_session_t *ra_session;
> > + apr_hash_t *excludes = apr_hash_make (pool);
> > + svn_node_kind_t kind;
> > + const char *base_dir = path;
> > + apr_array_header_t *new_entries = apr_array_make (pool, 4,
> > + sizeof (const char *));
> > + const char *temp;
> > + const char *dir;
> > + apr_pool_t *subpool;
> > +
> > + /* Create a new commit item and add it to the array. */
> > + if (ctx->log_msg_func)
> > + {
> > + /* If there's a log message gatherer, create a temporary commit
> > + item array solely to help generate the log message. The
> > + array is not used for the import itself. */
> > + svn_client_commit_item_t *item;
> > + const char *tmp_file;
> > + apr_array_header_t *commit_items
> > + = apr_array_make (pool, 1, sizeof (item));
> > +
> > + item = apr_pcalloc (pool, sizeof (*item));
> > + item->path = apr_pstrdup (pool, path);
> > + item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
> > + (*((svn_client_commit_item_t **) apr_array_push (commit_items))) = item;
> > +
> > + SVN_ERR ((*ctx->log_msg_func) (&log_msg, &tmp_file, commit_items,
> > + ctx->log_msg_baton, pool));
> > + if (! log_msg)
> > + return SVN_NO_ERROR;
> > + if (tmp_file)
> > + {
> > + const char *abs_path;
> > + SVN_ERR (svn_path_get_absolute (&abs_path, tmp_file, pool));
> > + apr_hash_set (excludes, abs_path, APR_HASH_KEY_STRING, (void *)1);
> > + }
> > + }
> > +
> > + SVN_ERR (svn_io_check_path (path, &kind, pool));
> > + if (kind == svn_node_file)
> > + svn_path_split (path, &base_dir, NULL, pool);
> > +
> > + /* Figure out all the path components we need to create just to have
> > + a place to stick our imported tree. */
> > + subpool = svn_pool_create (pool);
> > + do
> > + {
> > + svn_pool_clear (subpool);
> > +
> > + /* See if the user is interested in cancelling this operation. */
> > + if (ctx->cancel_func)
> > + SVN_ERR (ctx->cancel_func (ctx->cancel_baton));
> > +
> > + if (err)
> > + {
> > + /* If get_ra_editor below failed we either tried to open
> > + an invalid url, or else some other kind of error. In case
> > + the url was bad we back up a directory and try again. */
> > +
> > + if (err->apr_err != SVN_ERR_FS_NO_SUCH_ENTRY)
> > + return err;
> > + else
> > + svn_error_clear (err);
> > +
> > + svn_path_split (url, &temp, &dir, pool);
> > + *((const char **) apr_array_push (new_entries)) =
> > + svn_path_uri_decode (dir, pool);
> > + url = temp;
> > + }
> > + }
> > + while ((err = get_ra_editor2 (&ra_session, NULL,
> > + &editor, &edit_baton, ctx, url, base_dir,
> > + NULL, log_msg, NULL, commit_info,
> > + FALSE, NULL, TRUE, subpool)));
> > +
> > + /* Reverse the order of the components we added to our NEW_ENTRIES array. */
> > + if (new_entries->nelts)
> > + {
> > + int i, j;
> > + const char *component;
> > + for (i = 0; i < (new_entries->nelts / 2); i++)
> > + {
> > + j = new_entries->nelts - i - 1;
> > + component =
> > + APR_ARRAY_IDX (new_entries, i, const char *);
> > + APR_ARRAY_IDX (new_entries, i, const char *) =
> > + APR_ARRAY_IDX (new_entries, j, const char *);
> > + APR_ARRAY_IDX (new_entries, j, const char *) =
> > + component;
> > + }
> > + }
> > +
> > + /* An empty NEW_ENTRIES list the first call to get_ra_editor() above
> > + succeeded. That means that URL corresponds to an already
> > + existing filesystem entity. */
> > + if (kind == svn_node_file && (! new_entries->nelts))
> > + return svn_error_createf
> > + (SVN_ERR_ENTRY_EXISTS, NULL,
> > + _("Path '%s' already exists"), url);
> > +
> > + /* The repository doesn't know about the reserved administrative
> > + directory. */
> > + if (new_entries->nelts &&
> > + (strcmp (APR_ARRAY_IDX (new_entries,
> > + new_entries->nelts - 1,
> > + const char *), SVN_WC_ADM_DIR_NAME) == 0))
> > + return svn_error_createf
> > + (SVN_ERR_CL_ADM_DIR_RESERVED, NULL,
> > + _("'%s' is a reserved name and cannot be imported"),
> > + /* ### Is svn_path_local_style() really necessary for this? */
> > + svn_path_local_style (SVN_WC_ADM_DIR_NAME, pool));
> > +
> > +
> > + /* If an error occurred during the commit, abort the edit and return
> > + the error. We don't even care if the abort itself fails. */
> > + if ((err = import (path, new_entries, editor, edit_baton,
> > + nonrecursive, excludes, ctx, subpool)))
> > + {
> > + svn_error_clear (editor->abort_edit (edit_baton, subpool));
> > + return err;
> > + }
> > +
> > + /* Transfer *COMMIT_INFO from the subpool to the callers pool */
> > + if (*commit_info)
> > + {
> > + svn_client_commit_info2_t *tmp_commit_info;
> > +
> > + tmp_commit_info = apr_palloc(pool, sizeof(*tmp_commit_info));
> > + *tmp_commit_info = **commit_info;
> > + if (tmp_commit_info->date)
> > + tmp_commit_info->date = apr_pstrdup (pool, tmp_commit_info->date);
> > + if (tmp_commit_info->author)
> > + tmp_commit_info->author = apr_pstrdup (pool, tmp_commit_info->author);
> > + if (tmp_commit_info->post_commit_err)
> > + tmp_commit_info->post_commit_err
> > + = apr_pstrdup (pool, tmp_commit_info->post_commit_err);
> > + *commit_info = tmp_commit_info;
> > + }
> > +
> > + svn_pool_destroy (subpool);
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +svn_error_t *
> > svn_client_import (svn_client_commit_info_t **commit_info,
> > const char *path,
> > const char *url,
> > @@ -1594,6 +1804,465 @@
> > }
> >
> > svn_error_t *
> > +svn_client_commit3 (svn_client_commit_info2_t **commit_info,
> > + const apr_array_header_t *targets,
> > + svn_boolean_t recurse,
> > + svn_boolean_t keep_locks,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + const svn_delta_editor_t *editor;
> > + void *edit_baton;
> > + svn_ra_session_t *ra_session;
> > + const char *log_msg;
> > + const char *base_dir;
> > + const char *base_url;
> > + const char *target;
> > + apr_array_header_t *rel_targets;
> > + apr_array_header_t *dirs_to_lock;
> > + apr_array_header_t *dirs_to_lock_recursive;
> > + svn_boolean_t lock_base_dir_recursive = FALSE;
> > + apr_hash_t *committables, *lock_tokens, *tempfiles = NULL;
> > + svn_wc_adm_access_t *base_dir_access;
> > + apr_array_header_t *commit_items;
> > + svn_error_t *cmt_err = SVN_NO_ERROR, *unlock_err = SVN_NO_ERROR;
> > + svn_error_t *bump_err = SVN_NO_ERROR, *cleanup_err = SVN_NO_ERROR;
> > + svn_boolean_t commit_in_progress = FALSE;
> > + const char *display_dir = "";
> > + int i;
> > +
> > + /* Committing URLs doesn't make sense, so error if it's tried. */
> > + for (i = 0; i < targets->nelts; i++)
> > + {
> > + target = APR_ARRAY_IDX (targets, i, const char *);
> > + if (svn_path_is_url (target))
> > + return svn_error_createf
> > + (SVN_ERR_ILLEGAL_TARGET, NULL,
> > + _("'%s' is a URL, but URLs cannot be commit targets"), target);
> > + }
> > +
> > + /* Condense the target list. */
> > + SVN_ERR (svn_path_condense_targets (&base_dir, &rel_targets, targets,
> > + recurse, pool));
> > +
> > + /* No targets means nothing to commit, so just return. */
> > + if (! base_dir)
> > + return SVN_NO_ERROR;
> > +
> > + /* Prepare an array to accumulate dirs to lock */
> > + dirs_to_lock = apr_array_make (pool, 1, sizeof (target));
> > + dirs_to_lock_recursive = apr_array_make (pool, 1, sizeof (target));
> > +
> > + /* If we calculated only a base_dir and no relative targets, this
> > + must mean that we are being asked to commit (effectively) a
> > + single path. */
> > + if ((! rel_targets) || (! rel_targets->nelts))
> > + {
> > + const char *parent_dir, *name;
> > +
> > + SVN_ERR (svn_wc_get_actual_target (base_dir, &parent_dir, &name, pool));
> > + if (*name)
> > + {
> > + svn_node_kind_t kind;
> > +
> > + /* Our new "grandfather directory" is the parent directory
> > + of the former one. */
> > + base_dir = apr_pstrdup (pool, parent_dir);
> > +
> > + /* Make the array if it wasn't already created. */
> > + if (! rel_targets)
> > + rel_targets = apr_array_make (pool, targets->nelts, sizeof (name));
> > +
> > + /* Now, push this name as a relative path to our new
> > + base directory. */
> > + APR_ARRAY_PUSH (rel_targets, const char *) = name;
> > +
> > + target = svn_path_join (base_dir, name, pool);
> > + SVN_ERR (svn_io_check_path (target, &kind, pool));
> > +
> > + /* If the final target is a dir, we want to recursively lock it */
> > + if (kind == svn_node_dir)
> > + {
> > + if (recurse)
> > + APR_ARRAY_PUSH (dirs_to_lock_recursive, const char *) = target;
> > + else
> > + APR_ARRAY_PUSH (dirs_to_lock, const char *) = target;
> > + }
> > + }
> > + else
> > + {
> > + /* This will recursively lock the base_dir further down */
> > + lock_base_dir_recursive = TRUE;
> > + }
> > + }
> > + else
> > + {
> > + apr_pool_t *subpool = svn_pool_create (pool);
> > +
> > + SVN_ERR (adjust_rel_targets (&base_dir, &rel_targets,
> > + base_dir, rel_targets,
> > + pool));
> > +
> > + for (i = 0; i < rel_targets->nelts; i++)
> > + {
> > + const char *parent_dir, *name;
> > +
> > + svn_pool_clear (subpool);
> > + target = svn_path_join (base_dir,
> > + APR_ARRAY_IDX (rel_targets, i, const char *),
> > + subpool);
> > + SVN_ERR (svn_wc_get_actual_target (target, &parent_dir,
> > + &name, subpool));
> > +
> > + if (*name)
> > + {
> > + svn_node_kind_t kind;
> > +
> > + target = svn_path_join (parent_dir, name, subpool);
> > +
> > + SVN_ERR (svn_io_check_path (target, &kind, subpool));
> > +
> > + /* If the final target is a dir, we want to recursively
> > + lock it */
> > + if (kind == svn_node_dir)
> > + {
> > + if (recurse)
> > + APR_ARRAY_PUSH (dirs_to_lock_recursive,
> > + const char *) = apr_pstrdup (pool, target);
> > + else
> > + APR_ARRAY_PUSH (dirs_to_lock,
> > + const char *) = apr_pstrdup (pool, target);
> > + }
> > + }
> > +
> > + target = parent_dir;
> > + while (strcmp (target, base_dir))
> > + {
> > + if (target[0] == '/' && target[1] == '\0')
> > + abort();
> > +
> > + APR_ARRAY_PUSH (dirs_to_lock,
> > + const char *) = apr_pstrdup (pool, target);
> > + target = svn_path_dirname(target, subpool);
> > + }
> > + }
> > + svn_pool_destroy (subpool);
> > + }
> > +
> > + SVN_ERR (svn_wc_adm_open3 (&base_dir_access, NULL, base_dir,
> > + TRUE, /* Write lock */
> > + lock_base_dir_recursive ? -1 : 0, /* Depth */
> > + ctx->cancel_func, ctx->cancel_baton,
> > + pool));
> > +
> > + if (!lock_base_dir_recursive)
> > + {
> > + svn_wc_adm_access_t *adm_access;
> > + apr_array_header_t *unique_dirs_to_lock;
> > +
> > + /* Sort the paths in a depth-last directory-ish order. */
> > + qsort (dirs_to_lock->elts, dirs_to_lock->nelts,
> > + dirs_to_lock->elt_size, svn_sort_compare_paths);
> > + qsort (dirs_to_lock_recursive->elts, dirs_to_lock_recursive->nelts,
> > + dirs_to_lock_recursive->elt_size, svn_sort_compare_paths);
> > +
> > + /* Remove any duplicates */
> > + SVN_ERR (svn_path_remove_redundancies (&unique_dirs_to_lock,
> > + dirs_to_lock_recursive,
> > + pool));
> > + dirs_to_lock_recursive = unique_dirs_to_lock;
> > +
> > + /* Remove dirs and descendants from dirs_to_lock if there is
> > + any ancestor in dirs_to_lock_recursive */
> > + SVN_ERR (remove_redundancies (&unique_dirs_to_lock,
> > + dirs_to_lock,
> > + dirs_to_lock_recursive,
> > + pool));
> > + dirs_to_lock = unique_dirs_to_lock;
> > +
> > + /* First lock all the dirs to be locked non-recursively */
> > + if (dirs_to_lock)
> > + {
> > + for (i = 0; i < dirs_to_lock->nelts ; ++i)
> > + {
> > + target = APR_ARRAY_IDX (dirs_to_lock, i, const char *);
> > +
> > + SVN_ERR (svn_wc_adm_open3 (&adm_access, base_dir_access,
> > + target,
> > + TRUE, /* Write lock */
> > + 0, /* Depth */
> > + ctx->cancel_func,
> > + ctx->cancel_baton,
> > + pool));
> > + }
> > + }
> > +
> > + /* Lock the rest of the targets (recursively) */
> > + if (dirs_to_lock_recursive)
> > + {
> > + for (i = 0; i < dirs_to_lock_recursive->nelts ; ++i)
> > + {
> > + target = APR_ARRAY_IDX (dirs_to_lock_recursive, i, const char *);
> > +
> > + SVN_ERR (svn_wc_adm_open3 (&adm_access, base_dir_access,
> > + target,
> > + TRUE, /* Write lock */
> > + -1, /* Depth */
> > + ctx->cancel_func,
> > + ctx->cancel_baton,
> > + pool));
> > + }
> > + }
> > + }
> > +
> > + /* One day we might support committing from multiple working copies, but
> > + we don't yet. This check ensures that we don't silently commit a
> > + subset of the targets.
> > +
> > + At the same time, if a non-recursive commit is desired, do not
> > + allow a deleted directory as one of the targets. */
> > + for (i = 0; i < targets->nelts; ++i)
> > + {
> > + svn_wc_adm_access_t *adm_access;
> > +
> > + SVN_ERR (svn_path_get_absolute (&target,
> > + APR_ARRAY_IDX (targets, i, const char *),
> > + pool));
> > + SVN_ERR_W (svn_wc_adm_probe_retrieve (&adm_access, base_dir_access,
> > + target, pool),
> > + _("Are all the targets part of the same working copy?"));
> > +
> > + if (!recurse)
> > + {
> > + svn_wc_status2_t *status;
> > + svn_node_kind_t kind;
> > +
> > + SVN_ERR (svn_io_check_path (target, &kind, pool));
> > +
> > + if (kind == svn_node_dir)
> > + {
> > + SVN_ERR (svn_wc_status2 (&status, target, adm_access, pool));
> > + if (status->text_status == svn_wc_status_deleted ||
> > + status->text_status == svn_wc_status_replaced)
> > + return svn_error_create (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
> > + _("Cannot non-recursively commit a "
> > + "directory deletion"));
> > + }
> > + }
> > + }
> > +
> > + /* Crawl the working copy for commit items. */
> > + if ((cmt_err = svn_client__harvest_committables (&committables,
> > + &lock_tokens,
> > + base_dir_access,
> > + rel_targets,
> > + recurse ? FALSE : TRUE,
> > + ! keep_locks,
> > + ctx,
> > + pool)))
> > + goto cleanup;
> > +
> > + /* ### todo: Currently there should be only one hash entry, which
> > + has a hacked name until we have the entries files storing
> > + canonical repository URLs. Then, the hacked name can go away
> > + and be replaced with a canonical repos URL, and from there we
> > + are poised to started handling nested working copies. See
> > + http://subversion.tigris.org/issues/show_bug.cgi?id=960. */
> > + if (! ((commit_items = apr_hash_get (committables,
> > + SVN_CLIENT__SINGLE_REPOS_NAME,
> > + APR_HASH_KEY_STRING))))
> > + goto cleanup;
> > +
> > + /* If our array of targets contains only locks (and no actual file
> > + or prop modifications), then we return here to avoid committing a
> > + revision with no changes. */
> > + {
> > + svn_boolean_t found_changed_path = FALSE;
> > +
> > + for (i = 0; i < commit_items->nelts; ++i)
> > + {
> > + svn_client_commit_item_t *item;
> > + item = APR_ARRAY_IDX (commit_items, i, svn_client_commit_item_t *);
> > +
> > + if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
> > + {
> > + found_changed_path = TRUE;
> > + break;
> > + }
> > + }
> > +
> > + if (!found_changed_path)
> > + goto cleanup;
> > + }
> > +
> > + /* Go get a log message. If an error occurs, or no log message is
> > + specified, abort the operation. */
> > + if (ctx->log_msg_func)
> > + {
> > + const char *tmp_file;
> > + cmt_err = (*ctx->log_msg_func)(&log_msg, &tmp_file, commit_items,
> > + ctx->log_msg_baton, pool);
> > + if (cmt_err || (! log_msg))
> > + goto cleanup;
> > + }
> > + else
> > + log_msg = "";
> > +
> > + /* Sort and condense our COMMIT_ITEMS. */
> > + if ((cmt_err = svn_client__condense_commit_items (&base_url,
> > + commit_items,
> > + pool)))
> > + goto cleanup;
> > +
> > + /* Collect our lock tokens with paths relative to base_url. */
> > + if ((cmt_err = collect_lock_tokens (&lock_tokens, lock_tokens, base_url,
> > + pool)))
> > + goto cleanup;
> > +
> > + if ((cmt_err = get_ra_editor2 (&ra_session, NULL,
> > + &editor, &edit_baton, ctx,
> > + base_url, base_dir, base_dir_access,
> > + log_msg, commit_items, commit_info,
> > + TRUE, lock_tokens, keep_locks, pool)))
> > + goto cleanup;
> > +
> > + /* Make a note that we have a commit-in-progress. */
> > + commit_in_progress = TRUE;
> > +
> > + /* Determine prefix to strip from the commit notify messages */
> > + if ((cmt_err = svn_path_get_absolute (&display_dir,
> > + display_dir, pool)))
> > + goto cleanup;
> > + display_dir = svn_path_get_longest_ancestor (display_dir, base_dir, pool);
> > +
> > + /* Perform the commit. */
> > + cmt_err = svn_client__do_commit (base_url, commit_items, base_dir_access,
> > + editor, edit_baton,
> > + display_dir,
> > + &tempfiles, ctx, pool);
> > +
> > + /* Handle a successful commit. */
> > + if ((! cmt_err)
> > + || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
> > + {
> > + apr_pool_t *subpool = svn_pool_create (pool);
> > +
> > + /* Make a note that our commit is finished. */
> > + commit_in_progress = FALSE;
> > +
> > + for (i = 0; i < commit_items->nelts; i++)
> > + {
> > + svn_client_commit_item_t *item
> > + = ((svn_client_commit_item_t **) commit_items->elts)[i];
> > + svn_boolean_t loop_recurse = FALSE;
> > + const char *adm_access_path;
> > + svn_wc_adm_access_t *adm_access;
> > + const svn_wc_entry_t *entry;
> > + svn_boolean_t remove_lock;
> > +
> > + /* Clear the subpool here because there are some 'continue'
> > + statements in this loop. */
> > + svn_pool_clear (subpool);
> > +
> > + if (item->kind == svn_node_dir)
> > + adm_access_path = item->path;
> > + else
> > + svn_path_split (item->path, &adm_access_path, NULL, subpool);
> > +
> > + bump_err = svn_wc_adm_retrieve (&adm_access, base_dir_access,
> > + adm_access_path, subpool);
> > + if (bump_err)
> > + {
> > + if (bump_err->apr_err == SVN_ERR_WC_NOT_LOCKED)
> > + {
> > + if (have_processed_parent (commit_items, i,
> > + item->path, subpool))
> > + {
> > + /* This happens when the item is a directory that is
> > + deleted, and it has been processed as a child of an
> > + earlier item. */
> > + svn_error_clear (bump_err);
> > + bump_err = SVN_NO_ERROR;
> > + continue;
> > + }
> > +
> > + /* Is it a directory that was deleted in the commit? */
> > + if (item->kind == svn_node_dir
> > + && item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
> > + {
> > + /* It better be missing then. Assuming it is,
> > + mark as deleted in parent. If not, then
> > + something is way bogus. */
> > + svn_error_clear (bump_err);
> > + bump_err = svn_wc_mark_missing_deleted (item->path,
> > + base_dir_access,
> > + subpool);
> > + if (bump_err)
> > + goto cleanup;
> > + continue;
> > + }
> > + }
> > + goto cleanup;
> > + }
> > + if ((bump_err = svn_wc_entry (&entry, item->path, adm_access, TRUE,
> > + subpool)))
> > + goto cleanup;
> > +
> > + if (! entry
> > + && have_processed_parent (commit_items, i, item->path, subpool))
> > + /* This happens when the item is a file that is deleted, and it
> > + has been processed as a child of an earlier item. */
> > + continue;
> > +
> > + if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
> > + && (item->kind == svn_node_dir)
> > + && (item->copyfrom_url))
> > + loop_recurse = TRUE;
> > +
> > + remove_lock = (! keep_locks && (item->state_flags
> > + & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN));
> > + assert (*commit_info);
> > + if ((bump_err = svn_wc_process_committed2 (item->path, adm_access,
> > + loop_recurse,
> > + (*commit_info)->revision,
> > + (*commit_info)->date,
> > + (*commit_info)->author,
> > + item->wcprop_changes,
> > + remove_lock,
> > + subpool)))
> > + break;
> > +
> > + }
> > +
> > + /* Destroy the subpool. */
> > + svn_pool_destroy (subpool);
> > + }
> > +
> > + /* Sleep to ensure timestamp integrity. */
> > + svn_sleep_for_timestamps ();
> > +
> > + cleanup:
> > + /* Abort the commit if it is still in progress. */
> > + if (commit_in_progress)
> > + svn_error_clear (editor->abort_edit (edit_baton, pool));
> > +
> > + /* A bump error is likely to occur while running a working copy log file,
> > + explicitly unlocking and removing temporary files would be wrong in
> > + that case. A commit error (cmt_err) should only occur before any
> > + attempt to modify the working copy, so it doesn't prevent explicit
> > + clean-up. */
> > + if (! bump_err)
> > + {
> > + unlock_err = svn_wc_adm_close (base_dir_access);
> > +
> > + if (! unlock_err)
> > + cleanup_err = remove_tmpfiles (tempfiles, pool);
> > + }
> > +
> > + return reconcile_errors (cmt_err, unlock_err, bump_err, cleanup_err, pool);
> > +}
> > +
> > +svn_error_t *
> > svn_client_commit (svn_client_commit_info_t **commit_info,
> > const apr_array_header_t *targets,
> > svn_boolean_t nonrecursive,
> > Index: subversion/libsvn_client/add.c
> > ===================================================================
> > --- subversion/libsvn_client/add.c (revision 14704)
> > +++ subversion/libsvn_client/add.c (working copy)
> > @@ -601,8 +601,174 @@
> > return SVN_NO_ERROR;
> > }
> >
> > +static svn_error_t *
> > +mkdir_urls2 (svn_client_commit_info2_t **commit_info,
> > + const apr_array_header_t *paths,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + svn_ra_session_t *ra_session;
> > + const svn_delta_editor_t *editor;
> > + void *edit_baton;
> > + void *commit_baton;
> > + const char *log_msg;
> > + apr_array_header_t *targets;
> > + svn_error_t *err;
> > + const char *common;
> > + int i;
> >
> > + /* Condense our list of mkdir targets. */
> > + SVN_ERR (svn_path_condense_targets (&common, &targets, paths, FALSE, pool));
> > + if (! targets->nelts)
> > + {
> > + const char *bname;
> > + svn_path_split (common, &common, &bname, pool);
> > + APR_ARRAY_PUSH (targets, const char *) = bname;
> > + }
> > + else
> > + {
> > + svn_boolean_t resplit = FALSE;
> > +
> > + /* We can't "mkdir" the root of an editor drive, so if one of
> > + our targets is the empty string, we need to back everything
> > + up by a path component. */
> > + for (i = 0; i < targets->nelts; i++)
> > + {
> > + const char *path = APR_ARRAY_IDX (targets, i, const char *);
> > + if (! *path)
> > + {
> > + resplit = TRUE;
> > + break;
> > + }
> > + }
> > + if (resplit)
> > + {
> > + const char *bname;
> > + svn_path_split (common, &common, &bname, pool);
> > + for (i = 0; i < targets->nelts; i++)
> > + {
> > + const char *path = APR_ARRAY_IDX (targets, i, const char *);
> > + path = svn_path_join (bname, path, pool);
> > + APR_ARRAY_IDX (targets, i, const char *) = path;
> > + }
> > + }
> > + }
> > +
> > + /* Create new commit items and add them to the array. */
> > + if (ctx->log_msg_func)
> > + {
> > + svn_client_commit_item_t *item;
> > + const char *tmp_file;
> > + apr_array_header_t *commit_items
> > + = apr_array_make (pool, targets->nelts, sizeof (item));
> > +
> > + for (i = 0; i < targets->nelts; i++)
> > + {
> > + const char *path = APR_ARRAY_IDX (targets, i, const char *);
> > + item = apr_pcalloc (pool, sizeof (*item));
> > + item->url = svn_path_join (common, path, pool);
> > + item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
> > + APR_ARRAY_PUSH (commit_items, svn_client_commit_item_t *) = item;
> > + }
> > + SVN_ERR ((*ctx->log_msg_func) (&log_msg, &tmp_file, commit_items,
> > + ctx->log_msg_baton, pool));
> > + if (! log_msg)
> > + return SVN_NO_ERROR;
> > + }
> > + else
> > + log_msg = "";
> > +
> > + /* Open an RA session for the URL. Note that we don't have a local
> > + directory, nor a place to put temp files. */
> > + SVN_ERR (svn_client__open_ra_session (&ra_session, common, NULL,
> > + NULL, NULL, FALSE, TRUE,
> > + ctx, pool));
> > +
> > + /* URI-decode each target. */
> > + for (i = 0; i < targets->nelts; i++)
> > + {
> > + const char *path = APR_ARRAY_IDX (targets, i, const char *);
> > + path = svn_path_uri_decode (path, pool);
> > + APR_ARRAY_IDX (targets, i, const char *) = path;
> > + }
> > +
> > + /* Fetch RA commit editor */
> > + SVN_ERR (svn_client__commit_get_baton2 (&commit_baton, commit_info, pool));
> > + SVN_ERR (svn_ra_get_commit_editor2 (ra_session, &editor, &edit_baton,
> > + log_msg, svn_client__commit_callback2,
> > + commit_baton,
> > + NULL, TRUE, /* No lock tokens */
> > + pool));
> > +
> > + /* Call the path-based editor driver. */
> > + err = svn_delta_path_driver (editor, edit_baton, SVN_INVALID_REVNUM,
> > + targets, path_driver_cb_func,
> > + (void *)editor, pool);
> > + if (err)
> > + {
> > + /* At least try to abort the edit (and fs txn) before throwing err. */
> > + svn_error_clear (editor->abort_edit (edit_baton, pool));
> > + return err;
> > + }
> > +
> > + /* Close the edit. */
> > + SVN_ERR (editor->close_edit (edit_baton, pool));
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +
> > svn_error_t *
> > +svn_client_mkdir2 (svn_client_commit_info2_t **commit_info,
> > + const apr_array_header_t *paths,
> > + svn_client_ctx_t *ctx,
> > + apr_pool_t *pool)
> > +{
> > + if (! paths->nelts)
> > + return SVN_NO_ERROR;
> > +
> > + if (svn_path_is_url (APR_ARRAY_IDX (paths, 0, const char *)))
> > + {
> > + SVN_ERR (mkdir_urls2 (commit_info, paths, ctx, pool));
> > + }
> > + else
> > + {
> > + /* This is a regular "mkdir" + "svn add" */
> > + apr_pool_t *subpool = svn_pool_create (pool);
> > + svn_error_t *err;
> > + int i;
> > +
> > + for (i = 0; i < paths->nelts; i++)
> > + {
> > + const char *path = APR_ARRAY_IDX (paths, i, const char *);
> > +
> > + svn_pool_clear (subpool);
> > +
> > + /* See if the user wants us to stop. */
> > + if (ctx->cancel_func)
> > + SVN_ERR (ctx->cancel_func (ctx->cancel_baton));
> > +
> > + SVN_ERR (svn_io_dir_make (path, APR_OS_DEFAULT, subpool));
> > + err = svn_client_add (path, FALSE, ctx, subpool);
> > +
> > + /* We just created a new directory, but couldn't add it to
> > + version control. Don't leave unversioned directoies behind. */
> > + if (err)
> > + {
> > + /* ### If this returns an error, should we link it onto
> > + err instead, so that the user is warned that we just
> > + created an unversioned directory? */
> > + svn_error_clear (svn_io_remove_dir (path, subpool));
> > + return err;
> > + }
> > + }
> > + svn_pool_destroy (subpool);
> > + }
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +svn_error_t *
> > svn_client_mkdir (svn_client_commit_info_t **commit_info,
> > const apr_array_header_t *paths,
> > svn_client_ctx_t *ctx,
> > Index: subversion/mod_dav_svn/merge.c
> > ===================================================================
> > --- subversion/mod_dav_svn/merge.c (revision 14704)
> > +++ subversion/mod_dav_svn/merge.c (working copy)
> > @@ -29,6 +29,7 @@
> > #include "svn_props.h"
> >
> > #include "dav_svn.h"
> > +#include "svn_xml.h"
> >
> >
> > /* #################################################################
> > @@ -197,18 +198,32 @@
> > PUBLIC FUNCTIONS
> > */
> >
> > -dav_error * dav_svn__merge_response(ap_filter_t *output,
> > +dav_error * dav_svn__merge_response (ap_filter_t *output,
> > const dav_svn_repos *repos,
> > svn_revnum_t new_rev,
> > apr_xml_elem *prop_elem,
> > svn_boolean_t disable_merge_response,
> > apr_pool_t *pool)
> > {
> > + return dav_svn__merge_response2 (output, repos, new_rev, NULL,
> > + prop_elem, disable_merge_response,
> > + pool);
> > +}
> > +
> > +dav_error * dav_svn__merge_response2 (ap_filter_t *output,
> > + const dav_svn_repos *repos,
> > + svn_revnum_t new_rev,
> > + char *post_commit_err,
> > + apr_xml_elem *prop_elem,
> > + svn_boolean_t disable_merge_response,
> > + apr_pool_t *pool)
> > +{
> > apr_bucket_brigade *bb;
> > svn_fs_root_t *root;
> > svn_error_t *serr;
> > const char *vcc;
> > const char *rev;
> > + const char *post_commit_err_elem, *post_commit_header_info;
> > svn_string_t *creationdate, *creator_displayname;
> >
> > serr = svn_fs_revision_root(&root, repos->fs, new_rev, pool);
> > @@ -231,6 +246,23 @@
> > /* the version-name of the baseline is the revision number */
> > rev = apr_psprintf(pool, "%ld", new_rev);
> >
> > + /* get the post-commit hook stderr, if any */
> > + if (post_commit_err)
> > + {
> > + post_commit_header_info = apr_psprintf (pool,
> > + " xmlns:S=\"%s\"",
> > + SVN_XML_NAMESPACE);
> > + post_commit_err_elem = apr_psprintf (pool,
> > + "<S:post-commit-err>%s"
> > + "</S:post-commit-err>",
> > + post_commit_err);
> > + }
> > + else
> > + {
> > + post_commit_header_info = "" ;
> > + post_commit_err_elem = "" ;
> > + }
> > +
> > /* get the creationdate and creator-displayname of the new revision, too. */
> > serr = svn_fs_revision_prop(&creationdate, repos->fs, new_rev,
> > SVN_PROP_REVISION_DATE, pool);
> > @@ -252,7 +284,9 @@
> >
> > (void) ap_fputstrs(output, bb,
> > DAV_XML_HEADER DEBUG_CR
> > - "<D:merge-response xmlns:D=\"DAV:\">" DEBUG_CR
> > + "<D:merge-response xmlns:D=\"DAV:\"",
> > + post_commit_header_info,
> > + ">" DEBUG_CR
> > "<D:updated-set>" DEBUG_CR
> >
> > /* generate a response for the new baseline */
> > @@ -264,7 +298,8 @@
> > /* ### this is wrong. it's a VCC, not a baseline. but
> > ### we need to tell the client to look at *this*
> > ### resource for the version-name. */
> > - "<D:resourcetype><D:baseline/></D:resourcetype>" DEBUG_CR
> > + "<D:resourcetype><D:baseline/></D:resourcetype>" DEBUG_CR,
> > + post_commit_err_elem, DEBUG_CR
> > "<D:version-name>", rev, "</D:version-name>" DEBUG_CR,
> > NULL);
> > if (creationdate)
> > Index: subversion/mod_dav_svn/dav_svn.h
> > ===================================================================
> > --- subversion/mod_dav_svn/dav_svn.h (revision 14704)
> > +++ subversion/mod_dav_svn/dav_svn.h (working copy)
> > @@ -503,6 +503,16 @@
> > apr_xml_elem *prop_elem,
> > svn_boolean_t disable_merge_response,
> > apr_pool_t *pool);
> > +/** @since New in 1.3.
> > + * v2. To accomodate post-commit hook's stderr
> > + */
> > +dav_error * dav_svn__merge_response2(ap_filter_t *output,
> > + const dav_svn_repos *repos,
> > + svn_revnum_t new_rev,
> > + char *post_commit_err,
> > + apr_xml_elem *prop_elem,
> > + svn_boolean_t disable_merge_response,
> > + apr_pool_t *pool);
> >
> > dav_error * dav_svn__update_report(const dav_resource *resource,
> > const apr_xml_doc *doc,
> > Index: subversion/mod_dav_svn/version.c
> > ===================================================================
> > --- subversion/mod_dav_svn/version.c (revision 14704)
> > +++ subversion/mod_dav_svn/version.c (working copy)
> > @@ -1619,6 +1619,7 @@
> > svn_fs_txn_t *txn;
> > const char *conflict;
> > svn_error_t *serr;
> > + char *post_commit_err = NULL ;
> > svn_revnum_t new_rev;
> > apr_hash_t *locks;
> > svn_boolean_t disable_merge_response = FALSE;
> > @@ -1699,7 +1700,11 @@
> > return dav_svn_convert_err(serr, HTTP_CONFLICT, msg, pool);
> > }
> > else if (serr)
> > - svn_error_clear(serr);
> > + {
> > + if (serr->child->message)
> > + post_commit_err = apr_pstrdup (pool, serr->child->message);
> > + svn_error_clear(serr);
> > + }
> >
> > /* Commit was successful, so schedule deltification. */
> > register_deltification_cleanup(source->info->repos->repos, new_rev,
> > @@ -1739,8 +1744,9 @@
> > }
> >
> > /* process the response for the new revision. */
> > - return dav_svn__merge_response(output, source->info->repos, new_rev,
> > - prop_elem, disable_merge_response, pool);
> > + return dav_svn__merge_response2 (output, source->info->repos, new_rev,
> > + post_commit_err, prop_elem,
> > + disable_merge_response, pool);
> > }
> >
> > const dav_hooks_vsn dav_svn_hooks_vsn = {
> > Index: subversion/clients/cmdline/cl.h
> > ===================================================================
> > --- subversion/clients/cmdline/cl.h (revision 14704)
> > +++ subversion/clients/cmdline/cl.h (working copy)
> > @@ -234,7 +234,13 @@
> > svn_error_t *svn_cl__print_commit_info (svn_client_commit_info_t *commit_info,
> > apr_pool_t *pool);
> >
> > +/** since v1.3
> > + * using svn_client_commit_info2_t
> > + */
> > +svn_error_t *svn_cl__print_commit_info2 (svn_client_commit_info2_t *commit_info,
> > + apr_pool_t *pool);
> >
> > +
> > /* Print STATUS for PATH to stdout for human consumption. Prints in
> > abbreviated format by default, or DETAILED format if flag is set.
> >
> > Index: subversion/clients/cmdline/mkdir-cmd.c
> > ===================================================================
> > --- subversion/clients/cmdline/mkdir-cmd.c (revision 14704)
> > +++ subversion/clients/cmdline/mkdir-cmd.c (working copy)
> > @@ -43,7 +43,7 @@
> > svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
> > apr_array_header_t *targets;
> > apr_pool_t *subpool = svn_pool_create (pool);
> > - svn_client_commit_info_t *commit_info = NULL;
> > + svn_client_commit_info2_t *commit_info = NULL;
> > svn_error_t *err;
> >
> > SVN_ERR (svn_opt_args_to_target_array2 (&targets, os,
> > @@ -72,7 +72,7 @@
> > NULL, ctx->config, subpool));
> > }
> >
> > - err = svn_client_mkdir (&commit_info, targets, ctx, subpool);
> > + err = svn_client_mkdir2 (&commit_info, targets, ctx, subpool);
> >
> > if (ctx->log_msg_func)
> > err = svn_cl__cleanup_log_msg (ctx->log_msg_baton, err);
> > @@ -87,7 +87,7 @@
> > }
> >
> > if (commit_info && ! opt_state->quiet)
> > - SVN_ERR (svn_cl__print_commit_info (commit_info, subpool));
> > + SVN_ERR (svn_cl__print_commit_info2 (commit_info, subpool));
> >
> > return SVN_NO_ERROR;
> > }
> > Index: subversion/clients/cmdline/move-cmd.c
> > ===================================================================
> > --- subversion/clients/cmdline/move-cmd.c (revision 14704)
> > +++ subversion/clients/cmdline/move-cmd.c (working copy)
> > @@ -42,7 +42,7 @@
> > svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
> > apr_array_header_t *targets;
> > const char *src_path, *dst_path;
> > - svn_client_commit_info_t *commit_info = NULL;
> > + svn_client_commit_info2_t *commit_info = NULL;
> > svn_error_t *err;
> >
> > SVN_ERR (svn_opt_args_to_target_array2 (&targets, os,
> > @@ -69,7 +69,7 @@
> > _("Cannot specify revisions (except HEAD) with move operations"));
> > }
> >
> > - err = svn_client_move2 (&commit_info, src_path, dst_path,
> > + err = svn_client_move3 (&commit_info, src_path, dst_path,
> > opt_state->force, ctx, pool);
> >
> > if (err)
> > @@ -77,7 +77,7 @@
> > SVN_ERR (svn_cl__cleanup_log_msg (ctx->log_msg_baton, err));
> >
> > if (commit_info && ! opt_state->quiet)
> > - SVN_ERR (svn_cl__print_commit_info (commit_info, pool));
> > + SVN_ERR (svn_cl__print_commit_info2 (commit_info, pool));
> >
> > return SVN_NO_ERROR;
> > }
> > Index: subversion/clients/cmdline/copy-cmd.c
> > ===================================================================
> > --- subversion/clients/cmdline/copy-cmd.c (revision 14704)
> > +++ subversion/clients/cmdline/copy-cmd.c (working copy)
> > @@ -43,7 +43,7 @@
> > apr_array_header_t *targets;
> > const char *src_path, *dst_path;
> > svn_boolean_t src_is_url, dst_is_url;
> > - svn_client_commit_info_t *commit_info = NULL;
> > + svn_client_commit_info2_t *commit_info = NULL;
> > svn_error_t *err;
> >
> > SVN_ERR (svn_opt_args_to_target_array2 (&targets, os,
> > @@ -110,9 +110,9 @@
> > SVN_ERR (svn_cl__make_log_msg_baton (&(ctx->log_msg_baton), opt_state,
> > NULL, ctx->config, pool));
> >
> > - err = svn_client_copy (&commit_info, src_path,
> > - &(opt_state->start_revision),
> > - dst_path, ctx, pool);
> > + err = svn_client_copy2 (&commit_info, src_path,
> > + &(opt_state->start_revision),
> > + dst_path, ctx, pool);
> >
> > if (ctx->log_msg_func)
> > SVN_ERR (svn_cl__cleanup_log_msg (ctx->log_msg_baton, err));
> > @@ -120,7 +120,7 @@
> > return err;
> >
> > if (commit_info && ! opt_state->quiet)
> > - SVN_ERR (svn_cl__print_commit_info (commit_info, pool));
> > + SVN_ERR (svn_cl__print_commit_info2 (commit_info, pool));
> >
> > return SVN_NO_ERROR;
> > }
> > Index: subversion/clients/cmdline/util.c
> > ===================================================================
> > --- subversion/clients/cmdline/util.c (revision 14704)
> > +++ subversion/clients/cmdline/util.c (working copy)
> > @@ -60,7 +60,24 @@
> > return SVN_NO_ERROR;
> > }
> >
> > +svn_error_t *
> > +svn_cl__print_commit_info2 (svn_client_commit_info2_t *commit_info,
> > + apr_pool_t *pool)
> > +{
> > + if ((commit_info)
> > + && (SVN_IS_VALID_REVNUM (commit_info->revision)))
> > + SVN_ERR (svn_cmdline_printf (pool, _("\nCommitted revision %ld.\n"),
> > + commit_info->revision));
> >
> > + if ((commit_info)
> > + && (commit_info->post_commit_err))
> > + SVN_ERR (svn_cmdline_printf (pool, _("\nWARNING:\n%s\n"),
> > + commit_info->post_commit_err));
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +
> > svn_error_t *
> > svn_cl__edit_externally (svn_string_t **edited_contents /* UTF-8! */,
> > const char **tmpfile_left /* UTF-8! */,
> > Index: subversion/clients/cmdline/commit-cmd.c
> > ===================================================================
> > --- subversion/clients/cmdline/commit-cmd.c (revision 14704)
> > +++ subversion/clients/cmdline/commit-cmd.c (working copy)
> > @@ -46,7 +46,7 @@
> > const char *base_dir;
> > svn_config_t *cfg;
> > svn_boolean_t no_unlock = FALSE;
> > - svn_client_commit_info_t *commit_info = NULL;
> > + svn_client_commit_info2_t *commit_info = NULL;
> >
> > SVN_ERR (svn_opt_args_to_target_array2 (&targets, os,
> > opt_state->targets, pool));
> > @@ -92,7 +92,7 @@
> >
> > /* Commit. */
> > SVN_ERR (svn_cl__cleanup_log_msg
> > - (ctx->log_msg_baton, svn_client_commit2 (&commit_info,
> > + (ctx->log_msg_baton, svn_client_commit3 (&commit_info,
> > targets,
> > opt_state->nonrecursive
> > ? FALSE : TRUE,
> > @@ -100,7 +100,7 @@
> > ctx,
> > pool)));
> > if (commit_info && ! opt_state->quiet)
> > - SVN_ERR (svn_cl__print_commit_info (commit_info, pool));
> > + SVN_ERR (svn_cl__print_commit_info2 (commit_info, pool));
> >
> > return SVN_NO_ERROR;
> > }
> > Index: subversion/clients/cmdline/delete-cmd.c
> > ===================================================================
> > --- subversion/clients/cmdline/delete-cmd.c (revision 14704)
> > +++ subversion/clients/cmdline/delete-cmd.c (working copy)
> > @@ -41,7 +41,7 @@
> > svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
> > svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
> > apr_array_header_t *targets;
> > - svn_client_commit_info_t *commit_info = NULL;
> > + svn_client_commit_info2_t *commit_info = NULL;
> > svn_error_t *err;
> >
> > SVN_ERR (svn_opt_args_to_target_array2 (&targets, os,
> > @@ -70,7 +70,7 @@
> > NULL, ctx->config, pool));
> > }
> >
> > - err = svn_client_delete (&commit_info, targets, opt_state->force, ctx, pool);
> > + err = svn_client_delete2 (&commit_info, targets, opt_state->force, ctx, pool);
> > if (err)
> > err = svn_cl__may_need_force (err);
> >
> > @@ -80,7 +80,7 @@
> > return err;
> >
> > if (commit_info && ! opt_state->quiet)
> > - SVN_ERR (svn_cl__print_commit_info (commit_info, pool));
> > + SVN_ERR (svn_cl__print_commit_info2 (commit_info, pool));
> >
> > return SVN_NO_ERROR;
> > }
> > Index: subversion/clients/cmdline/import-cmd.c
> > ===================================================================
> > --- subversion/clients/cmdline/import-cmd.c (revision 14704)
> > +++ subversion/clients/cmdline/import-cmd.c (working copy)
> > @@ -43,7 +43,7 @@
> > apr_array_header_t *targets;
> > const char *path;
> > const char *url;
> > - svn_client_commit_info_t *commit_info = NULL;
> > + svn_client_commit_info2_t *commit_info = NULL;
> >
> > /* Import takes two arguments, for example
> > *
> > @@ -107,15 +107,15 @@
> > SVN_ERR (svn_cl__make_log_msg_baton (&(ctx->log_msg_baton), opt_state,
> > NULL, ctx->config, pool));
> > SVN_ERR (svn_cl__cleanup_log_msg
> > - (ctx->log_msg_baton, svn_client_import (&commit_info,
> > - path,
> > - url,
> > - opt_state->nonrecursive,
> > - ctx,
> > - pool)));
> > + (ctx->log_msg_baton, svn_client_import2 (&commit_info,
> > + path,
> > + url,
> > + opt_state->nonrecursive,
> > + ctx,
> > + pool)));
> >
> > if (commit_info && ! opt_state->quiet)
> > - SVN_ERR (svn_cl__print_commit_info (commit_info, pool));
> > + SVN_ERR (svn_cl__print_commit_info2 (commit_info, pool));
> >
> > return SVN_NO_ERROR;
> > }
> > Index: subversion/tests/clients/cmdline/commit_tests.py
> > ===================================================================
> > --- subversion/tests/clients/cmdline/commit_tests.py (revision 14704)
> > +++ subversion/tests/clients/cmdline/commit_tests.py (working copy)
> > @@ -898,6 +898,45 @@
> >
> > #----------------------------------------------------------------------
> >
> > +def post_commit_hook_test(sbox):
> > + "post commit hook failure case testing"
> > +
> > + sbox.build()
> > +
> > + # Get paths to the working copy and repository
> > + wc_dir = sbox.wc_dir
> > + repo_dir = sbox.repo_dir
> > +
> > + # Setup the hook configs to echo data back
> > + post_commit_hook = svntest.main.get_post_commit_hook_path (repo_dir)
> > + svntest.main.file_append (post_commit_hook,
> > + """#!/bin/sh
> > + echo "Post-commit Hook says nothing doing on stderr" > /dev/stderr
> > + exit -1
> > + """)
> > + os.chmod (post_commit_hook, 0755)
> > +
> > + # Modify iota just so there is something to commit.
> > + iota_path = os.path.join (wc_dir, "iota")
> > + svntest.main.file_append (iota_path, "lakalakalakalaka")
> > +
> > + # Now, commit and examine the output (we happen to know that the
> > + # filesystem will report an absolute path because that's the way the
> > + # filesystem is created by this test suite.
> > + expected_output = [ "Sending "+ iota_path + "\n",
> > + "Transmitting file data .\n",
> > + "Committed revision 2.\n",
> > + "\n",
> > + "WARNING:\n",
> > + "'post-commit' hook failed with error output:\n",
> > + "Post-commit Hook says nothing doing on stderr\n",
> > + "\n"]
> > +
> > + svntest.actions.run_and_verify_svn (None, expected_output, None,
> > + 'ci', '-m', 'log msg', iota_path)
> > +
> > +#----------------------------------------------------------------------
> > +
> > # Regression test for bug #469, whereby merge() was once reporting
> > # erroneous conflicts due to Ancestor < Target < Source, in terms of
> > # node-rev-id parentage.
> > @@ -1994,6 +2033,7 @@
> > mods_in_schedule_delete,
> > Skip(tab_test, (os.name != 'posix' or sys.platform == 'cygwin')),
> > local_mods_are_not_commits,
> > + post_commit_hook_test,
> > ]
> >
> > if __name__ == '__main__':
> > Index: subversion/libsvn_repos/hooks.c
> > ===================================================================
> > --- subversion/libsvn_repos/hooks.c (revision 14704)
> > +++ subversion/libsvn_repos/hooks.c (working copy)
> > @@ -297,7 +297,7 @@
> > args[2] = apr_psprintf (pool, "%ld", rev);
> > args[3] = NULL;
> >
> > - SVN_ERR (run_hook_cmd ("post-commit", hook, args, FALSE, NULL, pool));
> > + SVN_ERR (run_hook_cmd ("post-commit", hook, args, TRUE, NULL, pool));
> > }
> >
> > return SVN_NO_ERROR;
> > Index: subversion/libsvn_repos/commit.c
> > ===================================================================
> > --- subversion/libsvn_repos/commit.c (revision 14704)
> > +++ subversion/libsvn_repos/commit.c (working copy)
> > @@ -93,7 +93,64 @@
> > const char **committed_author;
> > };
> >
> > +struct edit_baton2
> > +{
> > + apr_pool_t *pool;
> >
> > + /** Supplied when the editor is created: **/
> > +
> > + /* The user doing the commit. Presumably, some higher layer has
> > + already authenticated this user. */
> > + const char *user;
> > +
> > + /* Commit message for this commit. */
> > + const char *log_msg;
> > +
> > + /* Callback to run when the commit is done. */
> > + svn_commit_callback2_t callback;
> > + void *callback_baton;
> > +
> > + /* The already-open svn repository to commit to. */
> > + svn_repos_t *repos;
> > +
> > + /* URL to the root of the open repository. */
> > + const char *repos_url;
> > +
> > + /* The filesystem associated with the REPOS above (here for
> > + convenience). */
> > + svn_fs_t *fs;
> > +
> > + /* Location in fs where the edit will begin. */
> > + const char *base_path;
> > +
> > + /* Does this set of interfaces 'own' the commit transaction? */
> > + svn_boolean_t txn_owner;
> > +
> > + /* svn transaction associated with this edit (created in
> > + open_root, or supplied by the public API caller). */
> > + svn_fs_txn_t *txn;
> > +
> > + /** Filled in during open_root: **/
> > +
> > + /* The name of the transaction. */
> > + const char *txn_name;
> > +
> > + /* The object representing the root directory of the svn txn. */
> > + svn_fs_root_t *txn_root;
> > +
> > + /** Filled in when the edit is closed: **/
> > +
> > + /* The new revision created by this commit. */
> > + svn_revnum_t *new_rev;
> > +
> > + /* The date (according to the repository) of this commit. */
> > + const char **committed_date;
> > +
> > + /* The author (also according to the repository) of this commit. */
> > + const char **committed_author;
> > +};
> > +
> > +
> > struct dir_baton
> > {
> > struct edit_baton *edit_baton;
> > @@ -552,10 +609,11 @@
> > close_edit (void *edit_baton,
> > apr_pool_t *pool)
> > {
> > - struct edit_baton *eb = edit_baton;
> > + struct edit_baton2 *eb = edit_baton;
> > svn_revnum_t new_revision = SVN_INVALID_REVNUM;
> > svn_error_t *err;
> > const char *conflict;
> > + char *post_commit_err = NULL ;
> >
> > /* Commit. */
> > err = svn_repos_fs_commit_txn (&conflict, eb->repos,
> > @@ -587,16 +645,14 @@
> > }
> > else if (err)
> > {
> > - /* ### TODO: ra_local is the only RA layer that currently
> > - understands SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED. And as of
> > - at least r12960, svn_repos_fs_commit_txn() would never return
> > - that anyway. If someone who knows ra_svn better can add
> > - handling for this special case, this whole "else if" block
> > - can go away (again). */
> > + /* retrieve the post-commit hook stderr */
> > + if (err->child->message)
> > + post_commit_err = apr_pstrdup (pool, err->child->message) ;
> > +
> > svn_error_clear(err);
> > err = SVN_NO_ERROR;
> > }
> > -
> > +
> > /* Pass new revision information to the caller's callback. */
> > {
> > svn_string_t *date, *author;
> > @@ -618,6 +674,7 @@
> > err2 = (*eb->callback) (new_revision,
> > date ? date->data : NULL,
> > author ? author->data : NULL,
> > + post_commit_err,
> > eb->callback_baton);
> > if (err2)
> > {
> > @@ -692,6 +749,56 @@
> >
> > *edit_baton = eb;
> > *editor = e;
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > +svn_error_t *
> > +svn_repos_get_commit_editor3 (const svn_delta_editor_t **editor,
> > + void **edit_baton,
> > + svn_repos_t *repos,
> > + svn_fs_txn_t *txn,
> > + const char *repos_url,
> > + const char *base_path,
> > + const char *user,
> > + const char *log_msg,
> > + svn_commit_callback2_t callback,
> > + void *callback_baton,
> > + apr_pool_t *pool)
> > +{
> > + svn_delta_editor_t *e = svn_delta_default_editor (pool);
> > + apr_pool_t *subpool = svn_pool_create (pool);
> > + struct edit_baton2 *eb = apr_pcalloc (subpool, sizeof (*eb));
> > +
> > + /* Set up the editor. */
> > + e->open_root = open_root;
> > + e->delete_entry = delete_entry;
> > + e->add_directory = add_directory;
> > + e->open_directory = open_directory;
> > + e->change_dir_prop = change_dir_prop;
> > + e->add_file = add_file;
> > + e->open_file = open_file;
> > + e->close_file = close_file;
> > + e->apply_textdelta = apply_textdelta;
> > + e->change_file_prop = change_file_prop;
> > + e->close_edit = close_edit;
> > + e->abort_edit = abort_edit;
> > +
> > + /* Set up the edit baton. */
> > + eb->pool = subpool;
> > + eb->user = user ? apr_pstrdup (subpool, user) : NULL;
> > + eb->log_msg = apr_pstrdup (subpool, log_msg);
> > + eb->callback = callback;
> > + eb->callback_baton = callback_baton;
> > + eb->base_path = apr_pstrdup (subpool, base_path);
> > + eb->repos = repos;
> > + eb->repos_url = repos_url;
> > + eb->fs = svn_repos_fs (repos);
> > + eb->txn = txn;
> > + eb->txn_owner = txn ? FALSE : TRUE;
> > +
> > + *edit_baton = eb;
> > + *editor = e;
> >
> > return SVN_NO_ERROR;
> > }
> > Index: subversion/libsvn_ra_svn/client.c
> > ===================================================================
> > --- subversion/libsvn_ra_svn/client.c (revision 14704)
> > +++ subversion/libsvn_ra_svn/client.c (working copy)
> > @@ -56,7 +56,7 @@
> > ra_svn_session_baton_t *sess_baton;
> > apr_pool_t *pool;
> > svn_revnum_t *new_rev;
> > - svn_commit_callback_t callback;
> > + svn_commit_callback2_t callback;
> > void *callback_baton;
> > } ra_svn_commit_callback_baton_t;
> >
> > @@ -785,15 +785,18 @@
> > svn_revnum_t new_rev;
> > const char *committed_date;
> > const char *committed_author;
> > + const char *post_commit_err;
> >
> > SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
> > - SVN_ERR(svn_ra_svn_read_tuple(ccb->sess_baton->conn, ccb->pool, "r(?c)(?c)",
> > + SVN_ERR(svn_ra_svn_read_tuple(ccb->sess_baton->conn, ccb->pool,
> > + "r(?c)(?c)?(?c)",
> > &new_rev,
> > &committed_date,
> > - &committed_author));
> > + &committed_author,
> > + &post_commit_err));
> >
> > return ccb->callback(new_rev, committed_date, committed_author,
> > - ccb->callback_baton);
> > + post_commit_err, ccb->callback_baton);
> >
> > }
> >
> > @@ -801,7 +804,7 @@
> > const svn_delta_editor_t **editor,
> > void **edit_baton,
> > const char *log_msg,
> > - svn_commit_callback_t callback,
> > + svn_commit_callback2_t callback,
> > void *callback_baton,
> > apr_hash_t *lock_tokens,
> > svn_boolean_t keep_locks,
> > Index: subversion/libsvn_ra_dav/merge.c
> > ===================================================================
> > --- subversion/libsvn_ra_dav/merge.c (revision 14704)
> > +++ subversion/libsvn_ra_dav/merge.c (working copy)
> > @@ -58,6 +58,8 @@
> > { "DAV:", "collection", ELEM_collection, 0 },
> > { "DAV:", "baseline", ELEM_baseline, 0 },
> > { "DAV:", "version-name", ELEM_version_name, SVN_RA_DAV__XML_CDATA },
> > + { SVN_XML_NAMESPACE, "post-commit-err",
> > + ELEM_post_commit_err, SVN_RA_DAV__XML_CDATA },
> > { "DAV:", "creationdate", ELEM_creationdate, SVN_RA_DAV__XML_CDATA },
> > { "DAV:", "creator-displayname", ELEM_creator_displayname,
> > SVN_RA_DAV__XML_CDATA },
> > @@ -104,6 +106,8 @@
> > svn_stringbuf_t *committed_date; /* DAV:creationdate for this resource */
> > svn_stringbuf_t *last_author; /* DAV:creator-displayname for this
> > resource */
> > + svn_stringbuf_t *post_commit_err;/* SVN_XML_NAMESPACE:post-commit hook's
> > + stderr */
> >
> > /* We only invoke set_prop() on targets listed in valid_targets.
> > Some entities (such as directories that have had changes
> > @@ -341,6 +345,7 @@
> > || child == ELEM_version_name
> > || child == ELEM_creationdate
> > || child == ELEM_creator_displayname
> > + || child == ELEM_post_commit_err
> > /* other props */)
> > return SVN_RA_DAV__XML_VALID;
> > else
> > @@ -524,6 +529,10 @@
> > svn_stringbuf_set(mc->vsn_name, cdata);
> > break;
> >
> > + case ELEM_post_commit_err:
> > + svn_stringbuf_set(mc->post_commit_err, cdata);
> > + break;
> > +
> > case ELEM_creationdate:
> > svn_stringbuf_set(mc->committed_date, cdata);
> > break;
> > @@ -636,11 +645,39 @@
> > }
> >
> >
> > +svn_error_t * svn_ra_dav__merge_activity (
> > + svn_revnum_t *new_rev,
> > + const char **committed_date,
> > + const char **committed_author,
> > + svn_ra_dav__session_t *ras,
> > + const char *repos_url,
> > + const char *activity_url,
> > + apr_hash_t *valid_targets,
> > + apr_hash_t *lock_tokens,
> > + svn_boolean_t keep_locks,
> > + svn_boolean_t disable_merge_response,
> > + apr_pool_t *pool)
> > +{
> > + return svn_ra_dav__merge_activity2 (new_rev,
> > + committed_date,
> > + committed_author,
> > + NULL,
> > + ras,
> > + repos_url,
> > + activity_url,
> > + valid_targets,
> > + lock_tokens,
> > + keep_locks,
> > + disable_merge_response,
> > + pool);
> >
> > -svn_error_t * svn_ra_dav__merge_activity(
> > +}
> > +
> > +svn_error_t * svn_ra_dav__merge_activity2 (
> > svn_revnum_t *new_rev,
> > const char **committed_date,
> > const char **committed_author,
> > + const char **post_commit_err,
> > svn_ra_dav__session_t *ras,
> > const char *repos_url,
> > const char *activity_url,
> > @@ -670,6 +707,8 @@
> > mc.vsn_url = MAKE_BUFFER(pool);
> > mc.committed_date = MAKE_BUFFER(pool);
> > mc.last_author = MAKE_BUFFER(pool);
> > + if (post_commit_err)
> > + mc.post_commit_err = MAKE_BUFFER(pool);
> >
> > if (disable_merge_response
> > || (! keep_locks))
> > @@ -727,6 +766,10 @@
> > *committed_author = mc.last_author->len
> > ? apr_pstrdup(pool, mc.last_author->data) : NULL;
> >
> > + if (post_commit_err)
> > + *post_commit_err = mc.post_commit_err->len
> > + ? apr_pstrdup(pool, mc.post_commit_err->data) : NULL;
> > +
> > svn_pool_destroy(mc.scratchpool);
> >
> > return NULL;
> > Index: subversion/libsvn_ra_dav/ra_dav.h
> > ===================================================================
> > --- subversion/libsvn_ra_dav/ra_dav.h (revision 14704)
> > +++ subversion/libsvn_ra_dav/ra_dav.h (working copy)
> > @@ -223,6 +223,19 @@
> > svn_boolean_t keep_locks,
> > apr_pool_t *pool);
> >
> > +/** since v1.3
> > + */
> > +svn_error_t * svn_ra_dav__get_commit_editor2(
> > + svn_ra_session_t *session,
> > + const svn_delta_editor_t **editor,
> > + void **edit_baton,
> > + const char *log_msg,
> > + svn_commit_callback2_t callback,
> > + void *callback_baton,
> > + apr_hash_t *lock_tokens,
> > + svn_boolean_t keep_locks,
> > + apr_pool_t *pool);
> > +
> > svn_error_t * svn_ra_dav__get_file(
> > svn_ra_session_t *session,
> > const char *path,
> > @@ -654,6 +667,7 @@
> > ELEM_updated_set,
> > ELEM_vcc,
> > ELEM_version_name,
> > + ELEM_post_commit_err,
> > ELEM_error,
> >
> > /* SVN elements */
> > @@ -716,6 +730,22 @@
> > svn_boolean_t keep_locks,
> > svn_boolean_t disable_merge_response,
> > apr_pool_t *pool);
> > +/** since v1.3
> > + * parameter added for post-commit hook's stderr
> > + */
> > +svn_error_t * svn_ra_dav__merge_activity2(
> > + svn_revnum_t *new_rev,
> > + const char **committed_date,
> > + const char **committed_author,
> > + const char **post_commit_err,
> > + svn_ra_dav__session_t *ras,
> > + const char *repos_url,
> > + const char *activity_url,
> > + apr_hash_t *valid_targets,
> > + apr_hash_t *lock_tokens,
> > + svn_boolean_t keep_locks,
> > + svn_boolean_t disable_merge_response,
> > + apr_pool_t *pool);
> >
> >
> > /* Make a buffer for repeated use with svn_stringbuf_set().
> > Index: subversion/libsvn_ra_dav/session.c
> > ===================================================================
> > --- subversion/libsvn_ra_dav/session.c (revision 14704)
> > +++ subversion/libsvn_ra_dav/session.c (working copy)
> > @@ -1432,7 +1432,7 @@
> > svn_ra_dav__change_rev_prop,
> > svn_ra_dav__rev_proplist,
> > svn_ra_dav__rev_prop,
> > - svn_ra_dav__get_commit_editor,
> > + svn_ra_dav__get_commit_editor2,
> > svn_ra_dav__get_file,
> > svn_ra_dav__get_dir,
> > svn_ra_dav__do_update,
> > Index: subversion/libsvn_ra_dav/commit.c
> > ===================================================================
> > --- subversion/libsvn_ra_dav/commit.c (revision 14704)
> > +++ subversion/libsvn_ra_dav/commit.c (working copy)
> > @@ -114,7 +114,7 @@
> > const char *log_msg;
> >
> > /* The commit callback and baton */
> > - svn_commit_callback_t callback;
> > + svn_commit_callback2_t callback;
> > void *callback_baton;
> >
> > /* The hash of lock-tokens owned by the working copy. */
> > @@ -225,7 +225,6 @@
> > NULL, 204 /* No Content */, 404 /* Not Found */, pool);
> > }
> >
> > -
> > /* Get the version resource URL for RSRC, storing it in
> > RSRC->vsn_url. Use POOL for all temporary allocations. */
> > static svn_error_t * get_version_url(commit_ctx_t *cc,
> > @@ -1416,6 +1415,37 @@
> > }
> >
> >
> > +static svn_error_t * commit_close_edit2(void *edit_baton,
> > + apr_pool_t *pool)
> > +{
> > + commit_ctx_t *cc = edit_baton;
> > + svn_revnum_t new_rev;
> > + const char *committed_date;
> > + const char *committed_author;
> > + const char *post_commit_err;
> > +
> > + SVN_ERR( svn_ra_dav__merge_activity2(&new_rev,
> > + &committed_date,
> > + &committed_author,
> > + &post_commit_err,
> > + cc->ras,
> > + cc->ras->root.path,
> > + cc->activity_url,
> > + cc->valid_targets,
> > + cc->tokens,
> > + cc->keep_locks,
> > + cc->disable_merge_response,
> > + pool) );
> > + SVN_ERR( delete_activity(edit_baton, pool) );
> > + SVN_ERR( svn_ra_dav__maybe_store_auth_info(cc->ras) );
> > +
> > + if (new_rev != SVN_INVALID_REVNUM)
> > + SVN_ERR( cc->callback (new_rev, committed_date, committed_author,
> > + post_commit_err, cc->callback_baton));
> > +
> > + return SVN_NO_ERROR;
> > +}
> > +
> > static svn_error_t * commit_close_edit(void *edit_baton,
> > apr_pool_t *pool)
> > {
> > @@ -1423,24 +1453,26 @@
> > svn_revnum_t new_rev;
> > const char *committed_date;
> > const char *committed_author;
> > + const char *post_commit_err;
> >
> > - SVN_ERR( svn_ra_dav__merge_activity(&new_rev,
> > - &committed_date,
> > - &committed_author,
> > - cc->ras,
> > - cc->ras->root.path,
> > - cc->activity_url,
> > - cc->valid_targets,
> > - cc->tokens,
> > - cc->keep_locks,
> > - cc->disable_merge_response,
> > - pool) );
> > + SVN_ERR( svn_ra_dav__merge_activity2(&new_rev,
> > + &committed_date,
> > + &committed_author,
> > + &post_commit_err,
> > + cc->ras,
> > + cc->ras->root.path,
> > + cc->activity_url,
> > + cc->valid_targets,
> > + cc->tokens,
> > + cc->keep_locks,
> > + cc->disable_merge_response,
> > + pool) );
> > SVN_ERR( delete_activity(edit_baton, pool) );
> > SVN_ERR( svn_ra_dav__maybe_store_auth_info(cc->ras) );
> >
> > if (new_rev != SVN_INVALID_REVNUM)
> > SVN_ERR( cc->callback (new_rev, committed_date, committed_author,
> > - cc->callback_baton));
> > + post_commit_err, cc->callback_baton));
> >
> > return SVN_NO_ERROR;
> > }
> > @@ -1529,6 +1561,93 @@
> > return SVN_NO_ERROR;
> > }
> >
> > +svn_error_t * svn_ra_dav__get_commit_editor2(svn_ra_session_t *session,
> > + const svn_delta_editor_t **editor,
> > + void **edit_baton,
> > + const char *log_msg,
> > + svn_commit_callback2_t callback,
> > + void *callback_baton,
> > + apr_hash_t *lock_tokens,
> > + svn_boolean_t keep_locks,
> > + apr_pool_t *pool)
> > +{
> > + svn_ra_dav__session_t *ras = session->priv;
> > + svn_delta_editor_t *commit_editor;
> > + commit_ctx_t *cc;
> > + struct copy_baton *cb;
> > +
> > + /* Build a copy_baton for COPY requests. */
> > + cb = apr_pcalloc(pool, sizeof(*cb));
> > + cb->pool = pool;
> > +
> > + /* Build the main commit editor's baton. */
> > + cc = apr_pcalloc(pool, sizeof(*cc));
> > + cc->ras = ras;
> > + cc->valid_targets = apr_hash_make(pool);
> > + cc->get_func = ras->callbacks->get_wc_prop;
> > + cc->push_func = ras->callbacks->push_wc_prop;
> > + cc->cb_baton = ras->callback_baton;
> > + cc->log_msg = log_msg;
> > + cc->callback = callback;
> > + cc->callback_baton = callback_baton;
> > + cc->tokens = lock_tokens;
> > + cc->keep_locks = keep_locks;
> > + cc->cb = cb;
> > +
> > + /* If the caller didn't give us any way of storing wcprops, then
> > + there's no point in getting back a MERGE response full of VR's. */
> > + if (ras->callbacks->push_wc_prop == NULL)
> > + cc->disable_merge_response = TRUE;
> > +
> > + /* ### should we perform an OPTIONS to validate the server we're about
> > + ### to talk to? */
> > +
> > + /*
> > + ** Create an Activity. This corresponds directly to an FS transaction.
> > + ** We will check out all further resources within the context of this
> > + ** activity.
> > + */
> > + SVN_ERR( create_activity(cc, pool) );
> > +
> > + /*
> > + ** Find the latest baseline resource, check it out, and then apply the
> > + ** log message onto the thing.
> > + */
> > + SVN_ERR( apply_log_message(cc, log_msg, pool) );
> > +
> > + /* Register request hooks in the neon session. They specifically
> > + allow any COPY requests (ne_copy()) to parse <D:error>
> > + responses. They're no-ops for other requests. */
> > + ne_hook_create_request(ras->sess, create_request_hook, cb);
> > + ne_hook_pre_send(ras->sess, pre_send_hook, cb);
> > +
> > + /*
> > + ** Set up the editor.
> > + **
> > + ** This structure is used during the commit process. An external caller
> > + ** uses these callbacks to describe all the changes in the working copy
> > + ** that must be committed to the server.
> > + */
> > + commit_editor = svn_delta_default_editor(pool);
> > + commit_editor->open_root = commit_open_root;
> > + commit_editor->delete_entry = commit_delete_entry;
> > + commit_editor->add_directory = commit_add_dir;
> > + commit_editor->open_directory = commit_open_dir;
> > + commit_editor->change_dir_prop = commit_change_dir_prop;
> > + commit_editor->close_directory = commit_close_dir;
> > + commit_editor->add_file = commit_add_file;
> > + commit_editor->open_file = commit_open_file;
> > + commit_editor->apply_textdelta = commit_apply_txdelta;
> > + commit_editor->change_file_prop = commit_change_file_prop;
> > + commit_editor->close_file = commit_close_file;
> > + commit_editor->close_edit = commit_close_edit2;
> > + commit_editor->abort_edit = commit_abort_edit;
> > +
> > + *editor = commit_editor;
> > + *edit_baton = cc;
> > + return SVN_NO_ERROR;
> > +}
> > +
> > svn_error_t * svn_ra_dav__get_commit_editor(svn_ra_session_t *session,
> > const svn_delta_editor_t **editor,
> > void **edit_baton,
> > Index: subversion/svnserve/serve.c
> > ===================================================================
> > --- subversion/svnserve/serve.c (revision 14704)
> > +++ subversion/svnserve/serve.c (working copy)
> > @@ -62,6 +62,7 @@
> > svn_revnum_t *new_rev;
> > const char **date;
> > const char **author;
> > + const char **post_commit_err;
> > } commit_callback_baton_t;
> >
> > typedef struct {
> > @@ -622,13 +623,15 @@
> > }
> >
> > static svn_error_t *commit_done(svn_revnum_t new_rev, const char *date,
> > - const char *author, void *baton)
> > + const char *author, const char *post_commit_err,
> > + void *baton)
> > {
> > commit_callback_baton_t *ccb = baton;
> >
> > *ccb->new_rev = new_rev;
> > *ccb->date = date;
> > *ccb->author = author;
> > + *ccb->post_commit_err = post_commit_err;
> > return SVN_NO_ERROR;
> > }
> >
> > @@ -722,7 +725,7 @@
> > apr_array_header_t *params, void *baton)
> > {
> > server_baton_t *b = baton;
> > - const char *log_msg, *date, *author;
> > + const char *log_msg, *date, *author, *post_commit_err;
> > apr_array_header_t *lock_tokens;
> > svn_boolean_t keep_locks;
> > const svn_delta_editor_t *editor;
> > @@ -751,8 +754,9 @@
> > ccb.new_rev = &new_rev;
> > ccb.date = &date;
> > ccb.author = &author;
> > + ccb.post_commit_err = &post_commit_err;
> > /* ### Note that svn_repos_get_commit_editor actually wants a decoded URL. */
> > - SVN_CMD_ERR(svn_repos_get_commit_editor2
> > + SVN_CMD_ERR(svn_repos_get_commit_editor3
> > (&editor, &edit_baton, b->repos, NULL,
> > svn_path_uri_decode(b->repos_url, pool),
> > b->fs_path, b->user,
> > @@ -775,8 +779,8 @@
> > if (! keep_locks && lock_tokens)
> > SVN_ERR(unlock_paths(lock_tokens, b, pool));
> >
> > - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)",
> > - new_rev, date, author));
> > + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)(?c)",
> > + new_rev, date, author, post_commit_err));
> >
> > if (! b->tunnel)
> > SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
> > For additional commands, e-mail: dev-help@subversion.tigris.org

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Tue May 17 08:10:30 2005

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