| /* |
| * merge.c : MERGE response parsing functions for ra_serf |
| * |
| * ==================================================================== |
| * 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 <apr_uri.h> |
| |
| #include <serf.h> |
| |
| #include "svn_hash.h" |
| #include "svn_pools.h" |
| #include "svn_ra.h" |
| #include "svn_dav.h" |
| #include "svn_xml.h" |
| #include "svn_config.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_props.h" |
| |
| #include "private/svn_dav_protocol.h" |
| #include "private/svn_fspath.h" |
| #include "svn_private_config.h" |
| |
| #include "ra_serf.h" |
| #include "../libsvn_ra/ra_loader.h" |
| |
| |
| /* |
| * This enum represents the current state of our XML parsing for a MERGE. |
| */ |
| typedef enum merge_state_e { |
| INITIAL = XML_STATE_INITIAL, |
| MERGE_RESPONSE, |
| UPDATED_SET, |
| RESPONSE, |
| HREF, |
| PROPSTAT, |
| PROP, |
| RESOURCE_TYPE, |
| BASELINE, |
| COLLECTION, |
| SKIP_HREF, |
| CHECKED_IN, |
| VERSION_NAME, |
| DATE, |
| AUTHOR, |
| POST_COMMIT_ERR, |
| |
| STATUS |
| } merge_state_e; |
| |
| |
| /* Structure associated with a MERGE request. */ |
| typedef struct merge_context_t |
| { |
| apr_pool_t *pool; |
| |
| svn_ra_serf__session_t *session; |
| svn_ra_serf__handler_t *handler; |
| |
| apr_hash_t *lock_tokens; |
| svn_boolean_t keep_locks; |
| |
| const char *merge_resource_url; /* URL of resource to be merged. */ |
| const char *merge_url; /* URL at which the MERGE request is aimed. */ |
| |
| svn_commit_info_t *commit_info; |
| |
| } merge_context_t; |
| |
| |
| #define D_ "DAV:" |
| #define S_ SVN_XML_NAMESPACE |
| static const svn_ra_serf__xml_transition_t merge_ttable[] = { |
| { INITIAL, D_, "merge-response", MERGE_RESPONSE, |
| FALSE, { NULL }, FALSE }, |
| |
| { MERGE_RESPONSE, D_, "updated-set", UPDATED_SET, |
| FALSE, { NULL }, FALSE }, |
| |
| { UPDATED_SET, D_, "response", RESPONSE, |
| FALSE, { NULL }, TRUE }, |
| |
| { RESPONSE, D_, "href", HREF, |
| TRUE, { NULL }, TRUE }, |
| |
| { RESPONSE, D_, "propstat", PROPSTAT, |
| FALSE, { NULL }, FALSE }, |
| |
| #if 0 |
| /* Not needed. */ |
| { PROPSTAT, D_, "status", STATUS, |
| FALSE, { NULL }, FALSE }, |
| #endif |
| |
| { PROPSTAT, D_, "prop", PROP, |
| FALSE, { NULL }, FALSE }, |
| |
| { PROP, D_, "resourcetype", RESOURCE_TYPE, |
| FALSE, { NULL }, FALSE }, |
| |
| { RESOURCE_TYPE, D_, "baseline", BASELINE, |
| FALSE, { NULL }, TRUE }, |
| |
| { RESOURCE_TYPE, D_, "collection", COLLECTION, |
| FALSE, { NULL }, TRUE }, |
| |
| { PROP, D_, "checked-in", SKIP_HREF, |
| FALSE, { NULL }, FALSE }, |
| |
| { SKIP_HREF, D_, "href", CHECKED_IN, |
| TRUE, { NULL }, TRUE }, |
| |
| { PROP, D_, SVN_DAV__VERSION_NAME, VERSION_NAME, |
| TRUE, { NULL }, TRUE }, |
| |
| { PROP, D_, SVN_DAV__CREATIONDATE, DATE, |
| TRUE, { NULL }, TRUE }, |
| |
| { PROP, D_, "creator-displayname", AUTHOR, |
| TRUE, { NULL }, TRUE }, |
| |
| { PROP, S_, "post-commit-err", POST_COMMIT_ERR, |
| TRUE, { NULL }, TRUE }, |
| |
| { 0 } |
| }; |
| |
| |
| /* Conforms to svn_ra_serf__xml_closed_t */ |
| static svn_error_t * |
| merge_closed(svn_ra_serf__xml_estate_t *xes, |
| void *baton, |
| int leaving_state, |
| const svn_string_t *cdata, |
| apr_hash_t *attrs, |
| apr_pool_t *scratch_pool) |
| { |
| merge_context_t *merge_ctx = baton; |
| |
| if (leaving_state == RESPONSE) |
| { |
| const char *rtype; |
| |
| rtype = svn_hash_gets(attrs, "resourcetype"); |
| |
| /* rtype can only be "baseline" or "collection" (or NULL). We can |
| keep this check simple. */ |
| if (rtype && *rtype == 'b') |
| { |
| const char *rev_str; |
| |
| rev_str = svn_hash_gets(attrs, "revision"); |
| if (rev_str) |
| { |
| apr_int64_t rev; |
| |
| SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); |
| merge_ctx->commit_info->revision = (svn_revnum_t)rev; |
| } |
| else |
| merge_ctx->commit_info->revision = SVN_INVALID_REVNUM; |
| |
| merge_ctx->commit_info->date = |
| apr_pstrdup(merge_ctx->pool, |
| svn_hash_gets(attrs, "date")); |
| |
| merge_ctx->commit_info->author = |
| apr_pstrdup(merge_ctx->pool, |
| svn_hash_gets(attrs, "author")); |
| |
| merge_ctx->commit_info->post_commit_err = |
| apr_pstrdup(merge_ctx->pool, |
| svn_hash_gets(attrs, "post-commit-err")); |
| } |
| else |
| { |
| const char *href; |
| |
| href = svn_urlpath__skip_ancestor( |
| merge_ctx->merge_url, |
| svn_hash_gets(attrs, "href")); |
| |
| if (href == NULL) |
| return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, |
| _("A MERGE response for '%s' is not " |
| "a child of the destination ('%s')"), |
| href, merge_ctx->merge_url); |
| |
| /* We now need to dive all the way into the WC to update the |
| base VCC url. */ |
| if (!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(merge_ctx->session) |
| && merge_ctx->session->wc_callbacks->push_wc_prop) |
| { |
| const char *checked_in; |
| svn_string_t checked_in_str; |
| |
| checked_in = svn_hash_gets(attrs, "checked-in"); |
| checked_in_str.data = checked_in; |
| checked_in_str.len = strlen(checked_in); |
| |
| SVN_ERR(merge_ctx->session->wc_callbacks->push_wc_prop( |
| merge_ctx->session->wc_callback_baton, |
| href, |
| SVN_RA_SERF__WC_CHECKED_IN_URL, |
| &checked_in_str, |
| scratch_pool)); |
| } |
| } |
| } |
| else if (leaving_state == BASELINE) |
| { |
| svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "baseline"); |
| } |
| else if (leaving_state == COLLECTION) |
| { |
| svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "collection"); |
| } |
| else |
| { |
| const char *name; |
| const char *value = cdata->data; |
| |
| if (leaving_state == HREF) |
| { |
| name = "href"; |
| value = svn_urlpath__canonicalize(value, scratch_pool); |
| } |
| else if (leaving_state == CHECKED_IN) |
| { |
| name = "checked-in"; |
| value = svn_urlpath__canonicalize(value, scratch_pool); |
| } |
| else if (leaving_state == VERSION_NAME) |
| name = "revision"; |
| else if (leaving_state == DATE) |
| name = "date"; |
| else if (leaving_state == AUTHOR) |
| name = "author"; |
| else if (leaving_state == POST_COMMIT_ERR) |
| name = "post-commit-err"; |
| else |
| SVN_ERR_MALFUNCTION(); |
| |
| svn_ra_serf__xml_note(xes, RESPONSE, name, value); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| setup_merge_headers(serf_bucket_t *headers, |
| void *baton, |
| apr_pool_t *pool /* request pool */, |
| apr_pool_t *scratch_pool) |
| { |
| merge_context_t *ctx = baton; |
| |
| if (!ctx->keep_locks) |
| { |
| serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, |
| SVN_DAV_OPTION_RELEASE_LOCKS); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| void |
| svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens, |
| const char *parent, |
| serf_bucket_t *body, |
| serf_bucket_alloc_t *alloc, |
| apr_pool_t *pool) |
| { |
| apr_hash_index_t *hi; |
| |
| if (!lock_tokens || apr_hash_count(lock_tokens) == 0) |
| return; |
| |
| svn_ra_serf__add_open_tag_buckets(body, alloc, |
| "S:lock-token-list", |
| "xmlns:S", SVN_XML_NAMESPACE, |
| SVN_VA_NULL); |
| |
| for (hi = apr_hash_first(pool, lock_tokens); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| apr_ssize_t klen; |
| void *val; |
| svn_string_t path; |
| |
| apr_hash_this(hi, &key, &klen, &val); |
| |
| path.data = key; |
| path.len = klen; |
| |
| if (parent && !svn_relpath_skip_ancestor(parent, key)) |
| continue; |
| |
| svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", SVN_VA_NULL); |
| |
| svn_ra_serf__add_open_tag_buckets(body, alloc, "lock-path", SVN_VA_NULL); |
| svn_ra_serf__add_cdata_len_buckets(body, alloc, path.data, path.len); |
| svn_ra_serf__add_close_tag_buckets(body, alloc, "lock-path"); |
| |
| svn_ra_serf__add_tag_buckets(body, "lock-token", val, alloc); |
| |
| svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock"); |
| } |
| |
| svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock-token-list"); |
| } |
| |
| /* Implements svn_ra_serf__request_body_delegate_t */ |
| static svn_error_t* |
| create_merge_body(serf_bucket_t **bkt, |
| void *baton, |
| serf_bucket_alloc_t *alloc, |
| apr_pool_t *pool /* request pool */, |
| apr_pool_t *scratch_pool) |
| { |
| merge_context_t *ctx = baton; |
| serf_bucket_t *body_bkt; |
| |
| body_bkt = serf_bucket_aggregate_create(alloc); |
| |
| svn_ra_serf__add_xml_header_buckets(body_bkt, alloc); |
| svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:merge", |
| "xmlns:D", "DAV:", |
| SVN_VA_NULL); |
| svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:source", SVN_VA_NULL); |
| svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", SVN_VA_NULL); |
| |
| svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc, |
| ctx->merge_resource_url, |
| strlen(ctx->merge_resource_url)); |
| |
| svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href"); |
| svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:source"); |
| |
| svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, |
| "D:no-auto-merge", SVN_VA_NULL); |
| svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, |
| "D:no-checkout", SVN_VA_NULL); |
| |
| svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", SVN_VA_NULL); |
| svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, |
| "D:checked-in", SVN_VA_NULL); |
| svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, |
| "D:" SVN_DAV__VERSION_NAME, SVN_VA_NULL); |
| svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, |
| "D:resourcetype", SVN_VA_NULL); |
| svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, |
| "D:" SVN_DAV__CREATIONDATE, SVN_VA_NULL); |
| svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, |
| "D:creator-displayname", SVN_VA_NULL); |
| svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); |
| |
| svn_ra_serf__merge_lock_token_list(ctx->lock_tokens, NULL, body_bkt, |
| alloc, pool); |
| |
| svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:merge"); |
| |
| *bkt = body_bkt; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_ra_serf__run_merge(const svn_commit_info_t **commit_info, |
| svn_ra_serf__session_t *session, |
| const char *merge_resource_url, |
| apr_hash_t *lock_tokens, |
| svn_boolean_t keep_locks, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| merge_context_t *merge_ctx; |
| svn_ra_serf__handler_t *handler; |
| svn_ra_serf__xml_context_t *xmlctx; |
| |
| merge_ctx = apr_pcalloc(scratch_pool, sizeof(*merge_ctx)); |
| |
| merge_ctx->pool = result_pool; |
| merge_ctx->session = session; |
| |
| merge_ctx->merge_resource_url = merge_resource_url; |
| |
| merge_ctx->lock_tokens = lock_tokens; |
| merge_ctx->keep_locks = keep_locks; |
| |
| merge_ctx->commit_info = svn_create_commit_info(result_pool); |
| |
| merge_ctx->merge_url = session->session_url.path; |
| |
| xmlctx = svn_ra_serf__xml_context_create(merge_ttable, |
| NULL, merge_closed, NULL, |
| merge_ctx, |
| scratch_pool); |
| handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, |
| scratch_pool); |
| |
| handler->method = "MERGE"; |
| handler->path = merge_ctx->merge_url; |
| handler->body_delegate = create_merge_body; |
| handler->body_delegate_baton = merge_ctx; |
| handler->body_type = "text/xml"; |
| |
| handler->header_delegate = setup_merge_headers; |
| handler->header_delegate_baton = merge_ctx; |
| |
| merge_ctx->handler = handler; |
| |
| SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); |
| |
| if (handler->sline.code != 200) |
| return svn_error_trace(svn_ra_serf__unexpected_status(handler)); |
| |
| *commit_info = merge_ctx->commit_info; |
| |
| /* Sanity check (Reported to be triggered by CodePlex's svnbridge) */ |
| if (! SVN_IS_VALID_REVNUM(merge_ctx->commit_info->revision)) |
| { |
| return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, |
| _("The MERGE response did not include " |
| "a new revision")); |
| } |
| |
| merge_ctx->commit_info->repos_root = apr_pstrdup(result_pool, |
| session->repos_root_str); |
| |
| return SVN_NO_ERROR; |
| } |