Locking branch: fix lock scalability problem in ra_dav commits. Work around apache's limitations on long-headers; for a final commit MERGE request, send all lock-tokens in the request body instead of a header. Have mod_dav_svn capture the MERGE body with an input filter. This is a short-term solution to work with existing httpd-2.0.x. The longer-term solution is to change mod_dav's API in future httpd releases. Input-filter writ by jerenkrantz. Thanks, justin! * subversion/libsvn_ra_dav/merge.c (svn_ra_dav__merge_activity): marshal the whole path->token hash to the server in the MERGE request body instead of 'If:' header. * subversion/mod_dav_svn/mod_dav_svn.c (merge_xml_filter_insert): new filter insertion helper. (merge_cxt_t): new context structure. (register_hooks): register and insert the new input filter. (merge_xml_in_filter): new input filter to capture MERGE request body and stash it away for later use. * subversion/mod_dav_svn/version.c (dav_svn_process_if_header, dav_svn_fetch_next_token, dav_svn_add_if_state, dav_svn_add_if_resource): remove these hacky duplicated funcs. (build_lock_hash): reimplement to fetch locks from the MERGE request body, rather than the 'If:' header. (dav_svn_merge): grab incoming locks and push them into the fs before committing. After the commit is done, possibly release them. Index: subversion/mod_dav_svn/mod_dav_svn.c =================================================================== --- subversion/mod_dav_svn/mod_dav_svn.c (revision 13288) +++ subversion/mod_dav_svn/mod_dav_svn.c (working copy) @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -289,7 +290,113 @@ return conf->do_path_authz != DAV_SVN_FLAG_OFF; } +static void merge_xml_filter_insert(request_rec *r) +{ + /* We only care about MERGE requests. */ + if (r->method_number == M_MERGE) { + dav_svn_dir_conf *conf; + conf = ap_get_module_config(r->per_dir_config, &dav_svn_module); + /* We only care if we are configured. */ + if (conf->fs_path || conf->fs_parent_path) { + ap_add_input_filter("SVN-MERGE", NULL, r, r->connection); + } + } +} + +typedef struct { + apr_bucket_brigade *bb; + apr_xml_parser *parser; + apr_pool_t *pool; +} merge_ctx_t; + +static apr_status_t merge_xml_in_filter(ap_filter_t *f, + apr_bucket_brigade *bb, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + apr_status_t rv; + request_rec *r = f->r; + merge_ctx_t *ctx = f->ctx; + apr_bucket *bucket; + int seen_eos = 0; + + /* We shouldn't be added if we're not a MERGE, but double check. */ + if (r->method_number != M_MERGE) { + ap_remove_input_filter(f); + return ap_get_brigade(f->next, bb, mode, block, readbytes); + } + + if (!ctx) { + f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx)); + ctx->parser = apr_xml_parser_create(r->pool); + ctx->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + apr_pool_create(&ctx->pool, r->pool); + } + + rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes); + + if (rv != APR_SUCCESS) { + return rv; + } + + for (bucket = APR_BRIGADE_FIRST(ctx->bb); + bucket != APR_BRIGADE_SENTINEL(ctx->bb); + bucket = APR_BUCKET_NEXT(bucket)) + { + const char *data; + apr_size_t len; + + if (APR_BUCKET_IS_EOS(bucket)) { + seen_eos = 1; + break; + } + + if (APR_BUCKET_IS_METADATA(bucket)) { + continue; + } + + rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + return rv; + } + + rv = apr_xml_parser_feed(ctx->parser, data, len); + if (rv != APR_SUCCESS) { + /* Clean up the parser. */ + (void) apr_xml_parser_done(ctx->parser, NULL); + break; + } + } + + /* This will clear-out the ctx->bb as well. */ + APR_BRIGADE_CONCAT(bb, ctx->bb); + + if (seen_eos) { + apr_xml_doc *pdoc; + + /* Remove ourselves now. */ + ap_remove_input_filter(f); + + /* tell the parser that we're done */ + rv = apr_xml_parser_done(ctx->parser, &pdoc); + if (rv == APR_SUCCESS) { +#if APR_CHARSET_EBCDIC + apr_xml_parser_convert_doc(r->pool, pdoc, ap_hdrs_from_ascii); +#endif + /* stash the doc away for mod_dav_svn's later use. */ + rv = apr_pool_userdata_set(pdoc, "svn-merge-body", NULL, r->pool); + if (rv != APR_SUCCESS) { + return rv; + } + + } + } + + return APR_SUCCESS; +} + /** Module framework stuff **/ @@ -356,6 +463,12 @@ /* our provider */ dav_register_provider(pconf, "svn", &dav_svn_provider); + /* input filter to read MERGE bodies. */ + ap_register_input_filter("SVN-MERGE", merge_xml_in_filter, NULL, + AP_FTYPE_RESOURCE); + ap_hook_insert_filter(merge_xml_filter_insert, NULL, NULL, + APR_HOOK_MIDDLE); + /* live property handling */ dav_hook_gather_propsets(dav_svn_gather_propsets, NULL, NULL, APR_HOOK_MIDDLE); Index: subversion/mod_dav_svn/version.c =================================================================== --- subversion/mod_dav_svn/version.c (revision 13288) +++ subversion/mod_dav_svn/version.c (working copy) @@ -1308,306 +1308,118 @@ } -/* ----------------------------------------------------------------------- */ -/* ### TEMPORARY HACK: REMOVE THESE FUNCTIONS when we stop pulling - the hash from the If: header and start parsing the request body! - It's only here because mod_dav doesn't export it, and - build_lock_hash() needs to parse the If: header. */ - -static dav_if_header *dav_svn_add_if_resource(apr_pool_t *p, - dav_if_header *next_ih, - const char *uri, - apr_size_t uri_len) -{ - dav_if_header *ih; - - if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL) - return NULL; - - ih->uri = uri; - ih->uri_len = uri_len; - ih->next = next_ih; - - return ih; -} - - -static dav_error * dav_svn_add_if_state(apr_pool_t *p, dav_if_header *ih, - const char *state_token, - dav_if_state_type t, int condition, - const dav_hooks_locks *locks_hooks) -{ - dav_if_state_list *new_sl; - - new_sl = apr_pcalloc(p, sizeof(*new_sl)); - - new_sl->condition = condition; - new_sl->type = t; - - if (t == dav_if_opaquelock) { - dav_error *err; - - if ((err = (*locks_hooks->parse_locktoken)(p, state_token, - &new_sl->locktoken)) != NULL) { - /* In cases where the state token is invalid, we'll just skip - * it rather than return 400. - */ - if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) { - return NULL; - } - else { - /* ### maybe add a higher-level description */ - return err; - } - } - } - else - new_sl->etag = state_token; - - new_sl->next = ih->state; - ih->state = new_sl; - - return NULL; -} - - -static char *dav_svn_fetch_next_token(char **str, char term) -{ - char *sp; - char *token; - - token = *str + 1; - - while (*token && (*token == ' ' || *token == '\t')) - token++; - - if ((sp = strchr(token, term)) == NULL) - return NULL; - - *sp = '\0'; - *str = sp; - return token; -} - - -static dav_error * dav_svn_process_if_header(request_rec *r, - dav_if_header **p_ih) -{ - dav_error *err; - char *str; - char *list; - const char *state_token; - const char *uri = NULL; /* scope of current production; NULL=no-tag */ - apr_size_t uri_len = 0; - dav_if_header *ih = NULL; - apr_uri_t parsed_uri; - const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); - enum {no_tagged, tagged, unknown} list_type = unknown; - int condition; - - *p_ih = NULL; - - if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL) - return NULL; - - while (*str) { - switch(*str) { - case '<': - /* Tagged-list production - following states apply to this uri */ - if (list_type == no_tagged - || ((uri = dav_svn_fetch_next_token(&str, '>')) == NULL)) { - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_TAGGED, - "Invalid If-header: unclosed \"<\" or " - "unexpected tagged-list production."); - } - - /* 2518 specifies this must be an absolute URI; just take the - * relative part for later comparison against r->uri */ - if (apr_uri_parse(r->pool, uri, &parsed_uri) != APR_SUCCESS) { - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_TAGGED, - "Invalid URI in tagged If-header."); - } - /* note that parsed_uri.path is allocated; we can trash it */ - - /* clean up the URI a bit */ - ap_getparents(parsed_uri.path); - uri_len = strlen(parsed_uri.path); - if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/') - parsed_uri.path[--uri_len] = '\0'; - - uri = parsed_uri.path; - list_type = tagged; - break; - - case '(': - /* List production */ - - /* If a uri has not been encountered, this is a No-Tagged-List */ - if (list_type == unknown) - list_type = no_tagged; - - if ((list = dav_svn_fetch_next_token(&str, ')')) == NULL) { - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_UNCLOSED_PAREN, - "Invalid If-header: unclosed \"(\"."); - } - - if ((ih = dav_svn_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) { - /* ### dav_add_if_resource() should return an error for us! */ - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_PARSE, - "Internal server error parsing \"If:\" " - "header."); - } - - condition = DAV_IF_COND_NORMAL; - - while (*list) { - /* List is the entire production (in a uri scope) */ - - switch (*list) { - case '<': - if ((state_token = dav_svn_fetch_next_token(&list, '>')) == NULL) { - /* ### add a description to this error */ - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_PARSE, NULL); - } - - if ((err = dav_svn_add_if_state(r->pool, ih, - state_token, dav_if_opaquelock, - condition, locks_hooks)) != NULL) { - /* ### maybe add a higher level description */ - return err; - } - condition = DAV_IF_COND_NORMAL; - break; - - case '[': - if ((state_token = dav_svn_fetch_next_token(&list, ']')) == NULL) { - /* ### add a description to this error */ - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_PARSE, NULL); - } - - if ((err = dav_svn_add_if_state(r->pool, ih, state_token, - dav_if_etag, - condition, locks_hooks)) != NULL) { - /* ### maybe add a higher level description */ - return err; - } - condition = DAV_IF_COND_NORMAL; - break; - - case 'N': - if (list[1] == 'o' && list[2] == 't') { - if (condition != DAV_IF_COND_NORMAL) { - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_MULTIPLE_NOT, - "Invalid \"If:\" header: " - "Multiple \"not\" entries " - "for the same state."); - } - condition = DAV_IF_COND_NOT; - } - list += 2; - break; - - case ' ': - case '\t': - break; - - default: - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_UNK_CHAR, - apr_psprintf(r->pool, - "Invalid \"If:\" " - "header: Unexpected " - "character encountered " - "(0x%02x, '%c').", - *list, *list)); - } - - list++; - } - break; - - case ' ': - case '\t': - break; - - default: - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_UNK_CHAR, - apr_psprintf(r->pool, - "Invalid \"If:\" header: " - "Unexpected character " - "encountered (0x%02x, '%c').", - *str, *str)); - } - - str++; - } - - *p_ih = ih; - return NULL; -} - /* -------------------------------------------------------------------- */ /* Helper for dav_svn_merge(). Return a hash that maps (const char *) absolute fs paths to (const char *) locktokens. Allocate the hash - and all keys/vals in POOL. BASE_URI is the uri sent in the MERGE - request. - - ### This will change later to scan an XML body document. For now, - it reads the data out of the request's If: header. + and all keys/vals in POOL. PATH_PREFIX is the prefix we need to + prepend to each relative 'lock-path' in the xml in order to create + an absolute fs-path. */ static dav_error *build_lock_hash(apr_hash_t **locks, request_rec *r, - const char *base_uri, + const char *path_prefix, apr_pool_t *pool) { - dav_error *err; - dav_if_header *ih; + apr_status_t apr_err; + void *data = NULL; + apr_xml_doc *doc = NULL; + apr_xml_elem *child, *lockchild; + int ns; apr_hash_t *hash = apr_hash_make(pool); + + /* Grab the MERGE body out of r->pool, as it contains all of the + lock tokens. It should have been stashed already by our custom + input filter. */ + apr_err = apr_pool_userdata_get(&data, "svn-merge-body", r->pool); + if (apr_err) + return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL), + HTTP_INTERNAL_SERVER_ERROR, + "Error fetching pool userdata.", + pool); + doc = data; + if (! doc) + { + *locks = hash; + return SVN_NO_ERROR; + } + + /* Sanity check. */ + ns = dav_svn_find_ns(doc->namespaces, SVN_XML_NAMESPACE); + if (ns == -1) + { + return dav_new_error_tag(pool, HTTP_BAD_REQUEST, 0, + "The request does not contain the 'svn:' " + "namespace, so it is not going to have certain " + "required elements.", + SVN_DAV_ERROR_NAMESPACE, + SVN_DAV_ERROR_TAG); + } - err = dav_svn_process_if_header(r, &ih); - if (err) - return err; + /* Search all the doc's children until we find the . */ + for (child = doc->root->first_child; child != NULL; child = child->next) + { + /* if this element isn't one of ours, then skip it */ + if (child->ns != ns) + continue; - if (ih != NULL) + if (strcmp(child->name, "lock-token-list") == 0) + break; + } + + /* Then look for N different structures within. */ + for (lockchild = child->first_child; lockchild != NULL; + lockchild = lockchild->next) { - dav_if_header *this_if = ih; - - do + if (strcmp(lockchild->name, "lock") == 0) { - if (this_if->uri - && this_if->state - && (this_if->state->type == dav_if_opaquelock)) + const char *lockpath = NULL, *locktoken = NULL; + apr_xml_elem *lfchild; + + for (lfchild = lockchild->first_child; lfchild != NULL; + lfchild = lfchild->next) { - const char *fs_path; - - fs_path = svn_path_is_child(base_uri, this_if->uri, pool); - if (! fs_path) + if (strcmp(lfchild->name, "lock-path") == 0) { - this_if = this_if->next; - continue; + if (lfchild->first_cdata.first) + { + /* Create an absolute fs-path */ + lockpath = + svn_path_join(path_prefix, + lfchild->first_cdata.first->text, + pool); + + if (lockpath && locktoken) + { + apr_hash_set(hash, lockpath, + APR_HASH_KEY_STRING, locktoken); + lockpath = NULL; + locktoken = NULL; + } + } } - - apr_hash_set(hash, fs_path, APR_HASH_KEY_STRING, - apr_pstrdup(pool, - this_if->state->locktoken->uuid_str)); + else if (strcmp(lfchild->name, "lock-token") == 0) + { + if (lfchild->first_cdata.first) + { + locktoken = + apr_pstrdup(pool, + lfchild->first_cdata.first->text); + + if (lockpath && locktoken) + { + apr_hash_set(hash, lockpath, + APR_HASH_KEY_STRING, locktoken); + lockpath = NULL; + locktoken = NULL; + } + } + } } - - this_if = this_if->next; - - } while (this_if != NULL); - + } } - + *locks = hash; return SVN_NO_ERROR; } @@ -1653,6 +1465,7 @@ const char *conflict; svn_error_t *serr; svn_revnum_t new_rev; + apr_hash_t *locks; svn_boolean_t disable_merge_response = FALSE; /* We'll use the target's pool for our operation. We happen to know that @@ -1672,6 +1485,54 @@ SVN_DAV_ERROR_TAG); } + /* Before attempting the final commit, we need to push any incoming + lock-tokens into the filesystem's access_t. Normally they come + in via 'If:' header, and dav_svn_get_resource() automatically + notices them and does this work for us. In the case of MERGE, + however, svn clients are sending them in the request body. */ + + err = build_lock_hash(&locks, source->info->r, + source->info->repos->root_path, + pool); + if (err != NULL) + return err; + + if (apr_hash_count(locks)) + { + svn_fs_access_t *fsaccess; + apr_hash_index_t *hi; + svn_lock_t *lock; + + serr = svn_fs_get_access (&fsaccess, source->info->repos->fs); + if (serr) + { + /* If an authenticated username was attached to the MERGE + request, then dav_svn_get_resource() should have already + noticed and created an fs_access_t in the filesystem. */ + const char *new_msg = "Lock token(s) in request, but no username."; + svn_error_t *sanitized_error = svn_error_create(serr->apr_err, + NULL, new_msg); + ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, source->info->r, + "%s", serr->message); + svn_error_clear(serr); + return dav_svn_convert_err (sanitized_error, HTTP_BAD_REQUEST, + apr_psprintf(pool, new_msg), pool); + } + + for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi)) + { + void *val; + apr_hash_this(hi, NULL, NULL, &val); + lock = val; + + serr = svn_fs_access_add_lock_token (fsaccess, lock->token); + if (serr) + return dav_svn_convert_err (serr, HTTP_INTERNAL_SERVER_ERROR, + "Error pushing token into filesystem.", + pool); + } + } + /* We will ignore no_auto_merge and no_checkout. We can't do those, but the client has no way to assert that we *should* do them. This should be fine because, presumably, the client has no way to do the various checkouts @@ -1732,25 +1593,19 @@ request. */ if (source->info->svn_client_options != NULL) { - if (NULL != (ap_strstr_c(source->info->svn_client_options, - SVN_DAV_OPTION_RELEASE_LOCKS))) + /* The client might want us to release all locks sent in the + MERGE request. */ + if ((NULL != (ap_strstr_c(source->info->svn_client_options, + SVN_DAV_OPTION_RELEASE_LOCKS))) + && apr_hash_count(locks)) { - /* Release any locks used in the commit. */ - apr_hash_t *locks; - - err = build_lock_hash(&locks, source->info->r, - source->info->repos->root_path, - pool); - if (err != NULL) - return err; - serr = release_locks(locks, source->info->repos->repos, pool); if (serr != NULL) return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Error releasing locks", pool); } - /* We may want to disable the merge response altogether. */ + /* The client might want us to disable the merge response altogether. */ if (NULL != (ap_strstr_c(source->info->svn_client_options, SVN_DAV_OPTION_NO_MERGE_RESPONSE))) disable_merge_response = TRUE; Index: subversion/libsvn_ra_dav/merge.c =================================================================== --- subversion/libsvn_ra_dav/merge.c (revision 13288) +++ subversion/libsvn_ra_dav/merge.c (working copy) @@ -33,6 +33,7 @@ #include "svn_ra.h" #include "svn_pools.h" #include "svn_props.h" +#include "svn_xml.h" #include "svn_private_config.h" @@ -556,6 +557,8 @@ merge_ctx_t mc = { 0 }; const char *body; apr_hash_t *extra_headers = NULL; + apr_hash_index_t *hi; + svn_stringbuf_t *lockbuf = svn_stringbuf_create("", pool); mc.pool = pool; mc.scratchpool = svn_pool_create (pool); @@ -590,45 +593,82 @@ value); } - if (lock_tokens != NULL) + /* Need to marshal the whole [path->token] hash to the server as + a string within the body of the MERGE request. */ + if ((lock_tokens != NULL) + && (apr_hash_count(lock_tokens) > 0)) { - /* Send *all* of the client's tokens in a single If: header. - Notice that unlike our other write requests, this header not - only contains tokens, but paths as well. That's because the - entire hash is needed by the server (both keys and values) to - free all the locks. */ +#define SVN_LOCK "" +#define SVN_LOCK_LEN sizeof(SVN_LOCK)-1 +#define SVN_LOCK_CLOSE "" +#define SVN_LOCK_CLOSE_LEN sizeof(SVN_LOCK_CLOSE)-1 +#define SVN_LOCK_PATH "" +#define SVN_LOCK_PATH_LEN sizeof(SVN_LOCK_PATH)-1 +#define SVN_LOCK_PATH_CLOSE "" +#define SVN_LOCK_PATH_CLOSE_LEN sizeof(SVN_LOCK_CLOSE)-1 +#define SVN_LOCK_TOKEN "" +#define SVN_LOCK_TOKEN_LEN sizeof(SVN_LOCK_TOKEN)-1 +#define SVN_LOCK_TOKEN_CLOSE "" +#define SVN_LOCK_TOKEN_CLOSE_LEN sizeof(SVN_LOCK_TOKEN_CLOSE)-1 - /* ### FIXME: we should be marshalling the lock hash in the XML - body, which requires a change to mod_dav itself. Apache will - reject a header longer than 8K, which means a limit of about - 70 locks in one commit. We could send multiple If: headers, - but then we still hit a 5000 lock boundaray. */ + apr_size_t buf_size = 0; - apr_hash_index_t *hi; - const char *tokenlist = ""; + /* First, figure out how much string data we're talking about, + and allocate a stringbuf big enough to hold it all. */ + for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi)) + { + const void *key; + void *val; + apr_ssize_t klen; - for (hi = apr_hash_first(pool, lock_tokens); - hi; hi = apr_hash_next(hi)) + apr_hash_this(hi, &key, &klen, &val); + + buf_size += SVN_LOCK_LEN; + buf_size += SVN_LOCK_PATH_LEN; + buf_size += klen; + buf_size += SVN_LOCK_PATH_CLOSE_LEN; + buf_size += SVN_LOCK_TOKEN_LEN; + buf_size += strlen(val); + buf_size += SVN_LOCK_TOKEN_CLOSE_LEN; + buf_size += SVN_LOCK_CLOSE_LEN; + } + svn_stringbuf_ensure(lockbuf, buf_size + 1); + + /* Now append all the hash's keys and values into the stringbuf. + This is better than doing apr_pstrcat() in a loop, because + (1) there's no need to constantly re-alloc, and (2) the + stringbuf already knows the end of the buffer, so there's no + seek-time to the end of the string when appending. */ + for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi)) { const void *key; void *val; - const char *abs_uri; apr_hash_this(hi, &key, NULL, &val); - abs_uri = apr_pstrcat(pool, - ras->url, "/", (const char *)key, NULL); - tokenlist = apr_pstrcat(pool, - tokenlist, - apr_psprintf(pool, "<%s> (<%s>) ", - abs_uri, - (const char *)val), - NULL); + svn_stringbuf_appendcstr(lockbuf, SVN_LOCK); + svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_PATH); + svn_stringbuf_appendcstr(lockbuf, key); + svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_PATH_CLOSE); + svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_TOKEN); + svn_stringbuf_appendcstr(lockbuf, val); + svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_TOKEN_CLOSE); + svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_CLOSE); } - if (! extra_headers) - extra_headers = apr_hash_make(pool); - apr_hash_set (extra_headers, "If", APR_HASH_KEY_STRING, tokenlist); +#undef SVN_LOCK +#undef SVN_LOCK_LEN +#undef SVN_LOCK_CLOSE +#undef SVN_LOCK_CLOSE_LEN +#undef SVN_LOCK_PATH +#undef SVN_LOCK_PATH_LEN +#undef SVN_LOCK_PATH_CLOSE +#undef SVN_LOCK_PATH_CLOSE_LEN +#undef SVN_LOCK_TOKEN +#undef SVN_LOCK_TOKEN_LEN +#undef SVN_LOCK_TOKEN_CLOSE +#undef SVN_LOCK_TOKEN_CLOSE_LEN + } body = apr_psprintf(pool, @@ -640,7 +680,10 @@ "" "" "" - "", activity_url); + "" + "%s" + "", + activity_url, lockbuf->data); SVN_ERR( svn_ra_dav__parsed_request_compat(ras->sess, "MERGE", repos_url, body, 0, NULL, merge_elements,