replay.c revision 299742
1/* 2 * replay.c : entry point for replay RA 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#include <serf.h> 28 29#include "svn_pools.h" 30#include "svn_ra.h" 31#include "svn_dav.h" 32#include "svn_hash.h" 33#include "svn_xml.h" 34#include "../libsvn_ra/ra_loader.h" 35#include "svn_config.h" 36#include "svn_delta.h" 37#include "svn_base64.h" 38#include "svn_path.h" 39#include "svn_private_config.h" 40 41#include "private/svn_string_private.h" 42 43#include "ra_serf.h" 44 45 46/* 47 * This enum represents the current state of our XML parsing. 48 */ 49typedef enum replay_state_e { 50 INITIAL = XML_STATE_INITIAL, 51 52 REPLAY_REPORT, 53 REPLAY_TARGET_REVISION, 54 REPLAY_OPEN_ROOT, 55 REPLAY_OPEN_DIRECTORY, 56 REPLAY_OPEN_FILE, 57 REPLAY_ADD_DIRECTORY, 58 REPLAY_ADD_FILE, 59 REPLAY_DELETE_ENTRY, 60 REPLAY_CLOSE_FILE, 61 REPLAY_CLOSE_DIRECTORY, 62 REPLAY_CHANGE_DIRECTORY_PROP, 63 REPLAY_CHANGE_FILE_PROP, 64 REPLAY_APPLY_TEXTDELTA 65} replay_state_e; 66 67#define S_ SVN_XML_NAMESPACE 68static const svn_ra_serf__xml_transition_t replay_ttable[] = { 69 { INITIAL, S_, "editor-report", REPLAY_REPORT, 70 FALSE, { NULL }, TRUE }, 71 72 /* Replay just throws every operation as xml element directly 73 in the replay report, so we can't really use the nice exit 74 handling of the transition parser to handle clean callbacks */ 75 76 { REPLAY_REPORT, S_, "target-revision", REPLAY_TARGET_REVISION, 77 FALSE, { "rev", NULL }, TRUE }, 78 79 { REPLAY_REPORT, S_, "open-root", REPLAY_OPEN_ROOT, 80 FALSE, { "rev", NULL }, TRUE }, 81 82 { REPLAY_REPORT, S_, "open-directory", REPLAY_OPEN_DIRECTORY, 83 FALSE, { "name", "rev", NULL }, TRUE }, 84 85 { REPLAY_REPORT, S_, "open-file", REPLAY_OPEN_FILE, 86 FALSE, { "name", "rev", NULL }, TRUE }, 87 88 { REPLAY_REPORT, S_, "add-directory", REPLAY_ADD_DIRECTORY, 89 FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", NULL}, TRUE }, 90 91 { REPLAY_REPORT, S_, "add-file", REPLAY_ADD_FILE, 92 FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", NULL}, TRUE }, 93 94 { REPLAY_REPORT, S_, "delete-entry", REPLAY_DELETE_ENTRY, 95 FALSE, { "name", "rev", NULL }, TRUE }, 96 97 { REPLAY_REPORT, S_, "close-file", REPLAY_CLOSE_FILE, 98 FALSE, { "?checksum", NULL }, TRUE }, 99 100 { REPLAY_REPORT, S_, "close-directory", REPLAY_CLOSE_DIRECTORY, 101 FALSE, { NULL }, TRUE }, 102 103 { REPLAY_REPORT, S_, "change-dir-prop", REPLAY_CHANGE_DIRECTORY_PROP, 104 TRUE, { "name", "?del", NULL }, TRUE }, 105 106 { REPLAY_REPORT, S_, "change-file-prop", REPLAY_CHANGE_FILE_PROP, 107 TRUE, { "name", "?del", NULL }, TRUE }, 108 109 { REPLAY_REPORT, S_, "apply-textdelta", REPLAY_APPLY_TEXTDELTA, 110 FALSE, { "?checksum", NULL }, TRUE }, 111 112 { 0 } 113}; 114 115/* Per directory/file state */ 116typedef struct replay_node_t { 117 apr_pool_t *pool; /* pool allocating this node's data */ 118 svn_boolean_t file; /* file or dir */ 119 120 void *baton; /* node baton */ 121 svn_stream_t *stream; /* stream while handling txdata */ 122 123 struct replay_node_t *parent; /* parent node or NULL */ 124} replay_node_t; 125 126/* Per revision replay report state */ 127typedef struct revision_report_t { 128 apr_pool_t *pool; /* per revision pool */ 129 130 struct replay_node_t *current_node; 131 struct replay_node_t *root_node; 132 133 /* Are we done fetching this file? 134 Handles book-keeping in multi-report case */ 135 svn_boolean_t *done; 136 int *replay_reports; /* NULL or number of outstanding reports */ 137 138 /* callback to get an editor */ 139 svn_ra_replay_revstart_callback_t revstart_func; 140 svn_ra_replay_revfinish_callback_t revfinish_func; 141 void *replay_baton; 142 143 /* replay receiver function and baton */ 144 const svn_delta_editor_t *editor; 145 void *editor_baton; 146 147 /* Path and revision used to filter replayed changes. If 148 INCLUDE_PATH is non-NULL, REVISION is unnecessary and will not be 149 included in the replay REPORT. (Because the REPORT is being 150 aimed an HTTP v2 revision resource.) */ 151 const char *include_path; 152 svn_revnum_t revision; 153 154 /* Information needed to create the replay report body */ 155 svn_revnum_t low_water_mark; 156 svn_boolean_t send_deltas; 157 158 /* Target and revision to fetch revision properties on */ 159 const char *revprop_target; 160 svn_revnum_t revprop_rev; 161 162 /* Revision properties for this revision. */ 163 apr_hash_t *rev_props; 164 165 /* Handlers for the PROPFIND and REPORT for the current revision. */ 166 svn_ra_serf__handler_t *propfind_handler; 167 svn_ra_serf__handler_t *report_handler; /* For done handler */ 168 169} revision_report_t; 170 171/* Conforms to svn_ra_serf__xml_opened_t */ 172static svn_error_t * 173replay_opened(svn_ra_serf__xml_estate_t *xes, 174 void *baton, 175 int entered_state, 176 const svn_ra_serf__dav_props_t *tag, 177 apr_pool_t *scratch_pool) 178{ 179 struct revision_report_t *ctx = baton; 180 181 if (entered_state == REPLAY_REPORT) 182 { 183 /* Before we can continue, we need the revision properties. */ 184 SVN_ERR_ASSERT(!ctx->propfind_handler || ctx->propfind_handler->done); 185 186 svn_ra_serf__keep_only_regular_props(ctx->rev_props, scratch_pool); 187 188 if (ctx->revstart_func) 189 { 190 SVN_ERR(ctx->revstart_func(ctx->revision, ctx->replay_baton, 191 &ctx->editor, &ctx->editor_baton, 192 ctx->rev_props, 193 ctx->pool)); 194 } 195 } 196 else if (entered_state == REPLAY_APPLY_TEXTDELTA) 197 { 198 struct replay_node_t *node = ctx->current_node; 199 apr_hash_t *attrs; 200 const char *checksum; 201 svn_txdelta_window_handler_t handler; 202 void *handler_baton; 203 204 if (! node || ! node->file || node->stream) 205 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 206 207 /* ### Is there a better way to access a specific attr here? */ 208 attrs = svn_ra_serf__xml_gather_since(xes, REPLAY_APPLY_TEXTDELTA); 209 checksum = svn_hash_gets(attrs, "checksum"); 210 211 SVN_ERR(ctx->editor->apply_textdelta(node->baton, checksum, node->pool, 212 &handler, &handler_baton)); 213 214 if (handler != svn_delta_noop_window_handler) 215 { 216 node->stream = svn_base64_decode( 217 svn_txdelta_parse_svndiff(handler, 218 handler_baton, 219 TRUE, 220 node->pool), 221 node->pool); 222 } 223 } 224 225 return SVN_NO_ERROR; 226} 227 228/* Conforms to svn_ra_serf__xml_closed_t */ 229static svn_error_t * 230replay_closed(svn_ra_serf__xml_estate_t *xes, 231 void *baton, 232 int leaving_state, 233 const svn_string_t *cdata, 234 apr_hash_t *attrs, 235 apr_pool_t *scratch_pool) 236{ 237 struct revision_report_t *ctx = baton; 238 239 if (leaving_state == REPLAY_REPORT) 240 { 241 if (ctx->current_node) 242 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 243 244 if (ctx->revfinish_func) 245 { 246 SVN_ERR(ctx->revfinish_func(ctx->revision, ctx->replay_baton, 247 ctx->editor, ctx->editor_baton, 248 ctx->rev_props, scratch_pool)); 249 } 250 } 251 else if (leaving_state == REPLAY_TARGET_REVISION) 252 { 253 const char *revstr = svn_hash_gets(attrs, "rev"); 254 apr_int64_t rev; 255 256 SVN_ERR(svn_cstring_atoi64(&rev, revstr)); 257 SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton, 258 (svn_revnum_t)rev, 259 scratch_pool)); 260 } 261 else if (leaving_state == REPLAY_OPEN_ROOT) 262 { 263 const char *revstr = svn_hash_gets(attrs, "rev"); 264 apr_int64_t rev; 265 apr_pool_t *root_pool = svn_pool_create(ctx->pool); 266 267 if (ctx->current_node || ctx->root_node) 268 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 269 270 ctx->root_node = apr_pcalloc(root_pool, sizeof(*ctx->root_node)); 271 ctx->root_node->pool = root_pool; 272 273 ctx->current_node = ctx->root_node; 274 275 SVN_ERR(svn_cstring_atoi64(&rev, revstr)); 276 SVN_ERR(ctx->editor->open_root(ctx->editor_baton, (svn_revnum_t)rev, 277 root_pool, 278 &ctx->current_node->baton)); 279 } 280 else if (leaving_state == REPLAY_OPEN_DIRECTORY 281 || leaving_state == REPLAY_OPEN_FILE 282 || leaving_state == REPLAY_ADD_DIRECTORY 283 || leaving_state == REPLAY_ADD_FILE) 284 { 285 struct replay_node_t *node; 286 apr_pool_t *node_pool; 287 const char *name = svn_hash_gets(attrs, "name"); 288 const char *rev_str; 289 apr_int64_t rev; 290 291 if (!ctx->current_node || ctx->current_node->file) 292 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 293 294 node_pool = svn_pool_create(ctx->current_node->pool); 295 node = apr_pcalloc(node_pool, sizeof(*node)); 296 node->pool = node_pool; 297 node->parent = ctx->current_node; 298 299 if (leaving_state == REPLAY_OPEN_DIRECTORY 300 || leaving_state == REPLAY_OPEN_FILE) 301 { 302 rev_str = svn_hash_gets(attrs, "rev"); 303 } 304 else 305 rev_str = svn_hash_gets(attrs, "copyfrom-rev"); 306 307 if (rev_str) 308 SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); 309 else 310 rev = SVN_INVALID_REVNUM; 311 312 switch (leaving_state) 313 { 314 case REPLAY_OPEN_DIRECTORY: 315 node->file = FALSE; 316 SVN_ERR(ctx->editor->open_directory(name, 317 ctx->current_node->baton, 318 (svn_revnum_t)rev, 319 node->pool, 320 &node->baton)); 321 break; 322 case REPLAY_OPEN_FILE: 323 node->file = TRUE; 324 SVN_ERR(ctx->editor->open_file(name, 325 ctx->current_node->baton, 326 (svn_revnum_t)rev, 327 node->pool, 328 &node->baton)); 329 break; 330 case REPLAY_ADD_DIRECTORY: 331 node->file = FALSE; 332 SVN_ERR(ctx->editor->add_directory( 333 name, 334 ctx->current_node->baton, 335 SVN_IS_VALID_REVNUM(rev) 336 ? svn_hash_gets(attrs, "copyfrom-path") 337 : NULL, 338 (svn_revnum_t)rev, 339 node->pool, 340 &node->baton)); 341 break; 342 case REPLAY_ADD_FILE: 343 node->file = TRUE; 344 SVN_ERR(ctx->editor->add_file( 345 name, 346 ctx->current_node->baton, 347 SVN_IS_VALID_REVNUM(rev) 348 ? svn_hash_gets(attrs, "copyfrom-path") 349 : NULL, 350 (svn_revnum_t)rev, 351 node->pool, 352 &node->baton)); 353 break; 354 /* default: unreachable */ 355 } 356 ctx->current_node = node; 357 } 358 else if (leaving_state == REPLAY_CLOSE_FILE) 359 { 360 struct replay_node_t *node = ctx->current_node; 361 362 if (! node || ! node->file) 363 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 364 365 SVN_ERR(ctx->editor->close_file(node->baton, 366 svn_hash_gets(attrs, "checksum"), 367 node->pool)); 368 ctx->current_node = node->parent; 369 svn_pool_destroy(node->pool); 370 } 371 else if (leaving_state == REPLAY_CLOSE_DIRECTORY) 372 { 373 struct replay_node_t *node = ctx->current_node; 374 375 if (! node || node->file) 376 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 377 378 SVN_ERR(ctx->editor->close_directory(node->baton, node->pool)); 379 ctx->current_node = node->parent; 380 svn_pool_destroy(node->pool); 381 } 382 else if (leaving_state == REPLAY_DELETE_ENTRY) 383 { 384 struct replay_node_t *parent_node = ctx->current_node; 385 const char *name = svn_hash_gets(attrs, "name"); 386 const char *revstr = svn_hash_gets(attrs, "rev"); 387 apr_int64_t rev; 388 389 if (! parent_node || parent_node->file) 390 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 391 392 SVN_ERR(svn_cstring_atoi64(&rev, revstr)); 393 SVN_ERR(ctx->editor->delete_entry(name, 394 (svn_revnum_t)rev, 395 parent_node->baton, 396 scratch_pool)); 397 } 398 else if (leaving_state == REPLAY_CHANGE_FILE_PROP 399 || leaving_state == REPLAY_CHANGE_DIRECTORY_PROP) 400 { 401 struct replay_node_t *node = ctx->current_node; 402 const char *name; 403 const svn_string_t *value; 404 405 if (! node || node->file != (leaving_state == REPLAY_CHANGE_FILE_PROP)) 406 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 407 408 name = svn_hash_gets(attrs, "name"); 409 410 if (svn_hash_gets(attrs, "del")) 411 value = NULL; 412 else 413 value = svn_base64_decode_string(cdata, scratch_pool); 414 415 if (node->file) 416 { 417 SVN_ERR(ctx->editor->change_file_prop(node->baton, name, value, 418 scratch_pool)); 419 } 420 else 421 { 422 SVN_ERR(ctx->editor->change_dir_prop(node->baton, name, value, 423 scratch_pool)); 424 } 425 } 426 else if (leaving_state == REPLAY_APPLY_TEXTDELTA) 427 { 428 struct replay_node_t *node = ctx->current_node; 429 430 if (! node || ! node->file || ! node->stream) 431 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 432 433 SVN_ERR(svn_stream_close(node->stream)); 434 435 node->stream = NULL; 436 } 437 return SVN_NO_ERROR; 438} 439 440/* Conforms to svn_ra_serf__xml_cdata_t */ 441static svn_error_t * 442replay_cdata(svn_ra_serf__xml_estate_t *xes, 443 void *baton, 444 int current_state, 445 const char *data, 446 apr_size_t len, 447 apr_pool_t *scratch_pool) 448{ 449 struct revision_report_t *ctx = baton; 450 451 if (current_state == REPLAY_APPLY_TEXTDELTA) 452 { 453 struct replay_node_t *node = ctx->current_node; 454 455 if (! node || ! node->file) 456 return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); 457 458 if (node->stream) 459 { 460 apr_size_t written = len; 461 462 SVN_ERR(svn_stream_write(node->stream, data, &written)); 463 if (written != len) 464 return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, 465 _("Error writing stream: unexpected EOF")); 466 } 467 } 468 469 return SVN_NO_ERROR; 470} 471 472/* Implements svn_ra_serf__request_body_delegate_t */ 473static svn_error_t * 474create_replay_body(serf_bucket_t **bkt, 475 void *baton, 476 serf_bucket_alloc_t *alloc, 477 apr_pool_t *pool /* request pool */, 478 apr_pool_t *scratch_pool) 479{ 480 struct revision_report_t *ctx = baton; 481 serf_bucket_t *body_bkt; 482 483 body_bkt = serf_bucket_aggregate_create(alloc); 484 485 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, 486 "S:replay-report", 487 "xmlns:S", SVN_XML_NAMESPACE, 488 SVN_VA_NULL); 489 490 /* If we have a non-NULL include path, we add it to the body and 491 omit the revision; otherwise, the reverse. */ 492 if (ctx->include_path) 493 { 494 svn_ra_serf__add_tag_buckets(body_bkt, 495 "S:include-path", 496 ctx->include_path, 497 alloc); 498 } 499 else 500 { 501 svn_ra_serf__add_tag_buckets(body_bkt, 502 "S:revision", 503 apr_ltoa(pool, ctx->revision), 504 alloc); 505 } 506 svn_ra_serf__add_tag_buckets(body_bkt, 507 "S:low-water-mark", 508 apr_ltoa(pool, ctx->low_water_mark), 509 alloc); 510 511 svn_ra_serf__add_tag_buckets(body_bkt, 512 "S:send-deltas", 513 apr_ltoa(pool, ctx->send_deltas), 514 alloc); 515 516 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "S:replay-report"); 517 518 *bkt = body_bkt; 519 return SVN_NO_ERROR; 520} 521 522svn_error_t * 523svn_ra_serf__replay(svn_ra_session_t *ra_session, 524 svn_revnum_t revision, 525 svn_revnum_t low_water_mark, 526 svn_boolean_t send_deltas, 527 const svn_delta_editor_t *editor, 528 void *edit_baton, 529 apr_pool_t *scratch_pool) 530{ 531 struct revision_report_t ctx = { NULL }; 532 svn_ra_serf__session_t *session = ra_session->priv; 533 svn_ra_serf__handler_t *handler; 534 svn_ra_serf__xml_context_t *xmlctx; 535 const char *report_target; 536 537 SVN_ERR(svn_ra_serf__report_resource(&report_target, session, 538 scratch_pool)); 539 540 ctx.pool = svn_pool_create(scratch_pool); 541 ctx.editor = editor; 542 ctx.editor_baton = edit_baton; 543 ctx.done = FALSE; 544 ctx.revision = revision; 545 ctx.low_water_mark = low_water_mark; 546 ctx.send_deltas = send_deltas; 547 ctx.rev_props = apr_hash_make(scratch_pool); 548 549 xmlctx = svn_ra_serf__xml_context_create(replay_ttable, 550 replay_opened, replay_closed, 551 replay_cdata, 552 &ctx, 553 scratch_pool); 554 555 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, 556 scratch_pool); 557 558 handler->method = "REPORT"; 559 handler->path = session->session_url.path; 560 handler->body_delegate = create_replay_body; 561 handler->body_delegate_baton = &ctx; 562 handler->body_type = "text/xml"; 563 564 /* Not setting up done handler as we don't use a global context */ 565 566 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 567 568 return svn_error_trace( 569 svn_ra_serf__error_on_status(handler->sline, 570 handler->path, 571 handler->location)); 572} 573 574/* The maximum number of outstanding requests at any time. When this 575 * number is reached, ra_serf will stop sending requests until 576 * responses on the previous requests are received and handled. 577 * 578 * Some observations about serf which lead us to the current value. 579 * ---------------------------------------------------------------- 580 * 581 * We aim to keep serf's outgoing queue filled with enough requests so 582 * the network bandwidth and server capacity is used 583 * optimally. Originally we used 5 as the max. number of outstanding 584 * requests, but this turned out to be too low. 585 * 586 * Serf doesn't exit out of the svn_ra_serf__context_run_wait loop as long as 587 * it has data to send or receive. With small responses (revs of a few 588 * kB), serf doesn't come out of this loop at all. So with 589 * MAX_OUTSTANDING_REQUESTS set to a low number, there's a big chance 590 * that serf handles those requests completely in its internal loop, 591 * and only then gives us a chance to create new requests. This 592 * results in hiccups, slowing down the whole process. 593 * 594 * With a larger MAX_OUTSTANDING_REQUESTS, like 100 or more, there's 595 * more chance that serf can come out of its internal loop so we can 596 * replenish the outgoing request queue. There's no real disadvantage 597 * of using a large number here, besides the memory used to store the 598 * message, parser and handler objects (approx. 250 bytes). 599 * 600 * In my test setup peak performance was reached at max. 30-35 601 * requests. So I added a small margin and chose 50. 602 */ 603#define MAX_OUTSTANDING_REQUESTS 50 604 605/* Implements svn_ra_serf__response_done_delegate_t for svn_ra_serf__replay_range */ 606static svn_error_t * 607replay_done(serf_request_t *request, 608 void *baton, 609 apr_pool_t *scratch_pool) 610{ 611 struct revision_report_t *ctx = baton; 612 svn_ra_serf__handler_t *handler = ctx->report_handler; 613 614 if (handler->server_error) 615 return svn_ra_serf__server_error_create(handler, scratch_pool); 616 else if (handler->sline.code != 200) 617 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 618 619 *ctx->done = TRUE; /* Breaks out svn_ra_serf__context_run_wait */ 620 621 /* Are re replaying multiple revisions? */ 622 if (ctx->replay_reports) 623 { 624 (*ctx->replay_reports)--; 625 } 626 627 svn_pool_destroy(ctx->pool); /* Destroys handler and request! */ 628 629 return SVN_NO_ERROR; 630} 631 632svn_error_t * 633svn_ra_serf__replay_range(svn_ra_session_t *ra_session, 634 svn_revnum_t start_revision, 635 svn_revnum_t end_revision, 636 svn_revnum_t low_water_mark, 637 svn_boolean_t send_deltas, 638 svn_ra_replay_revstart_callback_t revstart_func, 639 svn_ra_replay_revfinish_callback_t revfinish_func, 640 void *replay_baton, 641 apr_pool_t *scratch_pool) 642{ 643 svn_ra_serf__session_t *session = ra_session->priv; 644 svn_revnum_t rev = start_revision; 645 const char *report_target; 646 int active_reports = 0; 647 const char *include_path; 648 svn_boolean_t done; 649 650 SVN_ERR(svn_ra_serf__report_resource(&report_target, session, 651 scratch_pool)); 652 653 /* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests 654 aimed at the session URL. But that's incorrect -- these reports 655 aren't about specific resources -- they are above revisions. The 656 path-based filtering offered by this API is just that: a filter 657 applied to the full set of changes made in the revision. As 658 such, the correct target for these REPORT requests is the "me 659 resource" (or, pre-http-v2, the default VCC). 660 661 Our server should have told us if it supported this protocol 662 correction. If so, we aimed our report at the correct resource 663 and include the filtering path as metadata within the report 664 body. Otherwise, we fall back to the pre-1.8 behavior and just 665 wish for the best. 666 667 See issue #4287: 668 http://subversion.tigris.org/issues/show_bug.cgi?id=4287 669 */ 670 if (session->supports_rev_rsrc_replay) 671 { 672 SVN_ERR(svn_ra_serf__get_relative_path(&include_path, 673 session->session_url.path, 674 session, scratch_pool)); 675 } 676 else 677 { 678 include_path = NULL; 679 } 680 681 while (active_reports || rev <= end_revision) 682 { 683 if (session->cancel_func) 684 SVN_ERR(session->cancel_func(session->cancel_baton)); 685 686 /* Send pending requests, if any. Limit the number of outstanding 687 requests to MAX_OUTSTANDING_REQUESTS. */ 688 if (rev <= end_revision && active_reports < MAX_OUTSTANDING_REQUESTS) 689 { 690 struct revision_report_t *rev_ctx; 691 svn_ra_serf__handler_t *handler; 692 apr_pool_t *rev_pool = svn_pool_create(scratch_pool); 693 svn_ra_serf__xml_context_t *xmlctx; 694 const char *replay_target; 695 696 rev_ctx = apr_pcalloc(rev_pool, sizeof(*rev_ctx)); 697 rev_ctx->pool = rev_pool; 698 rev_ctx->revstart_func = revstart_func; 699 rev_ctx->revfinish_func = revfinish_func; 700 rev_ctx->replay_baton = replay_baton; 701 rev_ctx->done = &done; 702 rev_ctx->replay_reports = &active_reports; 703 rev_ctx->include_path = include_path; 704 rev_ctx->revision = rev; 705 rev_ctx->low_water_mark = low_water_mark; 706 rev_ctx->send_deltas = send_deltas; 707 708 /* Request all properties of a certain revision. */ 709 rev_ctx->rev_props = apr_hash_make(rev_ctx->pool); 710 711 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 712 { 713 rev_ctx->revprop_target = apr_psprintf(rev_pool, "%s/%ld", 714 session->rev_stub, rev); 715 rev_ctx->revprop_rev = SVN_INVALID_REVNUM; 716 } 717 else 718 { 719 rev_ctx->revprop_target = report_target; 720 rev_ctx->revprop_rev = rev; 721 } 722 723 SVN_ERR(svn_ra_serf__create_propfind_handler( 724 &rev_ctx->propfind_handler, 725 session, 726 rev_ctx->revprop_target, 727 rev_ctx->revprop_rev, 728 "0", all_props, 729 svn_ra_serf__deliver_svn_props, 730 rev_ctx->rev_props, 731 rev_pool)); 732 733 /* Spin up the serf request for the PROPFIND. */ 734 svn_ra_serf__request_create(rev_ctx->propfind_handler); 735 736 /* Send the replay REPORT request. */ 737 if (session->supports_rev_rsrc_replay) 738 { 739 replay_target = apr_psprintf(rev_pool, "%s/%ld", 740 session->rev_stub, rev); 741 } 742 else 743 { 744 replay_target = session->session_url.path; 745 } 746 747 xmlctx = svn_ra_serf__xml_context_create(replay_ttable, 748 replay_opened, replay_closed, 749 replay_cdata, rev_ctx, 750 rev_pool); 751 752 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, 753 rev_pool); 754 755 handler->method = "REPORT"; 756 handler->path = replay_target; 757 handler->body_delegate = create_replay_body; 758 handler->body_delegate_baton = rev_ctx; 759 handler->body_type = "text/xml"; 760 761 handler->done_delegate = replay_done; 762 handler->done_delegate_baton = rev_ctx; 763 764 rev_ctx->report_handler = handler; 765 svn_ra_serf__request_create(handler); 766 767 rev++; 768 active_reports++; 769 } 770 771 /* Run the serf loop. */ 772 done = FALSE; 773 SVN_ERR(svn_ra_serf__context_run_wait(&done, session, scratch_pool)); 774 775 /* The done handler of reports decrements active_reports when a report 776 is done. This same handler reports (fatal) report errors, so we can 777 just loop here. */ 778 } 779 780 return SVN_NO_ERROR; 781} 782#undef MAX_OUTSTANDING_REQUESTS 783