| /* |
| * client.c : Functions for repository access via the Subversion protocol |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #include "svn_private_config.h" |
| |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> |
| #include <apr_general.h> |
| #include <apr_strings.h> |
| #include <apr_network_io.h> |
| #include <apr_uri.h> |
| |
| #include "svn_hash.h" |
| #include "svn_types.h" |
| #include "svn_string.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_error.h" |
| #include "svn_time.h" |
| #include "svn_path.h" |
| #include "svn_pools.h" |
| #include "svn_config.h" |
| #include "svn_ra.h" |
| #include "svn_ra_svn.h" |
| #include "svn_props.h" |
| #include "svn_mergeinfo.h" |
| #include "svn_version.h" |
| #include "svn_ctype.h" |
| |
| #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" |
| |
| #include "ra_svn.h" |
| |
| #ifdef SVN_HAVE_SASL |
| #define DO_AUTH svn_ra_svn__do_cyrus_auth |
| #else |
| #define DO_AUTH svn_ra_svn__do_internal_auth |
| #endif |
| |
| /* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for |
| whatever reason) deems svn_depth_immediates as non-recursive, which |
| is ... kinda true, but not true enough for our purposes. We need |
| our requested recursion level to be *at least* as recursive as the |
| real depth we're looking for. |
| */ |
| #define DEPTH_TO_RECURSE(d) \ |
| ((d) == svn_depth_unknown || (d) > svn_depth_files) |
| |
| typedef struct ra_svn_commit_callback_baton_t { |
| svn_ra_svn__session_baton_t *sess_baton; |
| apr_pool_t *pool; |
| svn_revnum_t *new_rev; |
| svn_commit_callback2_t callback; |
| void *callback_baton; |
| } ra_svn_commit_callback_baton_t; |
| |
| typedef struct ra_svn_reporter_baton_t { |
| svn_ra_svn__session_baton_t *sess_baton; |
| svn_ra_svn_conn_t *conn; |
| apr_pool_t *pool; |
| const svn_delta_editor_t *editor; |
| void *edit_baton; |
| } ra_svn_reporter_baton_t; |
| |
| /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel |
| portion. */ |
| static void parse_tunnel(const char *url, const char **tunnel, |
| apr_pool_t *pool) |
| { |
| *tunnel = NULL; |
| |
| if (strncasecmp(url, "svn", 3) != 0) |
| return; |
| url += 3; |
| |
| /* Get the tunnel specification, if any. */ |
| if (*url == '+') |
| { |
| const char *p; |
| |
| url++; |
| p = strchr(url, ':'); |
| if (!p) |
| return; |
| *tunnel = apr_pstrmemdup(pool, url, p - url); |
| } |
| } |
| |
| static svn_error_t *make_connection(const char *hostname, unsigned short port, |
| apr_socket_t **sock, apr_pool_t *pool) |
| { |
| apr_sockaddr_t *sa; |
| apr_status_t status; |
| int family = APR_INET; |
| |
| /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get |
| APR_UNSPEC, because it may give us back an IPV6 address even if we can't |
| create IPV6 sockets. */ |
| |
| #if APR_HAVE_IPV6 |
| #ifdef MAX_SECS_TO_LINGER |
| status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool); |
| #else |
| status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, |
| APR_PROTO_TCP, pool); |
| #endif |
| if (status == 0) |
| { |
| apr_socket_close(*sock); |
| family = APR_UNSPEC; |
| } |
| #endif |
| |
| /* Resolve the hostname. */ |
| status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool); |
| if (status) |
| return svn_error_createf(status, NULL, _("Unknown hostname '%s'"), |
| hostname); |
| /* Iterate through the returned list of addresses attempting to |
| * connect to each in turn. */ |
| do |
| { |
| /* Create the socket. */ |
| #ifdef MAX_SECS_TO_LINGER |
| /* ### old APR interface */ |
| status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool); |
| #else |
| status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP, |
| pool); |
| #endif |
| if (status == APR_SUCCESS) |
| { |
| status = apr_socket_connect(*sock, sa); |
| if (status != APR_SUCCESS) |
| apr_socket_close(*sock); |
| } |
| sa = sa->next; |
| } |
| while (status != APR_SUCCESS && sa); |
| |
| if (status) |
| return svn_error_wrap_apr(status, _("Can't connect to host '%s'"), |
| hostname); |
| |
| /* Enable TCP keep-alives on the socket so we time out when |
| * the connection breaks due to network-layer problems. |
| * If the peer has dropped the connection due to a network partition |
| * or a crash, or if the peer no longer considers the connection |
| * valid because we are behind a NAT and our public IP has changed, |
| * it will respond to the keep-alive probe with a RST instead of an |
| * acknowledgment segment, which will cause svn to abort the session |
| * even while it is currently blocked waiting for data from the peer. |
| * See issue #3347. */ |
| status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1); |
| if (status) |
| { |
| /* It's not a fatal error if we cannot enable keep-alives. */ |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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 svn_ra_svn__list_t *list, |
| apr_pool_t *pool, |
| apr_array_header_t **diffs) |
| { |
| int i; |
| |
| *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t)); |
| |
| for (i = 0; i < list->nelts; i++) |
| { |
| svn_prop_t *prop; |
| 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, "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 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, "ccc(?c)c(?c)", &(*lock)->path, |
| &(*lock)->token, &(*lock)->owner, |
| &(*lock)->comment, &cdate, &edate)); |
| (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool); |
| SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool)); |
| if (edate) |
| SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* --- AUTHENTICATION ROUTINES --- */ |
| |
| svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn, |
| apr_pool_t *pool, |
| const char *mech, const char *mech_arg) |
| { |
| return svn_error_trace(svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg)); |
| } |
| |
| 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; |
| svn_ra_svn__list_t *mechlist; |
| const char *realm; |
| |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm)); |
| if (mechlist->nelts == 0) |
| return SVN_NO_ERROR; |
| return DO_AUTH(sess, mechlist, realm, pool); |
| } |
| |
| /* --- REPORTER IMPLEMENTATION --- */ |
| |
| static svn_error_t *ra_svn_set_path(void *baton, const char *path, |
| svn_revnum_t rev, |
| svn_depth_t depth, |
| svn_boolean_t start_empty, |
| const char *lock_token, |
| apr_pool_t *pool) |
| { |
| ra_svn_reporter_baton_t *b = baton; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev, |
| start_empty, lock_token, depth)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_delete_path(void *baton, const char *path, |
| apr_pool_t *pool) |
| { |
| ra_svn_reporter_baton_t *b = baton; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_link_path(void *baton, const char *path, |
| const char *url, |
| svn_revnum_t rev, |
| svn_depth_t depth, |
| svn_boolean_t start_empty, |
| const char *lock_token, |
| apr_pool_t *pool) |
| { |
| ra_svn_reporter_baton_t *b = baton; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev, |
| start_empty, lock_token, depth)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_finish_report(void *baton, |
| apr_pool_t *pool) |
| { |
| ra_svn_reporter_baton_t *b = baton; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool)); |
| SVN_ERR(handle_auth_request(b->sess_baton, b->pool)); |
| SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton, |
| NULL, FALSE)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, "")); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_abort_report(void *baton, |
| apr_pool_t *pool) |
| { |
| ra_svn_reporter_baton_t *b = baton; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_ra_reporter3_t ra_svn_reporter = { |
| ra_svn_set_path, |
| ra_svn_delete_path, |
| ra_svn_link_path, |
| ra_svn_finish_report, |
| ra_svn_abort_report |
| }; |
| |
| /* Set *REPORTER and *REPORT_BATON to a new reporter which will drive |
| * EDITOR/EDIT_BATON when it gets the finish_report() call. |
| * |
| * Allocate the new reporter in POOL. |
| */ |
| static svn_error_t * |
| ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton, |
| apr_pool_t *pool, |
| const svn_delta_editor_t *editor, |
| void *edit_baton, |
| const char *target, |
| svn_depth_t depth, |
| const svn_ra_reporter3_t **reporter, |
| void **report_baton) |
| { |
| ra_svn_reporter_baton_t *b; |
| const svn_delta_editor_t *filter_editor; |
| void *filter_baton; |
| |
| /* We can skip the depth filtering when the user requested |
| depth_files or depth_infinity because the server will |
| transmit the right stuff anyway. */ |
| if ((depth != svn_depth_files) && (depth != svn_depth_infinity) |
| && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH)) |
| { |
| SVN_ERR(svn_delta_depth_filter_editor(&filter_editor, |
| &filter_baton, |
| editor, edit_baton, depth, |
| *target != '\0', |
| pool)); |
| editor = filter_editor; |
| edit_baton = filter_baton; |
| } |
| |
| b = apr_palloc(pool, sizeof(*b)); |
| b->sess_baton = sess_baton; |
| b->conn = sess_baton->conn; |
| b->pool = pool; |
| b->editor = editor; |
| b->edit_baton = edit_baton; |
| |
| *reporter = &ra_svn_reporter; |
| *report_baton = b; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* --- RA LAYER IMPLEMENTATION --- */ |
| |
| /* (Note: *ARGV_P is an output parameter.) */ |
| static svn_error_t *find_tunnel_agent(const char *tunnel, |
| const char *hostinfo, |
| const char ***argv_p, |
| apr_hash_t *config, apr_pool_t *pool) |
| { |
| svn_config_t *cfg; |
| const char *val, *var, *cmd; |
| char **cmd_argv; |
| const char **argv; |
| apr_size_t len; |
| apr_status_t status; |
| int n; |
| |
| /* Look up the tunnel specification in config. */ |
| cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; |
| svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL); |
| |
| /* We have one predefined tunnel scheme, if it isn't overridden by config. */ |
| if (!val && strcmp(tunnel, "ssh") == 0) |
| { |
| /* Killing the tunnel agent with SIGTERM leads to unsightly |
| * stderr output from ssh, unless we pass -q. |
| * The "-q" option to ssh is widely supported: all versions of |
| * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com |
| * versions have it too. If the user is using some other ssh |
| * implementation that doesn't accept it, they can override it |
| * in the [tunnels] section of the config. */ |
| val = "$SVN_SSH ssh -q --"; |
| } |
| |
| if (!val || !*val) |
| return svn_error_createf(SVN_ERR_BAD_URL, NULL, |
| _("Undefined tunnel scheme '%s'"), tunnel); |
| |
| /* If the scheme definition begins with "$varname", it means there |
| * is an environment variable which can override the command. */ |
| if (*val == '$') |
| { |
| val++; |
| len = strcspn(val, " "); |
| var = apr_pstrmemdup(pool, val, len); |
| cmd = getenv(var); |
| if (!cmd) |
| { |
| cmd = val + len; |
| while (*cmd == ' ') |
| cmd++; |
| if (!*cmd) |
| return svn_error_createf(SVN_ERR_BAD_URL, NULL, |
| _("Tunnel scheme %s requires environment " |
| "variable %s to be defined"), tunnel, |
| var); |
| } |
| } |
| else |
| cmd = val; |
| |
| /* Tokenize the command into a list of arguments. */ |
| status = apr_tokenize_to_argv(cmd, &cmd_argv, pool); |
| if (status != APR_SUCCESS) |
| return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd); |
| |
| /* Calc number of the fixed arguments. */ |
| for (n = 0; cmd_argv[n] != NULL; n++) |
| ; |
| |
| argv = apr_palloc(pool, (n + 4) * sizeof(char *)); |
| |
| /* Append the fixed arguments to the result. */ |
| for (n = 0; cmd_argv[n] != NULL; n++) |
| argv[n] = cmd_argv[n]; |
| |
| argv[n++] = hostinfo; |
| argv[n++] = "svnserve"; |
| argv[n++] = "-t"; |
| argv[n] = NULL; |
| |
| *argv_p = argv; |
| return SVN_NO_ERROR; |
| } |
| |
| /* This function handles any errors which occur in the child process |
| * created for a tunnel agent. We write the error out as a command |
| * failure; the code in ra_svn_open() to read the server's greeting |
| * will see the error and return it to the caller. */ |
| static void handle_child_process_error(apr_pool_t *pool, apr_status_t status, |
| const char *desc) |
| { |
| svn_ra_svn_conn_t *conn; |
| apr_file_t *in_file, *out_file; |
| svn_stream_t *in_stream, *out_stream; |
| svn_error_t *err; |
| |
| if (apr_file_open_stdin(&in_file, pool) |
| || apr_file_open_stdout(&out_file, pool)) |
| return; |
| |
| 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_conn5(NULL, in_stream, out_stream, |
| SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, |
| 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); |
| svn_error_clear(svn_ra_svn__flush(conn, pool)); |
| } |
| |
| /* (Note: *CONN is an output parameter.) */ |
| static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| apr_proc_t *proc; |
| apr_procattr_t *attr; |
| svn_error_t *err; |
| |
| status = apr_procattr_create(&attr, pool); |
| if (status == APR_SUCCESS) |
| status = apr_procattr_io_set(attr, 1, 1, 0); |
| if (status == APR_SUCCESS) |
| status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); |
| if (status == APR_SUCCESS) |
| status = apr_procattr_child_errfn_set(attr, handle_child_process_error); |
| proc = apr_palloc(pool, sizeof(*proc)); |
| if (status == APR_SUCCESS) |
| status = apr_proc_create(proc, *args, args, NULL, attr, pool); |
| if (status != APR_SUCCESS) |
| return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL, |
| svn_error_wrap_apr(status, |
| _("Can't create tunnel")), NULL); |
| |
| /* Arrange for the tunnel agent to get a SIGTERM on pool |
| * cleanup. This is a little extreme, but the alternatives |
| * weren't working out. |
| * |
| * Closing the pipes and waiting for the process to die |
| * was prone to mysterious hangs which are difficult to |
| * diagnose (e.g. svnserve dumps core due to unrelated bug; |
| * sshd goes into zombie state; ssh connection is never |
| * closed; ssh never terminates). |
| * See also the long dicussion in issue #2580 if you really |
| * want to know various reasons for these problems and |
| * the different opinions on this issue. |
| * |
| * On Win32, APR does not support KILL_ONLY_ONCE. It only has |
| * KILL_ALWAYS and KILL_NEVER. Other modes are converted to |
| * KILL_ALWAYS, which immediately calls TerminateProcess(). |
| * This instantly kills the tunnel, leaving sshd and svnserve |
| * on a remote machine running indefinitely. These processes |
| * accumulate. The problem is most often seen with a fast client |
| * machine and a modest internet connection, as the tunnel |
| * is killed before being able to gracefully complete the |
| * session. In that case, svn is unusable 100% of the time on |
| * the windows machine. Thus, on Win32, we use KILL_NEVER and |
| * take the lesser of two evils. |
| */ |
| #ifdef WIN32 |
| apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER); |
| #else |
| apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE); |
| #endif |
| |
| /* APR pipe objects inherit by default. But we don't want the |
| * tunnel agent's pipes held open by future child processes |
| * (such as other ra_svn sessions), so turn that off. */ |
| apr_file_inherit_unset(proc->in); |
| apr_file_inherit_unset(proc->out); |
| |
| /* Guard against dotfile output to stdout on the server. */ |
| *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, 0, 0, pool); |
| err = svn_ra_svn__skip_leading_garbage(*conn, pool); |
| if (err) |
| return svn_error_quick_wrap( |
| err, |
| _("To better debug SSH connection problems, remove the -q " |
| "option from 'ssh' in the [tunnels] section of your " |
| "Subversion configuration file.")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Parse URL inot URI, validating it and setting the default port if none |
| was given. Allocate the URI fileds out of POOL. */ |
| static svn_error_t *parse_url(const char *url, apr_uri_t *uri, |
| apr_pool_t *pool) |
| { |
| apr_status_t apr_err; |
| |
| apr_err = apr_uri_parse(pool, url, uri); |
| |
| if (apr_err != 0) |
| return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
| _("Illegal svn repository URL '%s'"), url); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* This structure is used as a baton for the pool cleanup function to |
| store tunnel parameters used by the close-tunnel callback. */ |
| struct tunnel_data_t { |
| void *tunnel_context; |
| void *tunnel_baton; |
| svn_ra_close_tunnel_func_t close_tunnel; |
| svn_stream_t *request; |
| svn_stream_t *response; |
| }; |
| |
| /* Pool cleanup function that invokes the close-tunnel callback. */ |
| static apr_status_t close_tunnel_cleanup(void *baton) |
| { |
| const struct tunnel_data_t *const td = baton; |
| |
| if (td->close_tunnel) |
| td->close_tunnel(td->tunnel_context, td->tunnel_baton); |
| |
| svn_error_clear(svn_stream_close(td->request)); |
| |
| /* We might have one stream to use for both request and response! */ |
| if (td->request != td->response) |
| svn_error_clear(svn_stream_close(td->response)); |
| |
| return APR_SUCCESS; /* ignored */ |
| } |
| |
| /* Open a session to URL, returning it in *SESS_P, allocating it in POOL. |
| URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON |
| are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL, |
| it is the name of the tunnel type parsed from the URL scheme. |
| If TUNNEL_ARGV is not NULL, it points to a program argument list to use |
| when invoking the tunnel agent. |
| */ |
| static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, |
| const char *url, |
| const apr_uri_t *uri, |
| const char *tunnel_name, |
| const char **tunnel_argv, |
| apr_hash_t *config, |
| const svn_ra_callbacks2_t *callbacks, |
| void *callbacks_baton, |
| svn_auth_baton_t *auth_baton, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_ra_svn__session_baton_t *sess; |
| svn_ra_svn_conn_t *conn; |
| apr_socket_t *sock; |
| apr_uint64_t minver, maxver; |
| 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->parent = parent; |
| sess->user = uri->user; |
| sess->hostname = uri->hostname; |
| sess->tunnel_name = tunnel_name; |
| sess->tunnel_argv = tunnel_argv; |
| sess->callbacks = callbacks; |
| sess->callbacks_baton = callbacks_baton; |
| sess->bytes_read = sess->bytes_written = 0; |
| sess->auth_baton = auth_baton; |
| |
| if (config) |
| SVN_ERR(svn_config_copy_config(&sess->config, config, pool)); |
| else |
| sess->config = NULL; |
| |
| if (tunnel_name) |
| { |
| sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>", |
| tunnel_name, |
| uri->hostname, uri->port); |
| |
| if (tunnel_argv) |
| SVN_ERR(make_tunnel(tunnel_argv, &conn, pool)); |
| else |
| { |
| struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td)); |
| |
| td->tunnel_baton = callbacks->tunnel_baton; |
| td->close_tunnel = NULL; |
| |
| SVN_ERR(callbacks->open_tunnel_func( |
| &td->request, &td->response, |
| &td->close_tunnel, &td->tunnel_context, |
| callbacks->tunnel_baton, tunnel_name, |
| uri->user, uri->hostname, uri->port, |
| callbacks->cancel_func, callbacks_baton, |
| pool)); |
| |
| apr_pool_cleanup_register(pool, td, close_tunnel_cleanup, |
| apr_pool_cleanup_null); |
| |
| conn = svn_ra_svn_create_conn5(NULL, td->response, td->request, |
| SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, |
| 0, 0, 0, 0, pool); |
| SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool)); |
| } |
| } |
| else |
| { |
| sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname, |
| uri->port ? uri->port : SVN_RA_SVN_PORT); |
| |
| SVN_ERR(make_connection(uri->hostname, |
| uri->port ? uri->port : SVN_RA_SVN_PORT, |
| &sock, pool)); |
| conn = svn_ra_svn_create_conn5(sock, NULL, NULL, |
| SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, |
| 0, 0, 0, 0, pool); |
| } |
| |
| /* Build the useragent string, querying the client for any |
| customizations it wishes to note. For historical reasons, we |
| still deliver the hard-coded client version info |
| (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string |
| separately in the protocol/capabilities handshake below. But the |
| commit logic wants the combined form for use with the |
| SVN_PROP_TXN_USER_AGENT ephemeral property because that's |
| consistent with our DAV approach. */ |
| if (sess->callbacks->get_client_string != NULL) |
| SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton, |
| &client_string, pool)); |
| if (client_string) |
| sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ", |
| client_string, SVN_VA_NULL); |
| else |
| sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT; |
| |
| /* Make sure we set conn->session before reading from it, |
| * because the reader and writer functions expect a non-NULL value. */ |
| sess->conn = conn; |
| conn->session = sess; |
| |
| /* Read server's greeting. */ |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver, |
| &mechlist, &server_caplist)); |
| |
| /* We support protocol version 2. */ |
| if (minver > 2) |
| return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, |
| _("Server requires minimum version %d"), |
| (int) minver); |
| if (maxver < 2) |
| 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)); |
| |
| /* All released versions of Subversion support edit-pipeline, |
| * so we do not support servers that do not. */ |
| if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) |
| return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, |
| _("Server does not support edit pipelining")); |
| |
| /* In protocol version 2, we send back our protocol version, our |
| * 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(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, |
| SVN_RA_SVN_CAP_LOG_REVPROPS, |
| url, |
| SVN_RA_SVN__DEFAULT_USERAGENT, |
| client_string)); |
| SVN_ERR(handle_auth_request(sess, pool)); |
| |
| /* This is where the security layer would go into effect if we |
| * supported security layers, which is a ways off. */ |
| |
| /* Read the repository's uuid and root URL, and perhaps learn more |
| capabilities that weren't available before now. */ |
| 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)); |
| |
| if (conn->repos_root) |
| { |
| conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool); |
| /* We should check that the returned string is a prefix of url, since |
| that's the API guarantee, but this isn't true for 1.0 servers. |
| Checking the length prevents client crashes. */ |
| if (strlen(conn->repos_root) > strlen(url)) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Impossibly long repository root from " |
| "server")); |
| } |
| |
| *sess_p = sess; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| #ifdef SVN_HAVE_SASL |
| #define RA_SVN_DESCRIPTION \ |
| N_("Module for accessing a repository using the svn network protocol.\n" \ |
| " - with Cyrus SASL authentication") |
| #else |
| #define RA_SVN_DESCRIPTION \ |
| N_("Module for accessing a repository using the svn network protocol.") |
| #endif |
| |
| static const char *ra_svn_get_description(apr_pool_t *pool) |
| { |
| return _(RA_SVN_DESCRIPTION); |
| } |
| |
| static const char * const * |
| ra_svn_get_schemes(apr_pool_t *pool) |
| { |
| static const char *schemes[] = { "svn", NULL }; |
| |
| return schemes; |
| } |
| |
| |
| /* A simple whitelist to ensure the following are valid: |
| * user@server |
| * [::1]:22 |
| * server-name |
| * server_name |
| * 127.0.0.1 |
| * with an extra restriction that a leading '-' is invalid. |
| */ |
| static svn_boolean_t |
| is_valid_hostinfo(const char *hostinfo) |
| { |
| const char *p = hostinfo; |
| |
| if (p[0] == '-') |
| return FALSE; |
| |
| while (*p) |
| { |
| if (!svn_ctype_isalnum(*p) && !strchr(":.-_[]@", *p)) |
| return FALSE; |
| |
| ++p; |
| } |
| |
| return TRUE; |
| } |
| |
| static svn_error_t *ra_svn_open(svn_ra_session_t *session, |
| const char **corrected_url, |
| const char **redirect_url, |
| const char *url, |
| const svn_ra_callbacks2_t *callbacks, |
| void *callback_baton, |
| svn_auth_baton_t *auth_baton, |
| apr_hash_t *config, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *sess_pool = svn_pool_create(result_pool); |
| svn_ra_svn__session_baton_t *sess; |
| const char *tunnel, **tunnel_argv; |
| apr_uri_t uri; |
| svn_config_t *cfg, *cfg_client; |
| |
| /* We don't support server-prescribed redirections in ra-svn. */ |
| if (corrected_url) |
| *corrected_url = NULL; |
| if (redirect_url) |
| *redirect_url = NULL; |
| |
| SVN_ERR(parse_url(url, &uri, sess_pool)); |
| |
| parse_tunnel(url, &tunnel, result_pool); |
| |
| /* Use the default tunnel implementation if we got a tunnel name, |
| but either do not have tunnel handler callbacks installed, or |
| the handlers don't like the tunnel name. */ |
| if (tunnel |
| && (!callbacks->open_tunnel_func |
| || (callbacks->check_tunnel_func && callbacks->open_tunnel_func |
| && !callbacks->check_tunnel_func(callbacks->tunnel_baton, |
| tunnel)))) |
| { |
| const char *decoded_hostinfo; |
| |
| decoded_hostinfo = svn_path_uri_decode(uri.hostinfo, result_pool); |
| |
| if (!is_valid_hostinfo(decoded_hostinfo)) |
| return svn_error_createf(SVN_ERR_BAD_URL, NULL, _("Invalid host '%s'"), |
| uri.hostinfo); |
| |
| SVN_ERR(find_tunnel_agent(tunnel, decoded_hostinfo, &tunnel_argv, |
| config, result_pool)); |
| } |
| else |
| tunnel_argv = NULL; |
| |
| cfg_client = config |
| ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) |
| : NULL; |
| cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL; |
| svn_auth_set_parameter(auth_baton, |
| SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client); |
| svn_auth_set_parameter(auth_baton, |
| SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg); |
| |
| /* We open the session in a subpool so we can get rid of it if we |
| reparent with a server that doesn't support reparenting. */ |
| SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config, |
| callbacks, callback_baton, |
| auth_baton, sess_pool, scratch_pool)); |
| session->priv = sess; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session, |
| svn_ra_session_t *old_session, |
| const char *new_session_url, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_ra_svn__session_baton_t *old_sess = old_session->priv; |
| |
| SVN_ERR(ra_svn_open(new_session, NULL, NULL, new_session_url, |
| old_sess->callbacks, old_sess->callbacks_baton, |
| old_sess->auth_baton, old_sess->config, |
| result_pool, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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; |
| |
| /* 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, scratch_pool, "")); |
| svn_stringbuf_set(parent->server_url, url); |
| return SVN_NO_ERROR; |
| } |
| else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD) |
| return err; |
| |
| /* Servers before 1.4 doesn't support this command; try to reconnect |
| instead. */ |
| svn_error_clear(err); |
| /* Create a new subpool of the RA session pool. */ |
| sess_pool = svn_pool_create(ra_session->pool); |
| err = parse_url(url, &uri, sess_pool); |
| if (! err) |
| err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv, |
| sess->config, sess->callbacks, sess->callbacks_baton, |
| sess->auth_baton, sess_pool, sess_pool); |
| /* We destroy the new session pool on error, since it is allocated in |
| the main session pool. */ |
| if (err) |
| { |
| svn_pool_destroy(sess_pool); |
| return err; |
| } |
| |
| /* We have a new connection, assign it and destroy the old. */ |
| ra_session->priv = new_sess; |
| svn_pool_destroy(sess->pool); |
| |
| 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) |
| { |
| svn_ra_svn__session_baton_t *sess = session->priv; |
| svn_ra_svn__parent_t *parent = sess->parent; |
| |
| *url = apr_pstrmemdup(pool, parent->client_url->data, |
| parent->client_url->len); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session, |
| svn_revnum_t *rev, apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session, |
| svn_revnum_t *rev, apr_time_t tm, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Forward declaration. */ |
| static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session, |
| svn_boolean_t *has, |
| const char *capability, |
| apr_pool_t *pool); |
| |
| static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, |
| const char *name, |
| const svn_string_t *const *old_value_p, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| svn_boolean_t dont_care; |
| const svn_string_t *old_value; |
| svn_boolean_t has_atomic_revprops; |
| |
| SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops, |
| SVN_RA_SVN_CAP_ATOMIC_REVPROPS, |
| pool)); |
| |
| if (old_value_p) |
| { |
| /* How did you get past the same check in svn_ra_change_rev_prop2()? */ |
| SVN_ERR_ASSERT(has_atomic_revprops); |
| |
| dont_care = FALSE; |
| old_value = *old_value_p; |
| } |
| else |
| { |
| dont_care = TRUE; |
| old_value = NULL; |
| } |
| |
| if (has_atomic_revprops) |
| SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name, |
| value, dont_care, |
| old_value)); |
| else |
| SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name, |
| value)); |
| |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| *uuid = conn->uuid; |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| if (!conn->repos_root) |
| return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, |
| _("Server did not send repository root")); |
| *url = conn->repos_root; |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev, |
| apr_hash_t **props, apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| 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)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist)); |
| SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, |
| const char *name, |
| svn_string_t **value, apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_end_commit(void *baton) |
| { |
| ra_svn_commit_callback_baton_t *ccb = baton; |
| svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool); |
| |
| 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)?(?c)", |
| &(commit_info->revision), |
| &(commit_info->date), |
| &(commit_info->author), |
| &(commit_info->post_commit_err))); |
| |
| commit_info->repos_root = apr_pstrdup(ccb->pool, |
| ccb->sess_baton->conn->repos_root); |
| |
| if (ccb->callback) |
| SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_commit(svn_ra_session_t *session, |
| const svn_delta_editor_t **editor, |
| void **edit_baton, |
| apr_hash_t *revprop_table, |
| svn_commit_callback2_t callback, |
| void *callback_baton, |
| apr_hash_t *lock_tokens, |
| svn_boolean_t keep_locks, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| ra_svn_commit_callback_baton_t *ccb; |
| apr_hash_index_t *hi; |
| apr_pool_t *iterpool; |
| const svn_string_t *log_msg = svn_hash_gets(revprop_table, |
| SVN_PROP_REVISION_LOG); |
| |
| if (log_msg == NULL && |
| ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) |
| { |
| return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL, |
| _("ra_svn does not support not specifying " |
| "a log message with pre-1.5 servers; " |
| "consider passing an empty one, or upgrading " |
| "the server")); |
| } |
| else if (log_msg == NULL) |
| /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument |
| to the 'commit' protocol command is non-optional; on the server side, |
| only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The |
| "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit |
| will have a NULL log message (not just "", really NULL). |
| |
| svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was |
| present; this was elevated to a protocol promise in r1498550 (and |
| later documented in this comment) in order to fix the segmentation |
| fault bug described in the log message of r1498550.*/ |
| log_msg = svn_string_create("", pool); |
| |
| /* If we're sending revprops other than svn:log, make sure the server won't |
| silently ignore them. */ |
| if (apr_hash_count(revprop_table) > 1 && |
| ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) |
| return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, |
| _("Server doesn't support setting arbitrary " |
| "revision properties during commit")); |
| |
| /* If the server supports ephemeral txnprops, add the one that |
| reports the client's version level string. */ |
| if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) && |
| svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS)) |
| { |
| svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION, |
| svn_string_create(SVN_VER_NUMBER, pool)); |
| svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT, |
| 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. */ |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit", |
| log_msg->data)); |
| if (lock_tokens) |
| { |
| iterpool = svn_pool_create(pool); |
| for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| const char *path, *token; |
| |
| svn_pool_clear(iterpool); |
| apr_hash_this(hi, &key, NULL, &val); |
| path = key; |
| token = val; |
| SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token)); |
| } |
| svn_pool_destroy(iterpool); |
| } |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks)); |
| SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table)); |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); |
| |
| /* Remember a few arguments for when the commit is over. */ |
| ccb = apr_palloc(pool, sizeof(*ccb)); |
| ccb->sess_baton = sess_baton; |
| ccb->pool = pool; |
| ccb->new_rev = NULL; |
| ccb->callback = callback; |
| ccb->callback_baton = callback_baton; |
| |
| /* Fetch an editor for the caller to drive. The editor will call |
| * ra_svn_end_commit() upon close_edit(), at which point we'll fill |
| * in the new_rev, committed_date, and committed_author values. */ |
| svn_ra_svn_get_editor(editor, edit_baton, conn, pool, |
| ra_svn_end_commit, ccb); |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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 svn_ra_svn__list_t *iproplist, |
| svn_ra_session_t *session, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| |
| { |
| int i; |
| apr_pool_t *iterpool; |
| |
| if (iproplist == NULL) |
| { |
| /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS |
| capability we shouldn't be asking for inherited props, but if we |
| did and the server sent back nothing then we'll want to handle |
| that. */ |
| *inherited_props = NULL; |
| return SVN_NO_ERROR; |
| } |
| |
| *inherited_props = apr_array_make( |
| result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *)); |
| |
| iterpool = svn_pool_create(scratch_pool); |
| |
| for (i = 0; i < iproplist->nelts; i++) |
| { |
| 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 = &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, |
| _("Inherited proplist element not a list")); |
| |
| svn_pool_clear(iterpool); |
| |
| 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); |
| new_iprop->prop_hash = svn_hash__make(result_pool); |
| for (hi = apr_hash_first(iterpool, iprops); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const char *name = apr_hash_this_key(hi); |
| svn_string_t *value = apr_hash_this_val(hi); |
| svn_hash_sets(new_iprop->prop_hash, |
| apr_pstrdup(result_pool, name), |
| svn_string_dup(value, result_pool)); |
| } |
| APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) = |
| new_iprop; |
| } |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, |
| svn_revnum_t rev, svn_stream_t *stream, |
| svn_revnum_t *fetched_rev, |
| apr_hash_t **props, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| 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)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl", |
| &expected_digest, |
| &rev, &proplist)); |
| |
| if (fetched_rev) |
| *fetched_rev = rev; |
| if (props) |
| SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); |
| |
| /* We're done if the contents weren't wanted. */ |
| if (!stream) |
| return SVN_NO_ERROR; |
| |
| if (expected_digest) |
| { |
| SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, |
| expected_digest, pool)); |
| checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); |
| } |
| |
| /* Read the file's contents. */ |
| iterpool = svn_pool_create(pool); |
| while (1) |
| { |
| 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) |
| break; |
| |
| if (expected_checksum) |
| 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_pool_destroy(iterpool); |
| |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); |
| |
| if (expected_checksum) |
| { |
| svn_checksum_t *checksum; |
| |
| SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool)); |
| if (!svn_checksum_match(checksum, expected_checksum)) |
| return svn_checksum_mismatch_err(expected_checksum, checksum, pool, |
| _("Checksum mismatch for '%s'"), |
| 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, |
| apr_hash_t **props, |
| const char *path, |
| svn_revnum_t rev, |
| apr_uint32_t dirent_fields, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| 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))); |
| 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 |
| to see "true" if it is omitted. */ |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE)); |
| |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist, |
| &dirlist)); |
| |
| if (fetched_rev) |
| *fetched_rev = rev; |
| if (props) |
| SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); |
| |
| /* We're done if dirents aren't wanted. */ |
| if (!dirents) |
| return SVN_NO_ERROR; |
| |
| /* Interpret the directory list. */ |
| *dirents = svn_hash__make(pool); |
| for (i = 0; i < dirlist->nelts; i++) |
| { |
| const char *name, *kind, *cdate, *cauthor; |
| svn_boolean_t has_props; |
| svn_dirent_t *dirent; |
| apr_uint64_t size; |
| svn_revnum_t crev; |
| 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, "cwnbr(?c)(?c)", |
| &name, &kind, &size, &has_props, |
| &crev, &cdate, &cauthor)); |
| |
| /* Nothing to sanitize here. Any multi-segment path is simply |
| illegal in the hash returned by svn_ra_get_dir2. */ |
| if (strchr(name, '/')) |
| return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Invalid directory entry name '%s'"), |
| name); |
| |
| dirent = svn_dirent_create(pool); |
| dirent->kind = svn_node_kind_from_word(kind); |
| dirent->size = size;/* FIXME: svn_filesize_t */ |
| dirent->has_props = has_props; |
| dirent->created_rev = crev; |
| /* NOTE: the tuple's format string says CDATE may be NULL. But this |
| function does not allow that. The server has always sent us some |
| random date, however, so this just happens to work. But let's |
| be wary of servers that are (improperly) fixed to send NULL. |
| |
| Note: they should NOT be "fixed" to send NULL, as that would break |
| any older clients which received that NULL. But we may as well |
| be defensive against a malicous server. */ |
| if (cdate == NULL) |
| dirent->time = 0; |
| else |
| SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool)); |
| dirent->last_author = cauthor; |
| svn_hash_sets(*dirents, name, dirent); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Converts a apr_uint64_t with values TRUE, FALSE or |
| SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple |
| to a svn_tristate_t */ |
| static svn_tristate_t |
| optbool_to_tristate(apr_uint64_t v) |
| { |
| if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */ |
| return svn_tristate_true; |
| if (v == FALSE) |
| return svn_tristate_false; |
| |
| return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */ |
| } |
| |
| /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the |
| server, which defaults to youngest. */ |
| static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session, |
| svn_mergeinfo_catalog_t *catalog, |
| const apr_array_header_t *paths, |
| svn_revnum_t revision, |
| svn_mergeinfo_inheritance_t inherit, |
| svn_boolean_t include_descendants, |
| 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; |
| 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++) |
| { |
| path = APR_ARRAY_IDX(paths, i, const char *); |
| SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); |
| } |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision, |
| svn_inheritance_to_word(inherit), |
| include_descendants)); |
| |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple)); |
| |
| *catalog = NULL; |
| if (mergeinfo_tuple->nelts > 0) |
| { |
| *catalog = svn_hash__make(pool); |
| for (i = 0; i < mergeinfo_tuple->nelts; i++) |
| { |
| svn_mergeinfo_t for_path; |
| const char *to_parse; |
| |
| 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, "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! */ |
| 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); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_update(svn_ra_session_t *session, |
| const svn_ra_reporter3_t **reporter, |
| void **report_baton, svn_revnum_t rev, |
| const char *target, svn_depth_t depth, |
| svn_boolean_t send_copyfrom_args, |
| svn_boolean_t ignore_ancestry, |
| const svn_delta_editor_t *update_editor, |
| void *update_baton, |
| apr_pool_t *pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| 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, |
| ignore_ancestry)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| |
| /* Fetch a reporter for the caller to drive. The reporter will drive |
| * update_editor upon finish_report(). */ |
| SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, |
| target, depth, reporter, report_baton)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| ra_svn_switch(svn_ra_session_t *session, |
| const svn_ra_reporter3_t **reporter, |
| void **report_baton, svn_revnum_t rev, |
| const char *target, svn_depth_t depth, |
| const char *switch_url, |
| svn_boolean_t send_copyfrom_args, |
| svn_boolean_t ignore_ancestry, |
| const svn_delta_editor_t *update_editor, |
| void *update_baton, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *pool = result_pool; |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| 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, |
| send_copyfrom_args, ignore_ancestry)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| |
| /* Fetch a reporter for the caller to drive. The reporter will drive |
| * update_editor upon finish_report(). */ |
| SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, |
| target, depth, reporter, report_baton)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_status(svn_ra_session_t *session, |
| const svn_ra_reporter3_t **reporter, |
| void **report_baton, |
| const char *target, svn_revnum_t rev, |
| svn_depth_t depth, |
| const svn_delta_editor_t *status_editor, |
| void *status_baton, apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| 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)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| |
| /* Fetch a reporter for the caller to drive. The reporter will drive |
| * status_editor upon finish_report(). */ |
| SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton, |
| target, depth, reporter, report_baton)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_diff(svn_ra_session_t *session, |
| const svn_ra_reporter3_t **reporter, |
| void **report_baton, |
| svn_revnum_t rev, const char *target, |
| svn_depth_t depth, |
| svn_boolean_t ignore_ancestry, |
| svn_boolean_t text_deltas, |
| const char *versus_url, |
| const svn_delta_editor_t *diff_editor, |
| void *diff_baton, apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| 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, |
| text_deltas, depth)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| |
| /* Fetch a reporter for the caller to drive. The reporter will drive |
| * diff_editor upon finish_report(). */ |
| SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton, |
| target, depth, reporter, report_baton)); |
| 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, |
| svn_ra_session_t *session, |
| const apr_array_header_t *paths, |
| svn_revnum_t start, svn_revnum_t end, |
| int limit, |
| svn_boolean_t discover_changed_paths, |
| svn_boolean_t strict_node_history, |
| svn_boolean_t include_merged_revisions, |
| const apr_array_header_t *revprops, |
| svn_log_entry_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; |
| apr_pool_t *iterpool; |
| int i; |
| int nest_level = 0; |
| const char *path; |
| char *name; |
| svn_boolean_t want_custom_revprops; |
| svn_boolean_t want_author = FALSE; |
| svn_boolean_t want_message = FALSE; |
| svn_boolean_t want_date = FALSE; |
| int nreceived = 0; |
| |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log")); |
| if (paths) |
| { |
| for (i = 0; i < paths->nelts; i++) |
| { |
| path = APR_ARRAY_IDX(paths, i, const char *); |
| SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); |
| } |
| } |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end, |
| discover_changed_paths, strict_node_history, |
| (apr_uint64_t) limit, |
| include_merged_revisions)); |
| if (revprops) |
| { |
| want_custom_revprops = FALSE; |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops")); |
| for (i = 0; i < revprops->nelts; i++) |
| { |
| name = APR_ARRAY_IDX(revprops, i, char *); |
| SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name)); |
| |
| if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0) |
| want_author = TRUE; |
| else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0) |
| want_date = TRUE; |
| else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0) |
| want_message = TRUE; |
| else |
| want_custom_revprops = TRUE; |
| } |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); |
| } |
| else |
| { |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops")); |
| |
| want_author = TRUE; |
| want_date = TRUE; |
| want_message = TRUE; |
| want_custom_revprops = TRUE; |
| } |
| |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| |
| /* Read the log messages. */ |
| iterpool = svn_pool_create(pool); |
| while (1) |
| { |
| apr_uint64_t has_children_param, invalid_revnum_param; |
| apr_uint64_t has_subtractive_merge_param; |
| svn_string_t *author, *date, *message; |
| 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; |
| apr_hash_t *cphash; |
| svn_revnum_t rev; |
| |
| svn_pool_clear(iterpool); |
| 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, |
| _("Log entry not a list")); |
| SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, |
| "lr(?s)(?s)(?s)?BBnl?B", |
| &cplist, &rev, &author, &date, |
| &message, &has_children_param, |
| &invalid_revnum_param, |
| &revprop_count, &rplist, |
| &has_subtractive_merge_param)); |
| if (want_custom_revprops && rplist == NULL) |
| { |
| /* Caller asked for custom revprops, but server is too old. */ |
| return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, |
| _("Server does not support custom revprops" |
| " via log")); |
| } |
| |
| if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) |
| has_children = FALSE; |
| else |
| has_children = (svn_boolean_t) has_children_param; |
| |
| if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) |
| subtractive_merge = FALSE; |
| else |
| subtractive_merge = (svn_boolean_t) has_subtractive_merge_param; |
| |
| /* Because the svn protocol won't let us send an invalid revnum, we have |
| to recover that fact using the extra parameter. */ |
| if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER |
| && invalid_revnum_param) |
| rev = SVN_INVALID_REVNUM; |
| |
| if (cplist->nelts > 0) |
| { |
| /* Interpret the changed-paths list. */ |
| cphash = svn_hash__make(iterpool); |
| for (i = 0; i < cplist->nelts; i++) |
| { |
| svn_log_changed_path2_t *change; |
| svn_string_t *cpath; |
| 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 = &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, |
| &cpath, &action, ©_path, |
| ©_rev, &kind_str, |
| &text_mods, &prop_mods)); |
| |
| if (!svn_fspath__is_canonical(cpath->data)) |
| { |
| cpath->data = svn_fspath__canonicalize(cpath->data, iterpool); |
| cpath->len = strlen(cpath->data); |
| } |
| if (copy_path && !svn_fspath__is_canonical(copy_path)) |
| copy_path = svn_fspath__canonicalize(copy_path, iterpool); |
| |
| change = svn_log_changed_path2_create(iterpool); |
| change->action = *action; |
| change->copyfrom_path = copy_path; |
| change->copyfrom_rev = copy_rev; |
| change->node_kind = svn_node_kind_from_word(kind_str); |
| change->text_modified = optbool_to_tristate(text_mods); |
| change->props_modified = optbool_to_tristate(prop_mods); |
| apr_hash_set(cphash, cpath->data, cpath->len, change); |
| } |
| } |
| else |
| cphash = NULL; |
| |
| /* Invoke RECEIVER |
| - 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 > 0) && (nest_level == 0) && (++nreceived > limit)) |
| && ! *outer_error) |
| { |
| svn_error_t *err; |
| log_entry = svn_log_entry_create(iterpool); |
| |
| log_entry->changed_paths = cphash; |
| log_entry->changed_paths2 = cphash; |
| log_entry->revision = rev; |
| log_entry->has_children = has_children; |
| log_entry->subtractive_merge = subtractive_merge; |
| if (rplist) |
| SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool, |
| &log_entry->revprops)); |
| if (log_entry->revprops == NULL) |
| log_entry->revprops = svn_hash__make(iterpool); |
| |
| if (author && want_author) |
| svn_hash_sets(log_entry->revprops, |
| SVN_PROP_REVISION_AUTHOR, author); |
| if (date && want_date) |
| svn_hash_sets(log_entry->revprops, |
| SVN_PROP_REVISION_DATE, date); |
| if (message && want_message) |
| svn_hash_sets(log_entry->revprops, |
| SVN_PROP_REVISION_LOG, message); |
| |
| err = receiver(receiver_baton, log_entry, iterpool); |
| if (svn_error_find_cause(err, SVN_ERR_CEASE_INVOCATION)) |
| { |
| *outer_error = svn_error_trace( |
| svn_error_compose_create(*outer_error, err)); |
| } |
| else |
| SVN_ERR(err); |
| |
| if (log_entry->has_children) |
| { |
| nest_level++; |
| } |
| if (! SVN_IS_VALID_REVNUM(log_entry->revision)) |
| { |
| SVN_ERR_ASSERT(nest_level); |
| nest_level--; |
| } |
| } |
| } |
| svn_pool_destroy(iterpool); |
| |
| /* Read the response. */ |
| return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "")); |
| } |
| |
| static svn_error_t * |
| ra_svn_log(svn_ra_session_t *session, |
| const apr_array_header_t *paths, |
| svn_revnum_t start, svn_revnum_t end, |
| int limit, |
| svn_boolean_t discover_changed_paths, |
| svn_boolean_t strict_node_history, |
| svn_boolean_t include_merged_revisions, |
| const apr_array_header_t *revprops, |
| svn_log_entry_receiver_t receiver, |
| void *receiver_baton, apr_pool_t *pool) |
| { |
| 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, |
| limit, |
| discover_changed_paths, |
| strict_node_history, |
| include_merged_revisions, |
| revprops, |
| receiver, receiver_baton, |
| pool)); |
| return svn_error_trace( |
| svn_error_compose_create(outer_error, |
| err)); |
| } |
| |
| |
| |
| static svn_error_t *ra_svn_check_path(svn_ra_session_t *session, |
| const char *path, svn_revnum_t rev, |
| svn_node_kind_t *kind, apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| 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)); |
| *kind = svn_node_kind_from_word(kind_word); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* If ERR is a command not supported error, wrap it in a |
| SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */ |
| static svn_error_t *handle_unsupported_cmd(svn_error_t *err, |
| const char *msg) |
| { |
| if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) |
| return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, |
| _(msg)); |
| return err; |
| } |
| |
| |
| static svn_error_t *ra_svn_stat(svn_ra_session_t *session, |
| const char *path, svn_revnum_t rev, |
| svn_dirent_t **dirent, apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| 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"))); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); |
| |
| if (! list) |
| { |
| *dirent = NULL; |
| } |
| else |
| { |
| const char *kind, *cdate, *cauthor; |
| svn_boolean_t has_props; |
| svn_revnum_t crev; |
| apr_uint64_t size; |
| |
| SVN_ERR(svn_ra_svn__parse_tuple(list, "wnbr(?c)(?c)", |
| &kind, &size, &has_props, |
| &crev, &cdate, &cauthor)); |
| |
| the_dirent = svn_dirent_create(pool); |
| the_dirent->kind = svn_node_kind_from_word(kind); |
| the_dirent->size = size;/* FIXME: svn_filesize_t */ |
| the_dirent->has_props = has_props; |
| the_dirent->created_rev = crev; |
| SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool)); |
| the_dirent->last_author = cauthor; |
| |
| *dirent = the_dirent; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, |
| apr_hash_t **locations, |
| const char *path, |
| svn_revnum_t peg_revision, |
| const apr_array_header_t *location_revisions, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| 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)); |
| for (i = 0; i < location_revisions->nelts; i++) |
| { |
| revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t); |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision)); |
| } |
| |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); |
| |
| /* Servers before 1.1 don't support this command. Check for this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), |
| N_("'get-locations' not implemented"))); |
| |
| /* 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; |
| const char *ret_path; |
| |
| 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, "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)), |
| sizeof(revision), ret_path); |
| } |
| } |
| |
| 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 * |
| 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; |
| svn_boolean_t is_done; |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| |
| /* Transmit the parameters. */ |
| SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))", |
| "get-location-segments", |
| path, peg_revision, start_rev, end_rev)); |
| |
| /* Servers before 1.5 don't support this command. Check for this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), |
| N_("'get-location-segments'" |
| " not implemented"))); |
| |
| /* Parse the response. */ |
| is_done = FALSE; |
| while (!is_done) |
| { |
| svn_revnum_t range_start, range_end; |
| 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 (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 segment entry not a list")); |
| else |
| { |
| svn_location_segment_t *segment = apr_pcalloc(iterpool, |
| sizeof(*segment)); |
| 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))) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Expected valid revision range")); |
| if (ret_path) |
| ret_path = svn_relpath_canonicalize(ret_path, iterpool); |
| segment->path = ret_path; |
| segment->range_start = range_start; |
| segment->range_end = range_end; |
| |
| 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); |
| |
| /* Read the response. This is so the server would have a chance to |
| * report an error. */ |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); |
| |
| 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, |
| svn_boolean_t include_merged_revisions, |
| svn_file_rev_handler_t handler, |
| void *handler_baton, apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| apr_pool_t *rev_pool, *chunk_pool; |
| svn_boolean_t has_txdelta; |
| svn_boolean_t had_revision = FALSE; |
| |
| /* One sub-pool for each revision and one for each txdelta chunk. |
| Note that the rev_pool must live during the following txdelta. */ |
| 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)); |
| |
| /* Servers before 1.1 don't support this command. Check for this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), |
| N_("'get-file-revs' not implemented"))); |
| |
| while (1) |
| { |
| 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; |
| apr_hash_t *rev_props; |
| svn_revnum_t rev; |
| const char *p; |
| svn_boolean_t merged_rev; |
| svn_txdelta_window_handler_t d_handler; |
| void *d_baton; |
| |
| svn_pool_clear(rev_pool); |
| svn_pool_clear(chunk_pool); |
| SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item)); |
| if (is_done_response(item)) |
| break; |
| /* Either we've got a correct revision or we will error out below. */ |
| had_revision = TRUE; |
| if (item->kind != SVN_RA_SVN_LIST) |
| 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, |
| "crll?B", &p, &rev, &rev_proplist, |
| &proplist, &merged_rev_param)); |
| p = svn_fspath__canonicalize(p, rev_pool); |
| SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props)); |
| SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props)); |
| if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) |
| merged_rev = FALSE; |
| else |
| merged_rev = (svn_boolean_t) merged_rev_param; |
| |
| /* Get the first delta chunk so we know if there is a delta. */ |
| SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item)); |
| 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; |
| |
| SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev, |
| has_txdelta ? &d_handler : NULL, &d_baton, |
| props, rev_pool)); |
| |
| /* Process the text delta if any. */ |
| if (has_txdelta) |
| { |
| svn_stream_t *stream; |
| |
| if (d_handler && d_handler != svn_delta_noop_window_handler) |
| stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE, |
| rev_pool); |
| else |
| stream = NULL; |
| while (item->u.string.len > 0) |
| { |
| apr_size_t size; |
| |
| size = item->u.string.len; |
| if (stream) |
| 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, |
| &item)); |
| if (item->kind != SVN_RA_SVN_STRING) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Text delta chunk not a string")); |
| } |
| if (stream) |
| SVN_ERR(svn_stream_close(stream)); |
| } |
| } |
| |
| SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, "")); |
| |
| /* Return error if we didn't get any revisions. */ |
| if (!had_revision) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("The get-file-revs command didn't return " |
| "any revisions")); |
| |
| svn_pool_destroy(chunk_pool); |
| svn_pool_destroy(rev_pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* For each path in 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 steal_lock, |
| svn_ra_lock_callback_t lock_func, |
| void *lock_baton, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess = session->priv; |
| svn_ra_svn_conn_t* conn = sess->conn; |
| svn_ra_svn__list_t *list; |
| apr_hash_index_t *hi; |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| |
| for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) |
| { |
| svn_lock_t *lock; |
| const void *key; |
| const char *path; |
| void *val; |
| svn_revnum_t *revnum; |
| svn_error_t *err, *callback_err = NULL; |
| |
| svn_pool_clear(iterpool); |
| |
| apr_hash_this(hi, &key, NULL, &val); |
| path = key; |
| revnum = val; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment, |
| steal_lock, *revnum)); |
| |
| /* Servers before 1.2 doesn't support locking. Check this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), |
| N_("Server doesn't support " |
| "the lock command"))); |
| |
| err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list); |
| |
| if (!err) |
| SVN_ERR(parse_lock(list, iterpool, &lock)); |
| |
| if (err && !SVN_ERR_IS_LOCK_ERROR(err)) |
| return err; |
| |
| if (lock_func) |
| callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock, |
| err, iterpool); |
| |
| svn_error_clear(err); |
| |
| if (callback_err) |
| return callback_err; |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| 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 |
| 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 break_lock, |
| svn_ra_lock_callback_t lock_func, |
| void *lock_baton, |
| apr_pool_t *pool) |
| { |
| svn_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); |
| |
| for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| const char *path; |
| void *val; |
| const svn_string_t *token; |
| svn_error_t *err, *callback_err = NULL; |
| |
| svn_pool_clear(iterpool); |
| |
| apr_hash_this(hi, &key, NULL, &val); |
| path = key; |
| if (strcmp(val, "") != 0) |
| token = svn_string_create(val, iterpool); |
| else |
| token = NULL; |
| |
| SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token, |
| break_lock)); |
| |
| /* Servers before 1.2 don't support locking. Check this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool), |
| N_("Server doesn't support the unlock " |
| "command"))); |
| |
| err = svn_ra_svn__read_cmd_response(conn, iterpool, ""); |
| |
| if (err && !SVN_ERR_IS_UNLOCK_ERROR(err)) |
| return err; |
| |
| if (lock_func) |
| callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool); |
| |
| svn_error_clear(err); |
| |
| if (callback_err) |
| return callback_err; |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Tell the server to lock all paths in PATH_REVS. |
| See svn_ra_lock() 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 steal_lock, |
| svn_ra_lock_callback_t lock_func, |
| void *lock_baton, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess = session->priv; |
| svn_ra_svn_conn_t *conn = sess->conn; |
| apr_hash_index_t *hi; |
| 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)); |
| |
| 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(iterpool); |
| apr_hash_this(hi, &key, NULL, &val); |
| path = key; |
| revnum = val; |
| |
| SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "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) |
| { |
| svn_error_clear(err); |
| return ra_svn_lock_compat(session, path_revs, comment, steal_lock, |
| lock_func, lock_baton, pool); |
| } |
| |
| if (err) |
| return err; |
| |
| /* 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; |
| const void *key; |
| const char *path; |
| svn_error_t *callback_err; |
| const char *status; |
| svn_lock_t *lock; |
| svn_ra_svn__list_t *list; |
| |
| apr_hash_this(hi, &key, NULL, NULL); |
| path = key; |
| |
| svn_pool_clear(iterpool); |
| SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); |
| |
| /* The server might have encountered some sort of fatal error in |
| 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 (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, "wl", &status, &list)); |
| |
| if (strcmp(status, "failure") == 0) |
| err = svn_ra_svn__handle_failure_status(list); |
| else if (strcmp(status, "success") == 0) |
| { |
| SVN_ERR(parse_lock(list, iterpool, &lock)); |
| err = NULL; |
| } |
| else |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Unknown status for lock command")); |
| |
| if (lock_func) |
| callback_err = lock_func(lock_baton, path, TRUE, |
| err ? NULL : lock, |
| err, iterpool); |
| else |
| callback_err = SVN_NO_ERROR; |
| |
| svn_error_clear(err); |
| |
| if (callback_err) |
| return callback_err; |
| } |
| |
| /* If we didn't break early above, and the whole hash was traversed, |
| read the final "done" from the server. */ |
| if (!hi) |
| { |
| svn_ra_svn__item_t *elt; |
| |
| SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); |
| if (!is_done_response(elt)) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Didn't receive end marker for lock " |
| "responses")); |
| } |
| |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Tell the server to unlock all paths in PATH_TOKENS. |
| See svn_ra_unlock() for interface details. */ |
| static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, |
| apr_hash_t *path_tokens, |
| svn_boolean_t break_lock, |
| svn_ra_lock_callback_t lock_func, |
| void *lock_baton, |
| apr_pool_t *pool) |
| { |
| svn_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); |
| 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)); |
| |
| for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) |
| { |
| void *val; |
| const void *key; |
| const char *token; |
| |
| svn_pool_clear(iterpool); |
| 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, iterpool, "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) |
| { |
| svn_error_clear(err); |
| return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func, |
| lock_baton, pool); |
| } |
| |
| if (err) |
| return err; |
| |
| /* 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; |
| const void *key; |
| svn_error_t *callback_err; |
| const char *status; |
| svn_ra_svn__list_t *list; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); |
| |
| /* The server might have encountered some sort of fatal error in |
| 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 (is_done_response(elt)) |
| break; |
| |
| apr_hash_this(hi, &key, NULL, NULL); |
| path = key; |
| |
| if (elt->kind != SVN_RA_SVN_LIST) |
| 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, "wl", &status, &list)); |
| |
| if (strcmp(status, "failure") == 0) |
| err = svn_ra_svn__handle_failure_status(list); |
| else if (strcmp(status, "success") == 0) |
| { |
| SVN_ERR(svn_ra_svn__parse_tuple(list, "c", &path)); |
| err = SVN_NO_ERROR; |
| } |
| else |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Unknown status for unlock command")); |
| |
| if (lock_func) |
| callback_err = lock_func(lock_baton, path, FALSE, NULL, err, |
| iterpool); |
| else |
| callback_err = SVN_NO_ERROR; |
| |
| svn_error_clear(err); |
| |
| if (callback_err) |
| return callback_err; |
| } |
| |
| /* If we didn't break early above, and the whole hash was traversed, |
| read the final "done" from the server. */ |
| if (!hi) |
| { |
| svn_ra_svn__item_t *elt; |
| |
| SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); |
| if (! is_done_response(elt)) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Didn't receive end marker for unlock " |
| "responses")); |
| } |
| |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session, |
| svn_lock_t **lock, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess = session->priv; |
| svn_ra_svn_conn_t* conn = sess->conn; |
| 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. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), |
| N_("Server doesn't support the get-lock " |
| "command"))); |
| |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); |
| if (list) |
| SVN_ERR(parse_lock(list, pool, lock)); |
| else |
| *lock = NULL; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized |
| to prevent a dependency cycle. */ |
| static svn_error_t *path_relative_to_root(svn_ra_session_t *session, |
| const char **rel_path, |
| const char *url, |
| apr_pool_t *pool) |
| { |
| const char *root_url; |
| |
| SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool)); |
| *rel_path = svn_uri_skip_ancestor(root_url, url, pool); |
| if (! *rel_path) |
| return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, |
| _("'%s' isn't a child of repository root " |
| "URL '%s'"), |
| url, root_url); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, |
| apr_hash_t **locks, |
| const char *path, |
| svn_depth_t depth, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess = session->priv; |
| svn_ra_svn_conn_t* conn = sess->conn; |
| 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->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); |
| |
| SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth)); |
| |
| /* Servers before 1.2 doesn't support locking. Check this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), |
| N_("Server doesn't support the get-lock " |
| "command"))); |
| |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list)); |
| |
| *locks = apr_hash_make(pool); |
| |
| for (i = 0; i < list->nelts; ++i) |
| { |
| svn_lock_t *lock; |
| 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)); |
| |
| /* Filter out unwanted paths. Since Subversion only allows |
| locks on files, we can treat depth=immediates the same as |
| depth=files for filtering purposes. Meaning, we'll keep |
| this lock if: |
| |
| a) its path is the very path we queried, or |
| b) we've asked for a fully recursive answer, or |
| c) we've asked for depth=files or depth=immediates, and this |
| lock is on an immediate child of our query path. |
| */ |
| if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity)) |
| { |
| svn_hash_sets(*locks, lock->path, lock); |
| } |
| else if ((depth == svn_depth_files) || (depth == svn_depth_immediates)) |
| { |
| const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path); |
| if (relpath && (svn_path_component_count(relpath) == 1)) |
| svn_hash_sets(*locks, lock->path, lock); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t *ra_svn_replay(svn_ra_session_t *session, |
| svn_revnum_t revision, |
| svn_revnum_t low_water_mark, |
| svn_boolean_t send_deltas, |
| const svn_delta_editor_t *editor, |
| void *edit_baton, |
| apr_pool_t *pool) |
| { |
| 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)); |
| |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), |
| N_("Server doesn't support the replay " |
| "command"))); |
| |
| SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton, |
| NULL, TRUE)); |
| |
| return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, "")); |
| } |
| |
| |
| static svn_error_t * |
| ra_svn_replay_range(svn_ra_session_t *session, |
| svn_revnum_t start_revision, |
| svn_revnum_t end_revision, |
| svn_revnum_t low_water_mark, |
| svn_boolean_t send_deltas, |
| svn_ra_replay_revstart_callback_t revstart_func, |
| svn_ra_replay_revfinish_callback_t revfinish_func, |
| void *replay_baton, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess = session->priv; |
| apr_pool_t *iterpool; |
| 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)); |
| |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), |
| N_("Server doesn't support the " |
| "replay-range command"))); |
| |
| iterpool = svn_pool_create(pool); |
| for (rev = start_revision; rev <= end_revision; rev++) |
| { |
| const svn_delta_editor_t *editor; |
| void *edit_baton; |
| apr_hash_t *rev_props; |
| const char *word; |
| 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) |
| { |
| 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)); |
| |
| SVN_ERR(revstart_func(rev, replay_baton, |
| &editor, &edit_baton, |
| rev_props, |
| iterpool)); |
| SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool, |
| editor, edit_baton, |
| &drive_aborted, TRUE)); |
| /* If drive_editor2() aborted the commit, do NOT try to call |
| revfinish_func and commit the transaction! */ |
| if (drive_aborted) { |
| svn_pool_destroy(iterpool); |
| return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL, |
| _("Error while replaying commit")); |
| } |
| SVN_ERR(revfinish_func(rev, replay_baton, |
| editor, edit_baton, |
| rev_props, |
| iterpool)); |
| } |
| svn_pool_destroy(iterpool); |
| |
| return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, "")); |
| } |
| |
| |
| static svn_error_t * |
| ra_svn_has_capability(svn_ra_session_t *session, |
| svn_boolean_t *has, |
| const char *capability, |
| apr_pool_t *pool) |
| { |
| svn_ra_svn__session_baton_t *sess = session->priv; |
| static const char* capabilities[][2] = |
| { |
| /* { ra capability string, svn:// wire capability string} */ |
| {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH}, |
| {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO}, |
| {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS}, |
| {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY}, |
| {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS}, |
| {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS}, |
| {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS}, |
| {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, |
| 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 */ |
| }; |
| int i; |
| |
| *has = FALSE; |
| |
| for (i = 0; capabilities[i][0]; i++) |
| { |
| if (strcmp(capability, capabilities[i][0]) == 0) |
| { |
| *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]); |
| return SVN_NO_ERROR; |
| } |
| } |
| |
| return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL, |
| _("Don't know anything about capability '%s'"), |
| capability); |
| } |
| |
| static svn_error_t * |
| ra_svn_get_deleted_rev(svn_ra_session_t *session, |
| const char *path, |
| svn_revnum_t peg_revision, |
| svn_revnum_t end_revision, |
| svn_revnum_t *revision_deleted, |
| apr_pool_t *pool) |
| |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| svn_error_t *err; |
| |
| 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)); |
| |
| /* Servers before 1.6 don't support this command. Check for this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), |
| N_("'get-deleted-rev' not implemented"))); |
| |
| err = svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r", |
| revision_deleted)); |
| /* The protocol does not allow for a reply of SVN_INVALID_REVNUM directly. |
| Instead, a new enough server returns SVN_ERR_ENTRY_MISSING_REVISION to |
| indicate the answer to the query is SVN_INVALID_REVNUM. (An older server |
| closes the connection and returns SVN_ERR_RA_SVN_CONNECTION_CLOSED.) */ |
| if (err && err->apr_err == SVN_ERR_ENTRY_MISSING_REVISION) |
| { |
| *revision_deleted = SVN_INVALID_REVNUM; |
| svn_error_clear(err); |
| } |
| else |
| SVN_ERR(err); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session, |
| svn_delta_shim_callbacks_t *callbacks) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| conn->shim_callbacks = callbacks; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| ra_svn_get_inherited_props(svn_ra_session_t *session, |
| apr_array_header_t **iprops, |
| const char *path, |
| svn_revnum_t revision, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_ra_svn__session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| 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)); |
| |
| /* If we don't support native iprop handling, use the implementation |
| in libsvn_ra */ |
| if (!iprop_capable) |
| return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); |
| |
| SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool, |
| path, revision)); |
| SVN_ERR(handle_auth_request(sess_baton, scratch_pool)); |
| SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist)); |
| SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool, |
| scratch_pool)); |
| |
| 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, |
| ra_svn_get_schemes, |
| ra_svn_open, |
| ra_svn_dup_session, |
| ra_svn_reparent, |
| ra_svn_get_session_url, |
| ra_svn_get_latest_rev, |
| ra_svn_get_dated_rev, |
| ra_svn_change_rev_prop, |
| ra_svn_rev_proplist, |
| ra_svn_rev_prop, |
| ra_svn_commit, |
| ra_svn_get_file, |
| ra_svn_get_dir, |
| ra_svn_get_mergeinfo, |
| ra_svn_update, |
| ra_svn_switch, |
| ra_svn_status, |
| ra_svn_diff, |
| ra_svn_log, |
| ra_svn_check_path, |
| ra_svn_stat, |
| ra_svn_get_uuid, |
| ra_svn_get_repos_root, |
| ra_svn_get_locations, |
| ra_svn_get_location_segments, |
| ra_svn_get_file_revs, |
| ra_svn_lock, |
| ra_svn_unlock, |
| ra_svn_get_lock, |
| ra_svn_get_locks, |
| ra_svn_replay, |
| 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, |
| NULL /* commit_ev2 */, |
| NULL /* replay_range_ev2 */ |
| }; |
| |
| svn_error_t * |
| svn_ra_svn__init(const svn_version_t *loader_version, |
| const svn_ra__vtable_t **vtable, |
| apr_pool_t *pool) |
| { |
| static const svn_version_checklist_t checklist[] = |
| { |
| { "svn_subr", svn_subr_version }, |
| { "svn_delta", svn_delta_version }, |
| { NULL, NULL } |
| }; |
| |
| SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal)); |
| |
| /* Simplified version check to make sure we can safely use the |
| VTABLE parameter. The RA loader does a more exhaustive check. */ |
| if (loader_version->major != SVN_VER_MAJOR) |
| { |
| return svn_error_createf |
| (SVN_ERR_VERSION_MISMATCH, NULL, |
| _("Unsupported RA loader version (%d) for ra_svn"), |
| loader_version->major); |
| } |
| |
| *vtable = &ra_svn_vtable; |
| |
| #ifdef SVN_HAVE_SASL |
| SVN_ERR(svn_ra_svn__sasl_init()); |
| #endif |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Compatibility wrapper for the 1.1 and before API. */ |
| #define NAME "ra_svn" |
| #define DESCRIPTION RA_SVN_DESCRIPTION |
| #define VTBL ra_svn_vtable |
| #define INITFUNC svn_ra_svn__init |
| #define COMPAT_INITFUNC svn_ra_svn_init |
| #include "../libsvn_ra/wrapper_template.h" |