=== subversion/libsvn_ra_svn/client.c ================================================================== --- subversion/libsvn_ra_svn/client.c (revision 12925) +++ subversion/libsvn_ra_svn/client.c (local) @@ -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 + a one path at a time. ra_svn_lock, which supports 'lock-many' + is now the default. See svn_ra_lock comments 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) { 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,125 @@ 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) +/* Send a 'lock-many' command to the server to lock all paths in + @a path_revs. See svn_ra_lock comments for interface details. + @since new in 1.3. */ +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) { 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 && + strcmp (err->message, "Unknown command 'lock-many'") == 0) + 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 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")); + + 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; +} + +/* 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 + a one path at a time. ra_svn_unlock, which supports 'unlock-many' is + now the default. See svn_ra_unlock comments 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) +{ + 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 +1624,86 @@ return SVN_NO_ERROR; } +/* Send an 'unlock-many' command to the server to unlock all paths in + @a path_tokens. See svn_ra_unlock comments for interface details. + @since new in 1.3.*/ +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) +{ + 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; + + /* (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 && + strcmp (err->message, "Unknown command 'unlock-many'") == 0) + 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_error_clear (err); + svn_pool_destroy (subpool); + + return SVN_NO_ERROR; +} + static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session, svn_lock_t **lock, const char *path, === subversion/libsvn_ra_svn/marshal.c ================================================================== --- subversion/libsvn_ra_svn/marshal.c (revision 12925) +++ subversion/libsvn_ra_svn/marshal.c (local) @@ -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; +} + 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, === subversion/libsvn_ra_svn/ra_svn.h ================================================================== --- subversion/libsvn_ra_svn/ra_svn.h (revision 12925) +++ subversion/libsvn_ra_svn/ra_svn.h (local) @@ -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 */ === subversion/svnserve/serve.c ================================================================== --- subversion/svnserve/serve.c (revision 12925) +++ subversion/svnserve/serve.c (local) @@ -1407,6 +1407,80 @@ 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); + 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, pool), + pool); + + APR_ARRAY_PUSH(lock_cmds, struct lock_cmd) = *cmd; + } + + SVN_ERR(must_have_write_access(conn, pool, b, TRUE)); + + /* 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, "!)")); + + 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 +1504,64 @@ 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++) + { + 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, pool, "c(?c)", + &cmd->path, &cmd->token)); + cmd->full_path = svn_path_join(b->fs_path, + svn_path_canonicalize(cmd->path, pool), + 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)); + + /* 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, "")); + + 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 +1641,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 }