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