[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: <kfogel_at_collab.net>
Date: 2005-05-16 17:57:14 CEST

"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... :-)

-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 Mon May 16 18:35:20 2005

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