]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/replay.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_serf / replay.c
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 \f
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 \f
46 /*
47  * This enum represents the current state of our XML parsing.
48  */
49 typedef 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
68 static 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 */
116 typedef 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 */
127 typedef 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 */
172 static svn_error_t *
173 replay_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  */
229 static svn_error_t *
230 replay_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  */
441 static svn_error_t *
442 replay_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 */
473 static svn_error_t *
474 create_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
522 svn_error_t *
523 svn_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 */
606 static svn_error_t *
607 replay_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
632 svn_error_t *
633 svn_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