merge.c revision 362181
1/* 2 * merge.c : MERGE response parsing functions for ra_serf 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#include <apr_uri.h> 27 28#include <serf.h> 29 30#include "svn_hash.h" 31#include "svn_pools.h" 32#include "svn_ra.h" 33#include "svn_dav.h" 34#include "svn_xml.h" 35#include "svn_config.h" 36#include "svn_dirent_uri.h" 37#include "svn_props.h" 38 39#include "private/svn_dav_protocol.h" 40#include "private/svn_fspath.h" 41#include "svn_private_config.h" 42 43#include "ra_serf.h" 44#include "../libsvn_ra/ra_loader.h" 45 46 47/* 48 * This enum represents the current state of our XML parsing for a MERGE. 49 */ 50typedef enum merge_state_e { 51 INITIAL = XML_STATE_INITIAL, 52 MERGE_RESPONSE, 53 UPDATED_SET, 54 RESPONSE, 55 HREF, 56 PROPSTAT, 57 PROP, 58 RESOURCE_TYPE, 59 BASELINE, 60 COLLECTION, 61 SKIP_HREF, 62 CHECKED_IN, 63 VERSION_NAME, 64 DATE, 65 AUTHOR, 66 POST_COMMIT_ERR, 67 68 STATUS 69} merge_state_e; 70 71 72/* Structure associated with a MERGE request. */ 73typedef struct merge_context_t 74{ 75 apr_pool_t *pool; 76 77 svn_ra_serf__session_t *session; 78 svn_ra_serf__handler_t *handler; 79 80 apr_hash_t *lock_tokens; 81 svn_boolean_t keep_locks; 82 svn_boolean_t disable_merge_response; 83 84 const char *merge_resource_url; /* URL of resource to be merged. */ 85 const char *merge_url; /* URL at which the MERGE request is aimed. */ 86 87 svn_commit_info_t *commit_info; 88 89} merge_context_t; 90 91 92#define D_ "DAV:" 93#define S_ SVN_XML_NAMESPACE 94static const svn_ra_serf__xml_transition_t merge_ttable[] = { 95 { INITIAL, D_, "merge-response", MERGE_RESPONSE, 96 FALSE, { NULL }, FALSE }, 97 98 { MERGE_RESPONSE, D_, "updated-set", UPDATED_SET, 99 FALSE, { NULL }, FALSE }, 100 101 { UPDATED_SET, D_, "response", RESPONSE, 102 FALSE, { NULL }, TRUE }, 103 104 { RESPONSE, D_, "href", HREF, 105 TRUE, { NULL }, TRUE }, 106 107 { RESPONSE, D_, "propstat", PROPSTAT, 108 FALSE, { NULL }, FALSE }, 109 110#if 0 111 /* Not needed. */ 112 { PROPSTAT, D_, "status", STATUS, 113 FALSE, { NULL }, FALSE }, 114#endif 115 116 { PROPSTAT, D_, "prop", PROP, 117 FALSE, { NULL }, FALSE }, 118 119 { PROP, D_, "resourcetype", RESOURCE_TYPE, 120 FALSE, { NULL }, FALSE }, 121 122 { RESOURCE_TYPE, D_, "baseline", BASELINE, 123 FALSE, { NULL }, TRUE }, 124 125 { RESOURCE_TYPE, D_, "collection", COLLECTION, 126 FALSE, { NULL }, TRUE }, 127 128 { PROP, D_, "checked-in", SKIP_HREF, 129 FALSE, { NULL }, FALSE }, 130 131 { SKIP_HREF, D_, "href", CHECKED_IN, 132 TRUE, { NULL }, TRUE }, 133 134 { PROP, D_, SVN_DAV__VERSION_NAME, VERSION_NAME, 135 TRUE, { NULL }, TRUE }, 136 137 { PROP, D_, SVN_DAV__CREATIONDATE, DATE, 138 TRUE, { NULL }, TRUE }, 139 140 { PROP, D_, "creator-displayname", AUTHOR, 141 TRUE, { NULL }, TRUE }, 142 143 { PROP, S_, "post-commit-err", POST_COMMIT_ERR, 144 TRUE, { NULL }, TRUE }, 145 146 { 0 } 147}; 148 149 150/* Conforms to svn_ra_serf__xml_closed_t */ 151static svn_error_t * 152merge_closed(svn_ra_serf__xml_estate_t *xes, 153 void *baton, 154 int leaving_state, 155 const svn_string_t *cdata, 156 apr_hash_t *attrs, 157 apr_pool_t *scratch_pool) 158{ 159 merge_context_t *merge_ctx = baton; 160 161 if (leaving_state == RESPONSE) 162 { 163 const char *rtype; 164 165 rtype = svn_hash_gets(attrs, "resourcetype"); 166 167 /* rtype can only be "baseline" or "collection" (or NULL). We can 168 keep this check simple. */ 169 if (rtype && *rtype == 'b') 170 { 171 const char *rev_str; 172 173 rev_str = svn_hash_gets(attrs, "revision"); 174 if (rev_str) 175 { 176 apr_int64_t rev; 177 178 SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); 179 merge_ctx->commit_info->revision = (svn_revnum_t)rev; 180 } 181 else 182 merge_ctx->commit_info->revision = SVN_INVALID_REVNUM; 183 184 merge_ctx->commit_info->date = 185 apr_pstrdup(merge_ctx->pool, 186 svn_hash_gets(attrs, "date")); 187 188 merge_ctx->commit_info->author = 189 apr_pstrdup(merge_ctx->pool, 190 svn_hash_gets(attrs, "author")); 191 192 merge_ctx->commit_info->post_commit_err = 193 apr_pstrdup(merge_ctx->pool, 194 svn_hash_gets(attrs, "post-commit-err")); 195 } 196 else 197 { 198 const char *href; 199 200 href = svn_urlpath__skip_ancestor( 201 merge_ctx->merge_url, 202 svn_hash_gets(attrs, "href")); 203 204 if (href == NULL) 205 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 206 _("A MERGE response for '%s' is not " 207 "a child of the destination ('%s')"), 208 href, merge_ctx->merge_url); 209 210 /* We now need to dive all the way into the WC to update the 211 base VCC url. */ 212 if (!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(merge_ctx->session) 213 && merge_ctx->session->wc_callbacks->push_wc_prop) 214 { 215 const char *checked_in; 216 svn_string_t checked_in_str; 217 218 checked_in = svn_hash_gets(attrs, "checked-in"); 219 checked_in_str.data = checked_in; 220 checked_in_str.len = strlen(checked_in); 221 222 SVN_ERR(merge_ctx->session->wc_callbacks->push_wc_prop( 223 merge_ctx->session->wc_callback_baton, 224 href, 225 SVN_RA_SERF__WC_CHECKED_IN_URL, 226 &checked_in_str, 227 scratch_pool)); 228 } 229 } 230 } 231 else if (leaving_state == BASELINE) 232 { 233 svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "baseline"); 234 } 235 else if (leaving_state == COLLECTION) 236 { 237 svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "collection"); 238 } 239 else 240 { 241 const char *name; 242 const char *value = cdata->data; 243 244 if (leaving_state == HREF) 245 { 246 name = "href"; 247 value = svn_urlpath__canonicalize(value, scratch_pool); 248 } 249 else if (leaving_state == CHECKED_IN) 250 { 251 name = "checked-in"; 252 value = svn_urlpath__canonicalize(value, scratch_pool); 253 } 254 else if (leaving_state == VERSION_NAME) 255 name = "revision"; 256 else if (leaving_state == DATE) 257 name = "date"; 258 else if (leaving_state == AUTHOR) 259 name = "author"; 260 else if (leaving_state == POST_COMMIT_ERR) 261 name = "post-commit-err"; 262 else 263 SVN_ERR_MALFUNCTION(); 264 265 svn_ra_serf__xml_note(xes, RESPONSE, name, value); 266 } 267 268 return SVN_NO_ERROR; 269} 270 271 272static svn_error_t * 273setup_merge_headers(serf_bucket_t *headers, 274 void *baton, 275 apr_pool_t *pool /* request pool */, 276 apr_pool_t *scratch_pool) 277{ 278 merge_context_t *ctx = baton; 279 apr_array_header_t *vals = apr_array_make(scratch_pool, 2, 280 sizeof(const char *)); 281 282 if (!ctx->keep_locks) 283 APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_RELEASE_LOCKS; 284 if (ctx->disable_merge_response) 285 APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_NO_MERGE_RESPONSE; 286 287 if (vals->nelts > 0) 288 serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, 289 svn_cstring_join2(vals, " ", FALSE, scratch_pool)); 290 291 return SVN_NO_ERROR; 292} 293 294void 295svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens, 296 const char *parent, 297 serf_bucket_t *body, 298 serf_bucket_alloc_t *alloc, 299 apr_pool_t *pool) 300{ 301 apr_hash_index_t *hi; 302 303 if (!lock_tokens || apr_hash_count(lock_tokens) == 0) 304 return; 305 306 svn_ra_serf__add_open_tag_buckets(body, alloc, 307 "S:lock-token-list", 308 "xmlns:S", SVN_XML_NAMESPACE, 309 SVN_VA_NULL); 310 311 for (hi = apr_hash_first(pool, lock_tokens); 312 hi; 313 hi = apr_hash_next(hi)) 314 { 315 const void *key; 316 apr_ssize_t klen; 317 void *val; 318 svn_string_t path; 319 320 apr_hash_this(hi, &key, &klen, &val); 321 322 path.data = key; 323 path.len = klen; 324 325 if (parent && !svn_relpath_skip_ancestor(parent, key)) 326 continue; 327 328 svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", SVN_VA_NULL); 329 330 svn_ra_serf__add_open_tag_buckets(body, alloc, "lock-path", SVN_VA_NULL); 331 svn_ra_serf__add_cdata_len_buckets(body, alloc, path.data, path.len); 332 svn_ra_serf__add_close_tag_buckets(body, alloc, "lock-path"); 333 334 svn_ra_serf__add_tag_buckets(body, "lock-token", val, alloc); 335 336 svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock"); 337 } 338 339 svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock-token-list"); 340} 341 342/* Implements svn_ra_serf__request_body_delegate_t */ 343static svn_error_t* 344create_merge_body(serf_bucket_t **bkt, 345 void *baton, 346 serf_bucket_alloc_t *alloc, 347 apr_pool_t *pool /* request pool */, 348 apr_pool_t *scratch_pool) 349{ 350 merge_context_t *ctx = baton; 351 serf_bucket_t *body_bkt; 352 353 body_bkt = serf_bucket_aggregate_create(alloc); 354 355 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc); 356 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:merge", 357 "xmlns:D", "DAV:", 358 SVN_VA_NULL); 359 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:source", SVN_VA_NULL); 360 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", SVN_VA_NULL); 361 362 svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc, 363 ctx->merge_resource_url, 364 strlen(ctx->merge_resource_url)); 365 366 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href"); 367 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:source"); 368 369 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, 370 "D:no-auto-merge", SVN_VA_NULL); 371 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, 372 "D:no-checkout", SVN_VA_NULL); 373 374 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", SVN_VA_NULL); 375 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, 376 "D:checked-in", SVN_VA_NULL); 377 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, 378 "D:" SVN_DAV__VERSION_NAME, SVN_VA_NULL); 379 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, 380 "D:resourcetype", SVN_VA_NULL); 381 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, 382 "D:" SVN_DAV__CREATIONDATE, SVN_VA_NULL); 383 svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, 384 "D:creator-displayname", SVN_VA_NULL); 385 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); 386 387 svn_ra_serf__merge_lock_token_list(ctx->lock_tokens, NULL, body_bkt, 388 alloc, pool); 389 390 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:merge"); 391 392 *bkt = body_bkt; 393 394 return SVN_NO_ERROR; 395} 396 397 398svn_error_t * 399svn_ra_serf__run_merge(const svn_commit_info_t **commit_info, 400 svn_ra_serf__session_t *session, 401 const char *merge_resource_url, 402 apr_hash_t *lock_tokens, 403 svn_boolean_t keep_locks, 404 apr_pool_t *result_pool, 405 apr_pool_t *scratch_pool) 406{ 407 merge_context_t *merge_ctx; 408 svn_ra_serf__handler_t *handler; 409 svn_ra_serf__xml_context_t *xmlctx; 410 411 merge_ctx = apr_pcalloc(scratch_pool, sizeof(*merge_ctx)); 412 413 merge_ctx->pool = result_pool; 414 merge_ctx->session = session; 415 416 merge_ctx->merge_resource_url = merge_resource_url; 417 418 merge_ctx->lock_tokens = lock_tokens; 419 merge_ctx->keep_locks = keep_locks; 420 421 /* We don't need the full merge response when working over HTTPv2. 422 * Over HTTPv1, this response is only required with a non-null 423 * svn_ra_push_wc_prop_func_t callback. */ 424 merge_ctx->disable_merge_response = 425 SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session) || 426 session->wc_callbacks->push_wc_prop == NULL; 427 428 merge_ctx->commit_info = svn_create_commit_info(result_pool); 429 430 merge_ctx->merge_url = session->session_url.path; 431 432 xmlctx = svn_ra_serf__xml_context_create(merge_ttable, 433 NULL, merge_closed, NULL, 434 merge_ctx, 435 scratch_pool); 436 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, 437 scratch_pool); 438 439 handler->method = "MERGE"; 440 handler->path = merge_ctx->merge_url; 441 handler->body_delegate = create_merge_body; 442 handler->body_delegate_baton = merge_ctx; 443 handler->body_type = "text/xml"; 444 445 handler->header_delegate = setup_merge_headers; 446 handler->header_delegate_baton = merge_ctx; 447 448 merge_ctx->handler = handler; 449 450 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 451 452 if (handler->sline.code != 200) 453 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 454 455 *commit_info = merge_ctx->commit_info; 456 457 /* Sanity check (Reported to be triggered by CodePlex's svnbridge) */ 458 if (! SVN_IS_VALID_REVNUM(merge_ctx->commit_info->revision)) 459 { 460 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 461 _("The MERGE response did not include " 462 "a new revision")); 463 } 464 465 merge_ctx->commit_info->repos_root = apr_pstrdup(result_pool, 466 session->repos_root_str); 467 468 return SVN_NO_ERROR; 469} 470