diff options
author | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 |
---|---|---|
committer | Peter Wemm <peter@FreeBSD.org> | 2018-05-08 03:44:38 +0000 |
commit | 3faf8d6bffc5d0fb2525ba37bb504c53366caf9d (patch) | |
tree | 7e47911263e75034b767fe34b2f8d3d17e91f66d /subversion/libsvn_ra_svn/client.c | |
parent | a55fb3c0d5eca7d887798125d5b95942b1f01d4b (diff) |
Diffstat (limited to 'subversion/libsvn_ra_svn/client.c')
-rw-r--r-- | subversion/libsvn_ra_svn/client.c | 610 |
1 files changed, 481 insertions, 129 deletions
diff --git a/subversion/libsvn_ra_svn/client.c b/subversion/libsvn_ra_svn/client.c index a4939ab2ad53..ab1aa58cdc48 100644 --- a/subversion/libsvn_ra_svn/client.c +++ b/subversion/libsvn_ra_svn/client.c @@ -51,6 +51,7 @@ #include "svn_private_config.h" #include "private/svn_fspath.h" +#include "private/svn_string_private.h" #include "private/svn_subr_private.h" #include "../libsvn_ra/ra_loader.h" @@ -188,7 +189,7 @@ static svn_error_t *make_connection(const char *hostname, unsigned short port, /* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the property diffs in LIST, received from the server. */ -static svn_error_t *parse_prop_diffs(const apr_array_header_t *list, +static svn_error_t *parse_prop_diffs(const svn_ra_svn__list_t *list, apr_pool_t *pool, apr_array_header_t **diffs) { @@ -199,26 +200,27 @@ static svn_error_t *parse_prop_diffs(const apr_array_header_t *list, for (i = 0; i < list->nelts; i++) { svn_prop_t *prop; - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(list, i); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Prop diffs element not a list")); prop = apr_array_push(*diffs); - SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name, - &prop->value)); + SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "c(?s)", + &prop->name, &prop->value)); } return SVN_NO_ERROR; } /* Parse a lockdesc, provided in LIST as specified by the protocol into LOCK, allocated in POOL. */ -static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool, +static svn_error_t *parse_lock(const svn_ra_svn__list_t *list, + apr_pool_t *pool, svn_lock_t **lock) { const char *cdate, *edate; *lock = svn_lock_create(pool); - SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path, + SVN_ERR(svn_ra_svn__parse_tuple(list, "ccc(?c)c(?c)", &(*lock)->path, &(*lock)->token, &(*lock)->owner, &(*lock)->comment, &cdate, &edate)); (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool); @@ -241,7 +243,7 @@ static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess, apr_pool_t *pool) { svn_ra_svn_conn_t *conn = sess->conn; - apr_array_header_t *mechlist; + svn_ra_svn__list_t *mechlist; const char *realm; SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm)); @@ -470,9 +472,9 @@ static void handle_child_process_error(apr_pool_t *pool, apr_status_t status, in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool); out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool); - conn = svn_ra_svn_create_conn4(NULL, in_stream, out_stream, + conn = svn_ra_svn_create_conn5(NULL, in_stream, out_stream, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, - 0, pool); + 0, 0, 0, pool); err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc); svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err)); svn_error_clear(err); @@ -541,13 +543,13 @@ static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, apr_file_inherit_unset(proc->out); /* Guard against dotfile output to stdout on the server. */ - *conn = svn_ra_svn_create_conn4(NULL, + *conn = svn_ra_svn_create_conn5(NULL, svn_stream_from_aprfile2(proc->out, FALSE, pool), svn_stream_from_aprfile2(proc->in, FALSE, pool), SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, - 0, 0, pool); + 0, 0, 0, 0, pool); err = svn_ra_svn__skip_leading_garbage(*conn, pool); if (err) return svn_error_quick_wrap( @@ -625,14 +627,20 @@ static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, svn_ra_svn_conn_t *conn; apr_socket_t *sock; apr_uint64_t minver, maxver; - apr_array_header_t *mechlist, *server_caplist, *repos_caplist; + svn_ra_svn__list_t *mechlist, *server_caplist, *repos_caplist; const char *client_string = NULL; apr_pool_t *pool = result_pool; + svn_ra_svn__parent_t *parent; + + parent = apr_pcalloc(pool, sizeof(*parent)); + parent->client_url = svn_stringbuf_create(url, pool); + parent->server_url = svn_stringbuf_create(url, pool); + parent->path = svn_stringbuf_create_empty(pool); sess = apr_palloc(pool, sizeof(*sess)); sess->pool = pool; sess->is_tunneled = (tunnel_name != NULL); - sess->url = apr_pstrdup(pool, url); + sess->parent = parent; sess->user = uri->user; sess->hostname = uri->hostname; sess->tunnel_name = tunnel_name; @@ -673,9 +681,9 @@ static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, apr_pool_cleanup_register(pool, td, close_tunnel_cleanup, apr_pool_cleanup_null); - conn = svn_ra_svn_create_conn4(NULL, td->response, td->request, + conn = svn_ra_svn_create_conn5(NULL, td->response, td->request, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, - 0, 0, pool); + 0, 0, 0, 0, pool); SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool)); } } @@ -687,9 +695,9 @@ static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, SVN_ERR(make_connection(uri->hostname, uri->port ? uri->port : SVN_RA_SVN_PORT, &sock, pool)); - conn = svn_ra_svn_create_conn4(sock, NULL, NULL, + conn = svn_ra_svn_create_conn5(sock, NULL, NULL, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, - 0, 0, pool); + 0, 0, 0, 0, pool); } /* Build the useragent string, querying the client for any @@ -727,7 +735,7 @@ static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, _("Server only supports versions up to %d"), (int) maxver); - SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist)); + SVN_ERR(svn_ra_svn__set_capabilities(conn, server_caplist)); /* All released versions of Subversion support edit-pipeline, * so we do not support servers that do not. */ @@ -739,10 +747,11 @@ static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, * capability list, and the URL, and subsequently there is an auth * request. */ /* Client-side capabilities list: */ - SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)", + SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwwww)cc(?c)", (apr_uint64_t) 2, SVN_RA_SVN_CAP_EDIT_PIPELINE, SVN_RA_SVN_CAP_SVNDIFF1, + SVN_RA_SVN_CAP_SVNDIFF2_ACCEPTED, SVN_RA_SVN_CAP_ABSENT_ENTRIES, SVN_RA_SVN_CAP_DEPTH, SVN_RA_SVN_CAP_MERGEINFO, @@ -760,7 +769,7 @@ static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid, &conn->repos_root, &repos_caplist)); if (repos_caplist) - SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist)); + SVN_ERR(svn_ra_svn__set_capabilities(conn, repos_caplist)); if (conn->repos_root) { @@ -912,23 +921,29 @@ static svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session, return SVN_NO_ERROR; } -static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session, - const char *url, - apr_pool_t *pool) +/* Send the "reparent to URL" command to the server for RA_SESSION and + update the session state. Use SCRATCH_POOL for tempoaries. + */ +static svn_error_t * +reparent_server(svn_ra_session_t *ra_session, + const char *url, + apr_pool_t *scratch_pool) { svn_ra_svn__session_baton_t *sess = ra_session->priv; + svn_ra_svn__parent_t *parent = sess->parent; svn_ra_svn_conn_t *conn = sess->conn; svn_error_t *err; apr_pool_t *sess_pool; svn_ra_svn__session_baton_t *new_sess; apr_uri_t uri; - SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url)); - err = handle_auth_request(sess, pool); + /* Send the request to the server. */ + SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, scratch_pool, url)); + err = handle_auth_request(sess, scratch_pool); if (! err) { - SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); - sess->url = apr_pstrdup(sess->pool, url); + SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "")); + svn_stringbuf_set(parent->server_url, url); return SVN_NO_ERROR; } else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD) @@ -959,11 +974,143 @@ static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session, return SVN_NO_ERROR; } +/* Make sure that RA_SESSION's client and server-side parent infp are in + sync. Use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +ensure_exact_server_parent(svn_ra_session_t *ra_session, + apr_pool_t *scratch_pool) +{ + svn_ra_svn__session_baton_t *sess = ra_session->priv; + svn_ra_svn__parent_t *parent = sess->parent; + + /* During e.g. a checkout operation, many requests will be sent for the + same URL that was used to create the session. So, both sides are + often already in sync. */ + if (svn_stringbuf_compare(parent->client_url, parent->server_url)) + return SVN_NO_ERROR; + + /* Actually reparent the server to the session URL. */ + SVN_ERR(reparent_server(ra_session, parent->client_url->data, + scratch_pool)); + svn_stringbuf_setempty(parent->path); + + return SVN_NO_ERROR; +} + +/* Return a copy of PATH, adjusted to the RA_SESSION's server parent URL. + Allocate the result in RESULT_POOL. */ +static const char * +reparent_path(svn_ra_session_t *ra_session, + const char *path, + apr_pool_t *result_pool) +{ + svn_ra_svn__session_baton_t *sess = ra_session->priv; + svn_ra_svn__parent_t *parent = sess->parent; + + return svn_relpath_join(parent->path->data, path, result_pool); +} + +/* Return a copy of PATHS, containing the same const char * paths but + adjusted to the RA_SESSION's server parent URL. Returns NULL if + PATHS is NULL. Allocate the result in RESULT_POOL. */ +static apr_array_header_t * +reparent_path_array(svn_ra_session_t *ra_session, + const apr_array_header_t *paths, + apr_pool_t *result_pool) +{ + int i; + apr_array_header_t *result; + + if (!paths) + return NULL; + + result = apr_array_copy(result_pool, paths); + for (i = 0; i < result->nelts; ++i) + { + const char **path = &APR_ARRAY_IDX(result, i, const char *); + *path = reparent_path(ra_session, *path, result_pool); + } + + return result; +} + +/* Return a copy of PATHS, containing the same paths for keys but adjusted + to the RA_SESSION's server parent URL. Keeps the values as-are and + returns NULL if PATHS is NULL. Allocate the result in RESULT_POOL. */ +static apr_hash_t * +reparent_path_hash(svn_ra_session_t *ra_session, + apr_hash_t *paths, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *result; + apr_hash_index_t *hi; + + if (!paths) + return NULL; + + result = svn_hash__make(result_pool); + for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi)) + { + const char *path = apr_hash_this_key(hi); + svn_hash_sets(result, + reparent_path(ra_session, path, result_pool), + apr_hash_this_val(hi)); + } + + return result; +} + +static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session, + const char *url, + apr_pool_t *pool) +{ + svn_ra_svn__session_baton_t *sess = ra_session->priv; + svn_ra_svn__parent_t *parent = sess->parent; + svn_ra_svn_conn_t *conn = sess->conn; + const char *path; + + /* Eliminate reparent requests if they are to a sub-path of the + server's current parent path. */ + path = svn_uri_skip_ancestor(parent->server_url->data, url, pool); + if (!path) + { + /* Send the request to the server. + + If within the same repository, reparent to the repo root + because this will maximize the chance to turn future reparent + requests into a client-side update of the rel path. */ + path = conn->repos_root + ? svn_uri_skip_ancestor(conn->repos_root, url, pool) + : NULL; + + if (path) + SVN_ERR(reparent_server(ra_session, conn->repos_root, pool)); + else + SVN_ERR(reparent_server(ra_session, url, pool)); + } + + /* Update the local PARENT information. + PARENT.SERVER_BASE_URL is already up-to-date. */ + svn_stringbuf_set(parent->client_url, url); + if (path) + svn_stringbuf_set(parent->path, path); + else + svn_stringbuf_setempty(parent->path); + + return SVN_NO_ERROR; +} + static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session, - const char **url, apr_pool_t *pool) + const char **url, + apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; - *url = apr_pstrdup(pool, sess->url); + svn_ra_svn__parent_t *parent = sess->parent; + + *url = apr_pstrmemdup(pool, parent->client_url->data, + parent->client_url->len); + return SVN_NO_ERROR; } @@ -1069,7 +1216,7 @@ static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; - apr_array_header_t *proplist; + svn_ra_svn__list_t *proplist; SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev)); SVN_ERR(handle_auth_request(sess_baton, pool)); @@ -1172,6 +1319,9 @@ static svn_error_t *ra_svn_commit(svn_ra_session_t *session, svn_string_create(sess_baton->useragent, pool)); } + /* Callbacks may assume that all data is relative the sessions's URL. */ + SVN_ERR(ensure_exact_server_parent(session, pool)); + /* Tell the server we're starting the commit. Send log message here for backwards compatibility with servers before 1.5. */ @@ -1216,12 +1366,12 @@ static svn_error_t *ra_svn_commit(svn_ra_session_t *session, return SVN_NO_ERROR; } -/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of +/* Parse IPROPLIST, an array of svn_ra_svn__item_t structures, as a list of const char * repos relative paths and properties for those paths, storing the result as an array of svn_prop_inherited_item_t *items. */ static svn_error_t * parse_iproplist(apr_array_header_t **inherited_props, - const apr_array_header_t *iproplist, + const svn_ra_svn__list_t *iproplist, svn_ra_session_t *session, apr_pool_t *result_pool, apr_pool_t *scratch_pool) @@ -1247,14 +1397,13 @@ parse_iproplist(apr_array_header_t **inherited_props, for (i = 0; i < iproplist->nelts; i++) { - apr_array_header_t *iprop_list; + svn_ra_svn__list_t *iprop_list; char *parent_rel_path; apr_hash_t *iprops; apr_hash_index_t *hi; svn_prop_inherited_item_t *new_iprop = apr_palloc(result_pool, sizeof(*new_iprop)); - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i, - svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(iproplist, i); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create( SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, @@ -1262,7 +1411,7 @@ parse_iproplist(apr_array_header_t **inherited_props, svn_pool_clear(iterpool); - SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl", + SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cl", &parent_rel_path, &iprop_list)); SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops)); new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path); @@ -1292,12 +1441,13 @@ static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; - apr_array_header_t *proplist; + svn_ra_svn__list_t *proplist; const char *expected_digest; svn_checksum_t *expected_checksum = NULL; svn_checksum_ctx_t *checksum_ctx; apr_pool_t *iterpool; + path = reparent_path(session, path, pool); SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev, (props != NULL), (stream != NULL))); SVN_ERR(handle_auth_request(sess_baton, pool)); @@ -1325,22 +1475,22 @@ static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, iterpool = svn_pool_create(pool); while (1) { - svn_ra_svn_item_t *item; + svn_ra_svn__item_t *item; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); if (item->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Non-string as part of file contents")); - if (item->u.string->len == 0) + if (item->u.string.len == 0) break; if (expected_checksum) - SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data, - item->u.string->len)); + SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string.data, + item->u.string.len)); - SVN_ERR(svn_stream_write(stream, item->u.string->data, - &item->u.string->len)); + SVN_ERR(svn_stream_write(stream, item->u.string.data, + &item->u.string.len)); } svn_pool_destroy(iterpool); @@ -1360,6 +1510,35 @@ static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, return SVN_NO_ERROR; } +/* Write the protocol words that correspond to DIRENT_FIELDS to CONN + * and use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +send_dirent_fields(svn_ra_svn_conn_t *conn, + apr_uint32_t dirent_fields, + apr_pool_t *scratch_pool) +{ + if (dirent_fields & SVN_DIRENT_KIND) + SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, + SVN_RA_SVN_DIRENT_KIND)); + if (dirent_fields & SVN_DIRENT_SIZE) + SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, + SVN_RA_SVN_DIRENT_SIZE)); + if (dirent_fields & SVN_DIRENT_HAS_PROPS) + SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, + SVN_RA_SVN_DIRENT_HAS_PROPS)); + if (dirent_fields & SVN_DIRENT_CREATED_REV) + SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, + SVN_RA_SVN_DIRENT_CREATED_REV)); + if (dirent_fields & SVN_DIRENT_TIME) + SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, + SVN_RA_SVN_DIRENT_TIME)); + if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) + SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, + SVN_RA_SVN_DIRENT_LAST_AUTHOR)); + + return SVN_NO_ERROR; +} + static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session, apr_hash_t **dirents, svn_revnum_t *fetched_rev, @@ -1371,23 +1550,13 @@ static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session, { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; - apr_array_header_t *proplist, *dirlist; + svn_ra_svn__list_t *proplist, *dirlist; int i; + path = reparent_path(session, path, pool); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path, rev, (props != NULL), (dirents != NULL))); - if (dirent_fields & SVN_DIRENT_KIND) - SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND)); - if (dirent_fields & SVN_DIRENT_SIZE) - SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE)); - if (dirent_fields & SVN_DIRENT_HAS_PROPS) - SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS)); - if (dirent_fields & SVN_DIRENT_CREATED_REV) - SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV)); - if (dirent_fields & SVN_DIRENT_TIME) - SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME)); - if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) - SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR)); + SVN_ERR(send_dirent_fields(conn, dirent_fields, pool)); /* Always send the, nominally optional, want-iprops as "false" to workaround a bug in svnserve 1.8.0-1.8.8 that causes the server @@ -1416,12 +1585,12 @@ static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session, svn_dirent_t *dirent; apr_uint64_t size; svn_revnum_t crev; - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(dirlist, i); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Dirlist element not a list")); - SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)", + SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cwnbr(?c)(?c)", &name, &kind, &size, &has_props, &crev, &cdate, &cauthor)); @@ -1481,12 +1650,14 @@ static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn__parent_t *parent = sess_baton->parent; svn_ra_svn_conn_t *conn = sess_baton->conn; int i; - apr_array_header_t *mergeinfo_tuple; - svn_ra_svn_item_t *elt; + svn_ra_svn__list_t *mergeinfo_tuple; + svn_ra_svn__item_t *elt; const char *path; + paths = reparent_path_array(session, paths, pool); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo")); for (i = 0; i < paths->nelts; i++) { @@ -1509,16 +1680,23 @@ static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session, svn_mergeinfo_t for_path; const char *to_parse; - elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i]; + elt = &SVN_RA_SVN__LIST_ITEM(mergeinfo_tuple, i); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Mergeinfo element is not a list")); - SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc", + SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cc", &path, &to_parse)); SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool)); + /* Correct for naughty servers that send "relative" paths with leading slashes! */ - svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path); + if (path[0] == '/') + ++path; + + /* Correct for the (potential) difference between client and + server-side session parent paths. */ + path = svn_relpath_skip_ancestor(parent->path->data, path); + svn_hash_sets(*catalog, path, for_path); } } @@ -1540,6 +1718,9 @@ static svn_error_t *ra_svn_update(svn_ra_session_t *session, svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); + /* Callbacks may assume that all data is relative the sessions's URL. */ + SVN_ERR(ensure_exact_server_parent(session, scratch_pool)); + /* Tell the server we want to start an update. */ SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse, depth, send_copyfrom_args, @@ -1571,6 +1752,9 @@ ra_svn_switch(svn_ra_session_t *session, svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); + /* Callbacks may assume that all data is relative the sessions's URL. */ + SVN_ERR(ensure_exact_server_parent(session, scratch_pool)); + /* Tell the server we want to start a switch. */ SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse, switch_url, depth, @@ -1596,6 +1780,9 @@ static svn_error_t *ra_svn_status(svn_ra_session_t *session, svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); + /* Callbacks may assume that all data is relative the sessions's URL. */ + SVN_ERR(ensure_exact_server_parent(session, pool)); + /* Tell the server we want to start a status operation. */ SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev, depth)); @@ -1623,6 +1810,9 @@ static svn_error_t *ra_svn_diff(svn_ra_session_t *session, svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); + /* Callbacks may assume that all data is relative the sessions's URL. */ + SVN_ERR(ensure_exact_server_parent(session, pool)); + /* Tell the server we want to start a diff. */ SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse, ignore_ancestry, versus_url, @@ -1636,6 +1826,16 @@ static svn_error_t *ra_svn_diff(svn_ra_session_t *session, return SVN_NO_ERROR; } +/* Return TRUE if ITEM matches the word "done". */ +static svn_boolean_t +is_done_response(const svn_ra_svn__item_t *item) +{ + static const svn_string_t str_done = SVN__STATIC_STRING("done"); + + return ( item->kind == SVN_RA_SVN_WORD + && svn_string_compare(&item->u.word, &str_done)); +} + static svn_error_t * perform_ra_svn_log(svn_error_t **outer_error, @@ -1716,23 +1916,23 @@ perform_ra_svn_log(svn_error_t **outer_error, apr_uint64_t has_children_param, invalid_revnum_param; apr_uint64_t has_subtractive_merge_param; svn_string_t *author, *date, *message; - apr_array_header_t *cplist, *rplist; + svn_ra_svn__list_t *cplist, *rplist; svn_log_entry_t *log_entry; svn_boolean_t has_children; svn_boolean_t subtractive_merge = FALSE; apr_uint64_t revprop_count; - svn_ra_svn_item_t *item; + svn_ra_svn__item_t *item; apr_hash_t *cphash; svn_revnum_t rev; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); - if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) + if (is_done_response(item)) break; if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Log entry not a list")); - SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, + SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "lr(?s)(?s)(?s)?BBnl?B", &cplist, &rev, &author, &date, &message, &has_children_param, @@ -1774,13 +1974,12 @@ perform_ra_svn_log(svn_error_t **outer_error, const char *copy_path, *action, *kind_str; apr_uint64_t text_mods, prop_mods; svn_revnum_t copy_rev; - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i, - svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(cplist, i); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Changed-path entry not a list")); - SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list, + SVN_ERR(svn_ra_svn__read_data_log_changed_entry(&elt->u.list, &cpath, &action, ©_path, ©_rev, &kind_str, &text_mods, &prop_mods)); @@ -1810,7 +2009,7 @@ perform_ra_svn_log(svn_error_t **outer_error, - Except if the server sends more than a >= 1 limit top level items - Or when the callback reported a SVN_ERR_CEASE_INVOCATION in an earlier invocation. */ - if (! (limit && (nest_level == 0) && (++nreceived > limit)) + if (! ((limit > 0) && (nest_level == 0) && (++nreceived > limit)) && ! *outer_error) { svn_error_t *err; @@ -1838,7 +2037,7 @@ perform_ra_svn_log(svn_error_t **outer_error, SVN_PROP_REVISION_LOG, message); err = receiver(receiver_baton, log_entry, iterpool); - if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) + if (svn_error_find_cause(err, SVN_ERR_CEASE_INVOCATION)) { *outer_error = svn_error_trace( svn_error_compose_create(*outer_error, err)); @@ -1878,6 +2077,16 @@ ra_svn_log(svn_ra_session_t *session, svn_error_t *outer_error = NULL; svn_error_t *err; + /* If we don't specify paths, the session's URL is implied. + + Because the paths passed to callbacks are always relative the repos + root, there is no need *always* sync the parent URLs despite invoking + user-provided callbacks. */ + if (paths) + paths = reparent_path_array(session, paths, pool); + else + SVN_ERR(ensure_exact_server_parent(session, pool)); + err = svn_error_trace(perform_ra_svn_log(&outer_error, session, paths, start, end, @@ -1903,6 +2112,7 @@ static svn_error_t *ra_svn_check_path(svn_ra_session_t *session, svn_ra_svn_conn_t *conn = sess_baton->conn; const char *kind_word; + path = reparent_path(session, path, pool); SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word)); @@ -1929,9 +2139,10 @@ static svn_error_t *ra_svn_stat(svn_ra_session_t *session, { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; - apr_array_header_t *list = NULL; + svn_ra_svn__list_t *list = NULL; svn_dirent_t *the_dirent; + path = reparent_path(session, path, pool); SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev)); SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), N_("'stat' not implemented"))); @@ -1948,7 +2159,7 @@ static svn_error_t *ra_svn_stat(svn_ra_session_t *session, svn_revnum_t crev; apr_uint64_t size; - SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)", + SVN_ERR(svn_ra_svn__parse_tuple(list, "wnbr(?c)(?c)", &kind, &size, &has_props, &crev, &cdate, &cauthor)); @@ -1978,8 +2189,11 @@ static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, svn_ra_svn_conn_t *conn = sess_baton->conn; svn_revnum_t revision; svn_boolean_t is_done; + apr_pool_t *iterpool; int i; + path = reparent_path(session, path, pool); + /* Transmit the parameters. */ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!", "get-locations", path, peg_revision)); @@ -1998,21 +2212,25 @@ static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, /* Read the hash items. */ is_done = FALSE; *locations = apr_hash_make(pool); + iterpool = svn_pool_create(pool); while (!is_done) { - svn_ra_svn_item_t *item; + svn_ra_svn__item_t *item; const char *ret_path; - SVN_ERR(svn_ra_svn__read_item(conn, pool, &item)); - if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) + svn_pool_clear(iterpool); + SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); + if (is_done_response(item)) is_done = 1; else if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Location entry not a list")); else { - SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc", + SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "rc", &revision, &ret_path)); + + /* This also makes RET_PATH live in POOL rather than ITERPOOL. */ ret_path = svn_fspath__canonicalize(ret_path, pool); apr_hash_set(*locations, apr_pmemdup(pool, &revision, sizeof(revision)), @@ -2020,20 +2238,23 @@ static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, } } + svn_pool_destroy(iterpool); + /* Read the response. This is so the server would have a chance to * report an error. */ return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "")); } static svn_error_t * -ra_svn_get_location_segments(svn_ra_session_t *session, - const char *path, - svn_revnum_t peg_revision, - svn_revnum_t start_rev, - svn_revnum_t end_rev, - svn_location_segment_receiver_t receiver, - void *receiver_baton, - apr_pool_t *pool) +perform_get_location_segments(svn_error_t **outer_error, + svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_location_segment_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; @@ -2055,12 +2276,12 @@ ra_svn_get_location_segments(svn_ra_session_t *session, while (!is_done) { svn_revnum_t range_start, range_end; - svn_ra_svn_item_t *item; + svn_ra_svn__item_t *item; const char *ret_path; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); - if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) + if (is_done_response(item)) is_done = 1; else if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, @@ -2069,7 +2290,7 @@ ra_svn_get_location_segments(svn_ra_session_t *session, { svn_location_segment_t *segment = apr_pcalloc(iterpool, sizeof(*segment)); - SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)", + SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "rr(?c)", &range_start, &range_end, &ret_path)); if (! (SVN_IS_VALID_REVNUM(range_start) && SVN_IS_VALID_REVNUM(range_end))) @@ -2080,7 +2301,17 @@ ra_svn_get_location_segments(svn_ra_session_t *session, segment->path = ret_path; segment->range_start = range_start; segment->range_end = range_end; - SVN_ERR(receiver(segment, receiver_baton, iterpool)); + + if (!*outer_error) + { + svn_error_t *err = svn_error_trace(receiver(segment, receiver_baton, + iterpool)); + + if (svn_error_find_cause(err, SVN_ERR_CEASE_INVOCATION)) + *outer_error = err; + else + SVN_ERR(err); + } } } svn_pool_destroy(iterpool); @@ -2092,6 +2323,27 @@ ra_svn_get_location_segments(svn_ra_session_t *session, return SVN_NO_ERROR; } +static svn_error_t * +ra_svn_get_location_segments(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_location_segment_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + svn_error_t *outer_err = SVN_NO_ERROR; + svn_error_t *err; + + path = reparent_path(session, path, pool); + err = svn_error_trace( + perform_get_location_segments(&outer_err, session, path, + peg_revision, start_rev, end_rev, + receiver, receiver_baton, pool)); + return svn_error_compose_create(outer_err, err); +} + static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, const char *path, svn_revnum_t start, svn_revnum_t end, @@ -2109,6 +2361,7 @@ static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, rev_pool = svn_pool_create(pool); chunk_pool = svn_pool_create(pool); + path = reparent_path(session, path, pool); SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool, path, start, end, include_merged_revisions)); @@ -2119,10 +2372,10 @@ static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, while (1) { - apr_array_header_t *rev_proplist, *proplist; + svn_ra_svn__list_t *rev_proplist, *proplist; apr_uint64_t merged_rev_param; apr_array_header_t *props; - svn_ra_svn_item_t *item; + svn_ra_svn__item_t *item; apr_hash_t *rev_props; svn_revnum_t rev; const char *p; @@ -2133,7 +2386,7 @@ static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, svn_pool_clear(rev_pool); svn_pool_clear(chunk_pool); SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item)); - if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) + if (is_done_response(item)) break; /* Either we've got a correct revision or we will error out below. */ had_revision = TRUE; @@ -2141,7 +2394,7 @@ static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Revision entry not a list")); - SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool, + SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "crll?B", &p, &rev, &rev_proplist, &proplist, &merged_rev_param)); p = svn_fspath__canonicalize(p, rev_pool); @@ -2157,7 +2410,7 @@ static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, if (item->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Text delta chunk not a string")); - has_txdelta = item->u.string->len > 0; + has_txdelta = item->u.string.len > 0; SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev, has_txdelta ? &d_handler : NULL, &d_baton, @@ -2173,13 +2426,13 @@ static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, rev_pool); else stream = NULL; - while (item->u.string->len > 0) + while (item->u.string.len > 0) { apr_size_t size; - size = item->u.string->len; + size = item->u.string.len; if (stream) - SVN_ERR(svn_stream_write(stream, item->u.string->data, &size)); + SVN_ERR(svn_stream_write(stream, item->u.string.data, &size)); svn_pool_clear(chunk_pool); SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, @@ -2221,7 +2474,7 @@ static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session, { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t* conn = sess->conn; - apr_array_header_t *list; + svn_ra_svn__list_t *list; apr_hash_index_t *hi; apr_pool_t *iterpool = svn_pool_create(pool); @@ -2292,7 +2545,7 @@ static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session, const void *key; const char *path; void *val; - const char *token; + const svn_string_t *token; svn_error_t *err, *callback_err = NULL; svn_pool_clear(iterpool); @@ -2300,7 +2553,7 @@ static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session, apr_hash_this(hi, &key, NULL, &val); path = key; if (strcmp(val, "") != 0) - token = val; + token = svn_string_create(val, iterpool); else token = NULL; @@ -2347,6 +2600,7 @@ static svn_error_t *ra_svn_lock(svn_ra_session_t *session, svn_error_t *err; apr_pool_t *iterpool = svn_pool_create(pool); + path_revs = reparent_path_hash(session, path_revs, pool, pool); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many", comment, steal_lock)); @@ -2384,13 +2638,13 @@ static svn_error_t *ra_svn_lock(svn_ra_session_t *session, /* Loop over responses to get lock information. */ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) { - svn_ra_svn_item_t *elt; + svn_ra_svn__item_t *elt; const void *key; const char *path; svn_error_t *callback_err; const char *status; svn_lock_t *lock; - apr_array_header_t *list; + svn_ra_svn__list_t *list; apr_hash_this(hi, &key, NULL, NULL); path = key; @@ -2402,18 +2656,17 @@ static svn_error_t *ra_svn_lock(svn_ra_session_t *session, the middle of the request list. If this happens, it will transmit "done" to end the lock-info early, and then the overall command response will talk about the fatal error. */ - if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0) + if (is_done_response(elt)) break; if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Lock response not a list")); - SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status, - &list)); + SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "wl", &status, &list)); if (strcmp(status, "failure") == 0) - err = svn_ra_svn__handle_failure_status(list, iterpool); + err = svn_ra_svn__handle_failure_status(list); else if (strcmp(status, "success") == 0) { SVN_ERR(parse_lock(list, iterpool, &lock)); @@ -2440,10 +2693,10 @@ static svn_error_t *ra_svn_lock(svn_ra_session_t *session, read the final "done" from the server. */ if (!hi) { - svn_ra_svn_item_t *elt; + svn_ra_svn__item_t *elt; SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); - if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) + if (!is_done_response(elt)) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Didn't receive end marker for lock " "responses")); @@ -2472,6 +2725,7 @@ static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, svn_error_t *err; const char *path; + path_tokens = reparent_path_hash(session, path_tokens, pool, pool); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many", break_lock)); @@ -2513,11 +2767,11 @@ static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, /* Loop over responses to unlock files. */ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) { - svn_ra_svn_item_t *elt; + svn_ra_svn__item_t *elt; const void *key; svn_error_t *callback_err; const char *status; - apr_array_header_t *list; + svn_ra_svn__list_t *list; svn_pool_clear(iterpool); @@ -2527,7 +2781,7 @@ static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, the middle of the request list. If this happens, it will transmit "done" to end the lock-info early, and then the overall command response will talk about the fatal error. */ - if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0)) + if (is_done_response(elt)) break; apr_hash_this(hi, &key, NULL, NULL); @@ -2537,14 +2791,13 @@ static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Unlock response not a list")); - SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status, - &list)); + SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "wl", &status, &list)); if (strcmp(status, "failure") == 0) - err = svn_ra_svn__handle_failure_status(list, iterpool); + err = svn_ra_svn__handle_failure_status(list); else if (strcmp(status, "success") == 0) { - SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path)); + SVN_ERR(svn_ra_svn__parse_tuple(list, "c", &path)); err = SVN_NO_ERROR; } else @@ -2567,10 +2820,10 @@ static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, read the final "done" from the server. */ if (!hi) { - svn_ra_svn_item_t *elt; + svn_ra_svn__item_t *elt; SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); - if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) + if (! is_done_response(elt)) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Didn't receive end marker for unlock " "responses")); @@ -2590,8 +2843,9 @@ static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session, { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t* conn = sess->conn; - apr_array_header_t *list; + svn_ra_svn__list_t *list; + path = reparent_path(session, path, pool); SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path)); /* Servers before 1.2 doesn't support locking. Check this here. */ @@ -2635,12 +2889,13 @@ static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t* conn = sess->conn; - apr_array_header_t *list; + svn_ra_svn__list_t *list; const char *full_url, *abs_path; int i; /* Figure out the repository abspath from PATH. */ - full_url = svn_path_url_add_component2(sess->url, path, pool); + full_url = svn_path_url_add_component2(sess->parent->client_url->data, + path, pool); SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool)); abs_path = svn_fspath__canonicalize(abs_path, pool); @@ -2658,12 +2913,12 @@ static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, for (i = 0; i < list->nelts; ++i) { svn_lock_t *lock; - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(list, i); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Lock element not a list")); - SVN_ERR(parse_lock(elt->u.list, pool, &lock)); + SVN_ERR(parse_lock(&elt->u.list, pool, &lock)); /* Filter out unwanted paths. Since Subversion only allows locks on files, we can treat depth=immediates the same as @@ -2701,6 +2956,9 @@ static svn_error_t *ra_svn_replay(svn_ra_session_t *session, { svn_ra_svn__session_baton_t *sess = session->priv; + /* Complex EDITOR callbacks may rely on client and server parent path + being in sync. */ + SVN_ERR(ensure_exact_server_parent(session, pool)); SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision, low_water_mark, send_deltas)); @@ -2731,6 +2989,9 @@ ra_svn_replay_range(svn_ra_session_t *session, svn_revnum_t rev; svn_boolean_t drive_aborted = FALSE; + /* Complex EDITOR callbacks may rely on client and server parent path + being in sync. */ + SVN_ERR(ensure_exact_server_parent(session, pool)); SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool, start_revision, end_revision, low_water_mark, send_deltas)); @@ -2746,16 +3007,22 @@ ra_svn_replay_range(svn_ra_session_t *session, void *edit_baton; apr_hash_t *rev_props; const char *word; - apr_array_header_t *list; + svn_ra_svn__list_t *list; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool, "wl", &word, &list)); + if (strcmp(word, "revprops") != 0) - return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, - _("Expected 'revprops', found '%s'"), - word); + { + if (strcmp(word, "failure") == 0) + SVN_ERR(svn_ra_svn__handle_failure_status(list)); + + return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Expected 'revprops', found '%s'"), + word); + } SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props)); @@ -2805,6 +3072,7 @@ ra_svn_has_capability(svn_ra_session_t *session, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS}, {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE}, + {SVN_RA_CAPABILITY_LIST, SVN_RA_SVN_CAP_LIST}, {NULL, NULL} /* End of list marker */ }; @@ -2838,6 +3106,8 @@ ra_svn_get_deleted_rev(svn_ra_session_t *session, svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; + path = reparent_path(session, path, pool); + /* Transmit the parameters. */ SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path, peg_revision, end_revision)); @@ -2872,9 +3142,10 @@ ra_svn_get_inherited_props(svn_ra_session_t *session, { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; - apr_array_header_t *iproplist; + svn_ra_svn__list_t *iproplist; svn_boolean_t iprop_capable; + path = reparent_path(session, path, scratch_pool); SVN_ERR(ra_svn_has_capability(session, &iprop_capable, SVN_RA_CAPABILITY_INHERITED_PROPS, scratch_pool)); @@ -2894,6 +3165,83 @@ ra_svn_get_inherited_props(svn_ra_session_t *session, return SVN_NO_ERROR; } +static svn_error_t * +ra_svn_list(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + const apr_array_header_t *patterns, + svn_depth_t depth, + apr_uint32_t dirent_fields, + svn_ra_dirent_receiver_t receiver, + void *receiver_baton, + apr_pool_t *scratch_pool) +{ + svn_ra_svn__session_baton_t *sess_baton = session->priv; + svn_ra_svn_conn_t *conn = sess_baton->conn; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + path = reparent_path(session, path, scratch_pool); + + /* Send the list request. */ + SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w(c(?r)w(!", "list", + path, revision, svn_depth_to_word(depth))); + SVN_ERR(send_dirent_fields(conn, dirent_fields, scratch_pool)); + + if (patterns) + { + SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!)(!")); + + for (i = 0; i < patterns->nelts; ++i) + { + const char *pattern = APR_ARRAY_IDX(patterns, i, const char *); + SVN_ERR(svn_ra_svn__write_cstring(conn, scratch_pool, pattern)); + } + } + + SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!))")); + + /* Handle auth request by server */ + SVN_ERR(handle_auth_request(sess_baton, scratch_pool)); + + /* Read and process list response. */ + while (1) + { + svn_ra_svn__item_t *item; + const char *dirent_path; + const char *kind_word, *date; + svn_dirent_t dirent = { 0 }; + + svn_pool_clear(iterpool); + + /* Read the next dirent or bail out on "done", respectively */ + SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); + if (is_done_response(item)) + break; + if (item->kind != SVN_RA_SVN_LIST) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("List entry not a list")); + SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, + "cw?(?n)(?b)(?r)(?c)(?c)", + &dirent_path, &kind_word, &dirent.size, + &dirent.has_props, &dirent.created_rev, + &date, &dirent.last_author)); + + /* Convert data. */ + dirent.kind = svn_node_kind_from_word(kind_word); + if (date) + SVN_ERR(svn_time_from_cstring(&dirent.time, date, iterpool)); + + /* Invoke RECEIVER */ + SVN_ERR(receiver(dirent_path, &dirent, receiver_baton, iterpool)); + } + svn_pool_destroy(iterpool); + + /* Read the actual command response. */ + SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "")); + return SVN_NO_ERROR; +} + static const svn_ra__vtable_t ra_svn_vtable = { svn_ra_svn_version, ra_svn_get_description, @@ -2931,8 +3279,12 @@ static const svn_ra__vtable_t ra_svn_vtable = { ra_svn_has_capability, ra_svn_replay_range, ra_svn_get_deleted_rev, + ra_svn_get_inherited_props, + NULL /* ra_set_svn_ra_open */, + ra_svn_list, ra_svn_register_editor_shim_callbacks, - ra_svn_get_inherited_props + NULL /* commit_ev2 */, + NULL /* replay_range_ev2 */ }; svn_error_t * |