VK Sameer <sameer@collab.net> writes:
> Patch for issue #2264 - svn client and server enhancement to send multiple
> locks over ra_svn. Test #22 in lock_tests.py covers this scenario.
>
> * subversion/libsvn_ra_svn/ra_svn.h
> (svn_ra_svn__handle_failure_status): New function pulled out of
> svn_ra_svn_read_cmd_response to allow lower-level handling of
> cmd response.
> * subversion/libsvn_ra_svn/marshal.c
> (svn_ra_svn__handle_failure_status): New function.
> (svn_ra_svn_read_cmd_response): Refactored to call
> svn_ra_svn__handle_failure_status.
> * subversion/libsvn_ra_svn/client.c:
> (ra_svn_vtable): Function pointers for "lock" and "unlock" changed to
> ra_svn_lock_many and ra_svn_unlock_many, respectively.
I think you meant just "lock_many" and "unlock_many" :-). They don't
have "ra_svn_" prefixes.
> (ra_svn_lock_compat): Old ra_svn_lock, fallback for 1.2.x servers.
> (ra_svn_unlock_compat): Old ra_svn_unlock, fallback for 1.2.x servers.
> (ra_svn_lock): Modified to implement "lock-many" verb.
> (ra_svn_unlock): Modified to implement "unlock-many" verb
> * subversion/libsvn_ra_svn/protocol:
> Added grammar for "lock-many", "unlock-many"
> * subversion/libsvn_ra_svn/serve.c:
> (main_commands): Added "lock-many", "unlock-many" verbs and corresponding
> function pointers.
> (lock_many): New function to implement "lock-many" verb.
> (unlock_many): New function to implement "unlock-many" verb.
Other than the typo above, this log message looks great to me! Of
course, at this point I'm already very familiar with the problem and
with the previous patches. But still, one reading of this log message
was enough to totally comprehend the change -- it really prepared my
mind for the patch.
I recommend putting a blank line before each new "* file..." block,
but that's a minor nit.
> Index: subversion/libsvn_ra_svn/client.c
> ===================================================================
> --- subversion/libsvn_ra_svn/client.c (revision 15260)
> +++ subversion/libsvn_ra_svn/client.c (working copy)
> @@ -1396,13 +1396,17 @@
> return SVN_NO_ERROR;
> }
>
> -static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
> - apr_hash_t *path_revs,
> - const char *comment,
> - svn_boolean_t force,
> - svn_ra_lock_callback_t lock_func,
> - void *lock_baton,
> - apr_pool_t *pool)
> +/* For each path in @a path_revs, send a 'lock' command to the server.
> + Used with 1.2.x series servers which support locking, but of only
> + one path at a time. ra_svn_lock(), which supports 'lock-many'
> + is now the default. See svn_ra_lock() docstring for interface details. */
> +static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
> + apr_hash_t *path_revs,
> + const char *comment,
> + svn_boolean_t force,
> + svn_ra_lock_callback_t lock_func,
> + void *lock_baton,
> + apr_pool_t *pool)
Beautiful!
I suppose people might get confused and think we meant "ra_svn_lock()"
where it says "svn_ra_lock()", but if they look into it they'll soon
discover that the text is accurate.
> {
> ra_svn_session_baton_t *sess = session->priv;
> svn_ra_svn_conn_t* conn = sess->conn;
> @@ -1410,8 +1414,6 @@
> apr_hash_index_t *hi;
> apr_pool_t *iterpool = svn_pool_create (pool);
>
> - /* ### TODO for 1.3: Send all the locks over the wire at once. This
> - loop is just a temporary shim. */
> for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
> {
> svn_lock_t *lock;
> @@ -1459,20 +1461,22 @@
> return SVN_NO_ERROR;
> }
>
> -static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
> - apr_hash_t *path_tokens,
> - svn_boolean_t force,
> - svn_ra_lock_callback_t lock_func,
> - void *lock_baton,
> - apr_pool_t *pool)
> +/* For each path in @path_tokens, send an 'unlock' command to the server.
> + Used with 1.2.x series servers which support unlocking, but of only
> + one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
> + now the default. See svn_ra_unlock() docstring for interface details. */
> +static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
> + apr_hash_t *path_tokens,
> + svn_boolean_t force,
> + svn_ra_lock_callback_t lock_func,
> + void *lock_baton,
> + apr_pool_t *pool)
Likewise beautiful.
> {
> ra_svn_session_baton_t *sess = session->priv;
> svn_ra_svn_conn_t* conn = sess->conn;
> apr_hash_index_t *hi;
> apr_pool_t *iterpool = svn_pool_create (pool);
>
> - /* ### TODO for 1.3: Send all the lock tokens over the wire at once.
> - This loop is just a temporary shim. */
> for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
> {
> const void *key;
> @@ -1517,6 +1521,183 @@
> return SVN_NO_ERROR;
> }
>
> +/* Send a 'lock-many' command to the server to lock all paths in
> + @a path_revs. See svn_ra_lock() docstring for interface details. */
> +static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
> + apr_hash_t *path_revs,
> + const char *comment,
> + svn_boolean_t force,
> + svn_ra_lock_callback_t lock_func,
> + void *lock_baton,
> + apr_pool_t *pool)
This doc string is slightly inaccurate, because this function might
actually send 'lock' instead of 'lock-many', in the fallback case.
(Yes, I consider it to be this function sending the command, even
though it calls another function to do so, because the impetus comes
from this function -- i.e., calling this function can cause the action
to happen, so it's part of this function's API).
Anyway, this can all be avoided by simply not going into
implementation details. Say instead
/* Tell the server to lock all paths in @a path_revs. See the
svn_ra_lock() doc string for interface details. */
...or something like that.
> +{
> + ra_svn_session_baton_t *sess = session->priv;
> + svn_ra_svn_conn_t* conn = sess->conn;
> + apr_array_header_t *list;
> + apr_hash_index_t *hi;
> + int i;
> + svn_ra_svn_item_t *elt;
> + svn_error_t *err, *callback_err = NULL;
> + apr_pool_t *subpool = svn_pool_create (pool);
> + const char *status;
> + apr_array_header_t *condensed_tgt_paths;
> + condensed_tgt_paths = apr_array_make(pool, 1, sizeof(const char*));
> +
> + /* (lock-many (?c) b ( (c(?r)) ...) ) */
> + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)b(!", "lock-many",
> + comment, force));
> +
> + for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
> + {
> + const void *key;
> + const char *path;
> + void *val;
> + svn_revnum_t *revnum;
> +
> + svn_pool_clear (subpool);
> + apr_hash_this(hi, &key, NULL, &val);
> + path = key;
> + APR_ARRAY_PUSH(condensed_tgt_paths, const char*) = path;
> + revnum = val;
> +
> + SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "c(?r)",
> + path, *revnum));
> + }
> +
> + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
> +
> + err = handle_auth_request(sess, pool);
> +
> + /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
> + * to 'lock'. */
> + if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
> + return ra_svn_lock_compat(session, path_revs, comment, force, lock_func,
> + lock_baton, pool);
> +
> + /* Unknown error */
> + if (err)
> + return err;
> +
> + /* svn_ra_svn_read_cmd_response() is unusable as it parses the params,
> + * instead of returning a list over which to iterate. This means
> + * failure status must be handled explicitly. */
> + err = svn_ra_svn_read_tuple(conn, pool, "wl", &status, &list);
> + if (strcmp (status, "failure") == 0)
> + return svn_ra_svn__handle_failure_status(list, pool);
> +
> + if (err && !SVN_ERR_IS_LOCK_ERROR(err))
> + return err;
> +
> + for (i = 0; i < list->nelts; ++i)
> + {
> + svn_lock_t *lock;
> + const char *condensed_tgt_path;
> +
> + svn_pool_clear(subpool);
> + condensed_tgt_path = APR_ARRAY_IDX(condensed_tgt_paths, i, const char *);
> + elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
> +
> + if (elt->kind != SVN_RA_SVN_LIST)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Lock element not a list"));
> +
> + err = parse_lock(elt->u.list, subpool, &lock);
> + if (err)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, err,
> + _("unable to parse lock data"));
> +
I think we usually start error strings with a capital letter.
> + if (lock_func)
> + callback_err = lock_func(lock_baton, condensed_tgt_path, TRUE,
> + err ? NULL : lock,
> + err, subpool);
> +
> + if (callback_err)
> + return callback_err;
> + }
> +
> + svn_pool_destroy(subpool);
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* Send an 'unlock-many' command to the server to unlock all paths in
> + @a path_tokens. See svn_ra_unlock() docstring for interface details. */
> +static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
> + apr_hash_t *path_tokens,
> + svn_boolean_t force,
> + svn_ra_lock_callback_t lock_func,
> + void *lock_baton,
> + apr_pool_t *pool)
Same comment about how going into implementation details led to a
minor inaccuracy in the doc string.
> +{
> + ra_svn_session_baton_t *sess = session->priv;
> + svn_ra_svn_conn_t* conn = sess->conn;
> + apr_hash_index_t *hi;
> + apr_pool_t *subpool = svn_pool_create(pool);
> + svn_error_t *err, *callback_err = NULL;
> +
> + /* (unlock-many (b ( (c(?c)) ...) ) ) */
> + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(b(!", "unlock-many", force));
> +
> + for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
> + {
> + const void *key;
> + const char *path;
> + void *val;
> + const char *token;
> +
> + svn_pool_clear(subpool);
> + apr_hash_this(hi, &key, NULL, &val);
> + path = key;
> +
> + if (strcmp (val, "") != 0)
> + token = val;
> + else
> + token = NULL;
> +
> + SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "c(?c)", path,token));
> + }
> +
> + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
> +
> + err = handle_auth_request(sess, pool);
> +
> + /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
> + * to 'unlock'.
> + */
> + if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
> + return ra_svn_unlock_compat(session, path_tokens, force, lock_func,
> + lock_baton, pool);
> +
> + /* Unknown error */
> + if (err)
> + return err;
> +
> + err = svn_ra_svn_read_cmd_response(conn, pool, "");
> +
> + if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
> + return err;
> +
> + for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
> + {
> + const void *key;
> + const char *path;
> +
> + svn_pool_clear(subpool);
> + apr_hash_this(hi, &key, NULL, NULL);
> + path = key;
> +
> + if (lock_func)
> + callback_err = lock_func(lock_baton, path, FALSE, NULL, err, subpool);
> +
> + if (callback_err)
> + return callback_err;
> + }
> +
> + svn_pool_destroy(subpool);
> +
> + return SVN_NO_ERROR;
> +}
Looks great.
> static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
> svn_lock_t **lock,
> const char *path,
> Index: subversion/libsvn_ra_svn/protocol
> ===================================================================
> --- subversion/libsvn_ra_svn/protocol (revision 15260)
> +++ subversion/libsvn_ra_svn/protocol (working copy)
> @@ -333,10 +333,19 @@
> current-rev:number ] )
> response: ( lock:lockdesc )
>
> + lock-many
> + params: ( [ comment:string ] force:bool ( (path:string [
> + current-rev:number ] ) ... ) )
> + response: ( ( lock:lockdesc ) ... )
> +
> unlock
> params: ( path:string [ token:string ] force:bool )
> response: ( )
>
> + unlock-many
> + params: ( force:bool ( ( path:string [ token:string ] ) ... ) )
> + response: ( )
> +
> get-lock
> params: ( path:string )
> response: ( [ lock:lockdesc ] )
> Index: subversion/libsvn_ra_svn/marshal.c
> ===================================================================
> --- subversion/libsvn_ra_svn/marshal.c (revision 15260)
> +++ subversion/libsvn_ra_svn/marshal.c (working copy)
> @@ -741,17 +741,51 @@
>
> /* --- READING AND WRITING COMMANDS AND RESPONSES --- */
>
> +svn_error_t *svn_ra_svn__handle_failure_status(apr_array_header_t *params,
> + apr_pool_t *pool)
> +{
> + const char *message, *file;
> + svn_error_t *err = NULL;
> + svn_ra_svn_item_t *elt;
> + int i;
> + apr_uint64_t apr_err, line;
> + apr_pool_t *subpool = svn_pool_create(pool);
> +
> + if (params->nelts == 0)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Empty error list"));
> +
> + /* Rebuild the error list from the end, to avoid reversing the order. */
> + for (i = params->nelts - 1; i >= 0; i--)
> + {
> + svn_pool_clear(subpool);
> + elt = &((svn_ra_svn_item_t *) params->elts)[i];
> + if (elt->kind != SVN_RA_SVN_LIST)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Malformed error list"));
> + SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, subpool, "nccn", &apr_err,
> + &message, &file, &line));
> + /* The message field should have been optional, but we can't
> + easily change that, so "" means a nonexistent message. */
> + if (!*message)
> + message = NULL;
> + err = svn_error_create(apr_err, err, message);
> + err->file = apr_pstrdup(err->pool, file);
> + err->line = line;
> + }
> +
> + svn_pool_destroy(subpool);
> + return err;
> +}
Nice.
> svn_error_t *svn_ra_svn_read_cmd_response(svn_ra_svn_conn_t *conn,
> apr_pool_t *pool,
> const char *fmt, ...)
> {
> va_list ap;
> - const char *status, *message, *file;
> + const char *status;
> apr_array_header_t *params;
> svn_error_t *err;
> - svn_ra_svn_item_t *elt;
> - int i;
> - apr_uint64_t apr_err, line;
>
> SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "wl", &status, ¶ms));
> if (strcmp(status, "success") == 0)
> @@ -763,28 +797,7 @@
> }
> else if (strcmp(status, "failure") == 0)
> {
> - /* Rebuild the error list from the end, to avoid reversing the order. */
> - if (params->nelts == 0)
> - return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> - _("Empty error list"));
> - err = NULL;
> - for (i = params->nelts - 1; i >= 0; i--)
> - {
> - elt = &((svn_ra_svn_item_t *) params->elts)[i];
> - if (elt->kind != SVN_RA_SVN_LIST)
> - return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> - _("Malformed error list"));
> - SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "nccn", &apr_err,
> - &message, &file, &line));
> - /* The message field should have been optional, but we can't
> - easily change that, so "" means a nonexistent message. */
> - if (!*message)
> - message = NULL;
> - err = svn_error_create(apr_err, err, message);
> - err->file = apr_pstrdup(err->pool, file);
> - err->line = line;
> - }
> - return err;
> + return svn_ra_svn__handle_failure_status (params, pool);
> }
>
> return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> Index: subversion/libsvn_ra_svn/ra_svn.h
> ===================================================================
> --- subversion/libsvn_ra_svn/ra_svn.h (revision 15260)
> +++ subversion/libsvn_ra_svn/ra_svn.h (working copy)
> @@ -87,6 +87,13 @@
> const char *user, const char *password,
> const char **message);
>
> +/* Return an error chain based on @a params (which contains a
> + * command response indicating failure). The error chain will be
> + * in the same order as the errors indicated in @a params. Use
> + * @a pool for temporary allocations. */
> +svn_error_t *svn_ra_svn__handle_failure_status(apr_array_header_t *params,
> + apr_pool_t *pool);
> +
> #ifdef __cplusplus
> }
> #endif /* __cplusplus */
> Index: subversion/svnserve/serve.c
> ===================================================================
> --- subversion/svnserve/serve.c (revision 15260)
> +++ subversion/svnserve/serve.c (working copy)
> @@ -1407,6 +1407,82 @@
> return SVN_NO_ERROR;
> }
>
> +static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
> + apr_array_header_t *params, void *baton)
> +{
> + server_baton_t *b = baton;
> + apr_array_header_t *locks, *lock_cmds;
> + const char *comment;
> + svn_boolean_t force;
> + int i;
> + apr_pool_t *subpool;
> + struct lock_cmd {
> + const char *path;
> + const char *full_path;
> + svn_revnum_t current_rev;
> + svn_lock_t *l;
> + };
> +
> + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?c)bl", &comment, &force,
> + &locks));
> +
> + subpool = svn_pool_create(pool);
It's interesting that in this function, you do not initialize subpool
where you declare it, whereas in other functions you do. I don't
think it matters much one way or the other, just noting the
inconsistency. Why? Hemingway said it best: "Because I am a bastard."
:-)
> + lock_cmds = apr_array_make(pool, locks->nelts, sizeof(struct lock_cmd));
> +
> + /* Loop through the lock commands. */
> + for (i = 0; i < locks->nelts; ++i)
> + {
> + svn_pool_clear (subpool);
> + struct lock_cmd *cmd = apr_pcalloc(pool, sizeof(struct lock_cmd));
> +
> + svn_ra_svn_item_t *item = &APR_ARRAY_IDX(locks, i, svn_ra_svn_item_t);
> +
> + if (item->kind != SVN_RA_SVN_LIST)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + "Lock commands should be list of lists\n");
> +
> + SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "c(?r)",
> + &cmd->path, &cmd->current_rev));
> +
> + cmd->full_path = svn_path_join(b->fs_path,
> + svn_path_canonicalize(cmd->path, subpool),
> + pool);
> +
> + APR_ARRAY_PUSH(lock_cmds, struct lock_cmd) = *cmd;
> + }
> +
> + SVN_ERR(must_have_write_access(conn, pool, b, TRUE));
Technically you could use subpool for this check, but it really
doesn't matter. Maybe it's better the way you have it, to keep
subpool consistently used as a loop pool.
> + /* Loop through each path to be locked. */
> + for (i = 0; i < lock_cmds->nelts; i++)
> + {
> + struct lock_cmd *cmd = &APR_ARRAY_IDX(lock_cmds, i, struct lock_cmd);
> +
> + SVN_CMD_ERR(svn_repos_fs_lock(&cmd->l, b->repos, cmd->full_path,
> + NULL, comment, 0,
> + 0, /* No expiration time. */
> + cmd->current_rev,
> + force, pool));
> + }
> +
> + /* (success( (ccc(?c)c(?c) ... )) */
> + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(!", "success"));
> +
> + for (i = 0; i < lock_cmds->nelts; i++)
> + {
> + svn_pool_clear (subpool);
> + struct lock_cmd *cmd = &APR_ARRAY_IDX(lock_cmds, i, struct lock_cmd);
> +
> + SVN_ERR(write_lock(conn, subpool, cmd->l));
> + }
> +
> + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
Similar comment about pool vs subpool.
> + svn_pool_destroy (subpool);
> +
> + return SVN_NO_ERROR;
> +}
> +
> static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
> apr_array_header_t *params, void *baton)
> {
> @@ -1430,6 +1506,69 @@
> return SVN_NO_ERROR;
> }
>
> +static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
> + apr_array_header_t *params, void *baton)
> +{
> + server_baton_t *b = baton;
> + svn_boolean_t force;
> + apr_array_header_t *unlock_tokens, *unlock_cmds;
> + int i;
> + apr_pool_t *subpool;
> + struct unlock_cmd {
> + const char *path;
> + const char *full_path;
> + const char *token;
> + };
> +
> + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "bl", &force, &unlock_tokens));
> +
> + unlock_cmds =
> + apr_array_make(pool, unlock_tokens->nelts, sizeof(struct unlock_cmd));
> +
> + subpool = svn_pool_create (pool);
> + /* Loop through the unlock commands. */
> + for (i = 0; i < unlock_tokens->nelts; i++)
Here you initialize the subpool right before the loop in which it's
used :-) Actually, this is the way I prefer, too. Again, just idle
comments, I don't think it's a big deal either way.
> + {
> + svn_pool_clear (subpool);
> + struct unlock_cmd *cmd = apr_pcalloc(pool, sizeof(struct unlock_cmd));
> + svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
> + svn_ra_svn_item_t);
> +
> + if (item->kind != SVN_RA_SVN_LIST)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + "Unlock command should be a list of lists\n");
> +
> + SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "c(?c)",
> + &cmd->path, &cmd->token));
> +
> + cmd->full_path = svn_path_join(b->fs_path,
> + svn_path_canonicalize(cmd->path, subpool),
> + pool);
> +
> + APR_ARRAY_PUSH(unlock_cmds, struct unlock_cmd) = *cmd;
> + }
> +
> + /* Username required unless force was specified. */
> + SVN_ERR(must_have_write_access(conn, pool, b, ! force));
Same comment as before -- i.e., just as ignorable as before :-).
> + /* Loop through each path to be unlocked. */
> + for (i = 0; i < unlock_cmds->nelts; i++)
> + {
> + svn_pool_clear (subpool);
> + struct unlock_cmd *cmd = &APR_ARRAY_IDX(unlock_cmds, i,
> + struct unlock_cmd);
> +
> + SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, cmd->full_path,
> + cmd->token, force, subpool));
> + }
> +
> + SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
Ditto.
> + svn_pool_destroy (subpool);
> +
> + return SVN_NO_ERROR;
> +}
> +
> static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
> apr_array_header_t *params, void *baton)
> {
> @@ -1509,7 +1648,9 @@
> { "get-locations", get_locations },
> { "get-file-revs", get_file_revs },
> { "lock", lock },
> + { "lock-many", lock_many },
> { "unlock", unlock },
> + { "unlock-many", unlock_many },
> { "get-lock", get_lock },
> { "get-locks", get_locks },
> { NULL }
Summary:
This looks great. I'm going to apply it, make the minor tweaks noted
above, and run the regression tests (and check over ethereal to make
sure many locks are sent at once). Assuming that all passes, I'll
commit, no need for a v7.
Thanks!
-Karl
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Thu Jul 7 18:20:00 2005