]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_ra_serf/replay.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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_xml.h"
33 #include "../libsvn_ra/ra_loader.h"
34 #include "svn_config.h"
35 #include "svn_delta.h"
36 #include "svn_base64.h"
37 #include "svn_path.h"
38 #include "svn_private_config.h"
39
40 #include "private/svn_string_private.h"
41
42 #include "ra_serf.h"
43
44 \f
45 /*
46  * This enum represents the current state of our XML parsing.
47  */
48 typedef enum replay_state_e {
49   NONE = 0,
50   REPORT,
51   OPEN_DIR,
52   ADD_DIR,
53   OPEN_FILE,
54   ADD_FILE,
55   DELETE_ENTRY,
56   APPLY_TEXTDELTA,
57   CHANGE_PROP
58 } replay_state_e;
59
60 typedef struct replay_info_t replay_info_t;
61
62 struct replay_info_t {
63   apr_pool_t *pool;
64
65   void *baton;
66   svn_stream_t *stream;
67
68   replay_info_t *parent;
69 };
70
71 typedef svn_error_t *
72 (*change_prop_t)(void *baton,
73                  const char *name,
74                  const svn_string_t *value,
75                  apr_pool_t *pool);
76
77 typedef struct prop_info_t {
78   apr_pool_t *pool;
79
80   change_prop_t change;
81
82   const char *name;
83   svn_boolean_t del_prop;
84
85   svn_stringbuf_t *prop_value;
86
87   replay_info_t *parent;
88 } prop_info_t;
89
90 typedef struct replay_context_t {
91   apr_pool_t *src_rev_pool;
92   apr_pool_t *dst_rev_pool;
93
94   /* Are we done fetching this file? */
95   svn_boolean_t done;
96   svn_ra_serf__list_t **done_list;
97   svn_ra_serf__list_t done_item;
98
99   /* callback to get an editor */
100   svn_ra_replay_revstart_callback_t revstart_func;
101   svn_ra_replay_revfinish_callback_t revfinish_func;
102   void *replay_baton;
103
104   /* replay receiver function and baton */
105   const svn_delta_editor_t *editor;
106   void *editor_baton;
107
108   /* Path and revision used to filter replayed changes.  If
109      INCLUDE_PATH is non-NULL, REVISION is unnecessary and will not be
110      included in the replay REPORT.  (Because the REPORT is being
111      aimed an HTTP v2 revision resource.)  */
112   const char *include_path;
113   svn_revnum_t revision;
114
115   /* Information needed to create the replay report body */
116   svn_revnum_t low_water_mark;
117   svn_boolean_t send_deltas;
118
119   /* Target and revision to fetch revision properties on */
120   const char *revprop_target;
121   svn_revnum_t revprop_rev;
122
123   /* Revision properties for this revision. */
124   apr_hash_t *revs_props;
125   apr_hash_t *props;
126
127   /* Keep a reference to the XML parser ctx to report any errors. */
128   svn_ra_serf__xml_parser_t *parser_ctx;
129
130   /* Handlers for the PROPFIND and REPORT for the current revision. */
131   svn_ra_serf__handler_t *propfind_handler;
132   svn_ra_serf__handler_t *report_handler;
133
134 } replay_context_t;
135
136 \f
137 static void *
138 push_state(svn_ra_serf__xml_parser_t *parser,
139            replay_context_t *replay_ctx,
140            replay_state_e state)
141 {
142   svn_ra_serf__xml_push_state(parser, state);
143
144   if (state == OPEN_DIR || state == ADD_DIR ||
145       state == OPEN_FILE || state == ADD_FILE)
146     {
147       replay_info_t *info;
148       apr_pool_t *pool = svn_pool_create(replay_ctx->dst_rev_pool);
149
150       info = apr_palloc(pool, sizeof(*info));
151
152       info->pool = pool;
153       info->parent = parser->state->private;
154       info->baton = NULL;
155       info->stream = NULL;
156
157       parser->state->private = info;
158     }
159   else if (state == CHANGE_PROP)
160     {
161       prop_info_t *info;
162       apr_pool_t *pool = svn_pool_create(replay_ctx->dst_rev_pool);
163
164       info = apr_pcalloc(pool, sizeof(*info));
165
166       info->pool = pool;
167       info->parent = parser->state->private;
168       info->prop_value = svn_stringbuf_create_empty(pool);
169
170       parser->state->private = info;
171     }
172
173   return parser->state->private;
174 }
175
176 static svn_error_t *
177 start_replay(svn_ra_serf__xml_parser_t *parser,
178              svn_ra_serf__dav_props_t name,
179              const char **attrs,
180              apr_pool_t *scratch_pool)
181 {
182   replay_context_t *ctx = parser->user_data;
183   replay_state_e state;
184
185   state = parser->state->current_state;
186
187   if (state == NONE &&
188       strcmp(name.name, "editor-report") == 0)
189     {
190       push_state(parser, ctx, REPORT);
191
192       /* Before we can continue, we need the revision properties. */
193       SVN_ERR_ASSERT(!ctx->propfind_handler || ctx->propfind_handler->done);
194
195       /* Create a pool for the commit editor. */
196       ctx->dst_rev_pool = svn_pool_create(ctx->src_rev_pool);
197
198       SVN_ERR(svn_ra_serf__select_revprops(&ctx->props,
199                                            ctx->revprop_target,
200                                            ctx->revprop_rev,
201                                            ctx->revs_props,
202                                            ctx->dst_rev_pool,
203                                            scratch_pool));
204
205       if (ctx->revstart_func)
206         {
207           SVN_ERR(ctx->revstart_func(ctx->revision, ctx->replay_baton,
208                                      &ctx->editor, &ctx->editor_baton,
209                                      ctx->props,
210                                      ctx->dst_rev_pool));
211         }
212     }
213   else if (state == REPORT &&
214            strcmp(name.name, "target-revision") == 0)
215     {
216       const char *rev;
217
218       rev = svn_xml_get_attr_value("rev", attrs);
219       if (!rev)
220         {
221           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
222                     _("Missing revision attr in target-revision element"));
223         }
224
225       SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
226                                                SVN_STR_TO_REV(rev),
227                                                scratch_pool));
228     }
229   else if (state == REPORT &&
230            strcmp(name.name, "open-root") == 0)
231     {
232       const char *rev;
233       replay_info_t *info;
234
235       rev = svn_xml_get_attr_value("rev", attrs);
236
237       if (!rev)
238         {
239           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
240                     _("Missing revision attr in open-root element"));
241         }
242
243       info = push_state(parser, ctx, OPEN_DIR);
244
245       SVN_ERR(ctx->editor->open_root(ctx->editor_baton,
246                                      SVN_STR_TO_REV(rev),
247                                      ctx->dst_rev_pool,
248                                      &info->baton));
249     }
250   else if ((state == OPEN_DIR || state == ADD_DIR) &&
251            strcmp(name.name, "delete-entry") == 0)
252     {
253       const char *file_name, *rev;
254       replay_info_t *info;
255
256       file_name = svn_xml_get_attr_value("name", attrs);
257       if (!file_name)
258         {
259           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
260                     _("Missing name attr in delete-entry element"));
261         }
262       rev = svn_xml_get_attr_value("rev", attrs);
263       if (!rev)
264         {
265           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
266                     _("Missing revision attr in delete-entry element"));
267         }
268
269       info = push_state(parser, ctx, DELETE_ENTRY);
270
271       SVN_ERR(ctx->editor->delete_entry(file_name, SVN_STR_TO_REV(rev),
272                                         info->baton, scratch_pool));
273
274       svn_ra_serf__xml_pop_state(parser);
275     }
276   else if ((state == OPEN_DIR || state == ADD_DIR) &&
277            strcmp(name.name, "open-directory") == 0)
278     {
279       const char *rev, *dir_name;
280       replay_info_t *info;
281
282       dir_name = svn_xml_get_attr_value("name", attrs);
283       if (!dir_name)
284         {
285           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
286                     _("Missing name attr in open-directory element"));
287         }
288       rev = svn_xml_get_attr_value("rev", attrs);
289       if (!rev)
290         {
291           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
292                     _("Missing revision attr in open-directory element"));
293         }
294
295       info = push_state(parser, ctx, OPEN_DIR);
296
297       SVN_ERR(ctx->editor->open_directory(dir_name, info->parent->baton,
298                                           SVN_STR_TO_REV(rev),
299                                           ctx->dst_rev_pool, &info->baton));
300     }
301   else if ((state == OPEN_DIR || state == ADD_DIR) &&
302            strcmp(name.name, "add-directory") == 0)
303     {
304       const char *dir_name, *copyfrom, *copyrev;
305       svn_revnum_t rev;
306       replay_info_t *info;
307
308       dir_name = svn_xml_get_attr_value("name", attrs);
309       if (!dir_name)
310         {
311           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
312                     _("Missing name attr in add-directory element"));
313         }
314       copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs);
315       copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs);
316
317       if (copyrev)
318         rev = SVN_STR_TO_REV(copyrev);
319       else
320         rev = SVN_INVALID_REVNUM;
321
322       info = push_state(parser, ctx, ADD_DIR);
323
324       SVN_ERR(ctx->editor->add_directory(dir_name, info->parent->baton,
325                                          copyfrom, rev,
326                                          ctx->dst_rev_pool, &info->baton));
327     }
328   else if ((state == OPEN_DIR || state == ADD_DIR) &&
329            strcmp(name.name, "close-directory") == 0)
330     {
331       replay_info_t *info = parser->state->private;
332
333       SVN_ERR(ctx->editor->close_directory(info->baton, scratch_pool));
334
335       svn_ra_serf__xml_pop_state(parser);
336
337       svn_pool_destroy(info->pool);
338     }
339   else if ((state == OPEN_DIR || state == ADD_DIR) &&
340            strcmp(name.name, "open-file") == 0)
341     {
342       const char *file_name, *rev;
343       replay_info_t *info;
344
345       file_name = svn_xml_get_attr_value("name", attrs);
346       if (!file_name)
347         {
348           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
349                     _("Missing name attr in open-file element"));
350         }
351       rev = svn_xml_get_attr_value("rev", attrs);
352       if (!rev)
353         {
354           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
355                     _("Missing revision attr in open-file element"));
356         }
357
358       info = push_state(parser, ctx, OPEN_FILE);
359
360       SVN_ERR(ctx->editor->open_file(file_name, info->parent->baton,
361                                      SVN_STR_TO_REV(rev),
362                                      info->pool, &info->baton));
363     }
364   else if ((state == OPEN_DIR || state == ADD_DIR) &&
365            strcmp(name.name, "add-file") == 0)
366     {
367       const char *file_name, *copyfrom, *copyrev;
368       svn_revnum_t rev;
369       replay_info_t *info;
370
371       file_name = svn_xml_get_attr_value("name", attrs);
372       if (!file_name)
373         {
374           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
375                     _("Missing name attr in add-file element"));
376         }
377       copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs);
378       copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs);
379
380       info = push_state(parser, ctx, ADD_FILE);
381
382       if (copyrev)
383         rev = SVN_STR_TO_REV(copyrev);
384       else
385         rev = SVN_INVALID_REVNUM;
386
387       SVN_ERR(ctx->editor->add_file(file_name, info->parent->baton,
388                                     copyfrom, rev,
389                                     info->pool, &info->baton));
390     }
391   else if ((state == OPEN_FILE || state == ADD_FILE) &&
392            strcmp(name.name, "apply-textdelta") == 0)
393     {
394       const char *checksum;
395       replay_info_t *info;
396       svn_txdelta_window_handler_t textdelta;
397       void *textdelta_baton;
398       svn_stream_t *delta_stream;
399
400       info = push_state(parser, ctx, APPLY_TEXTDELTA);
401
402       checksum = svn_xml_get_attr_value("checksum", attrs);
403       if (checksum)
404         {
405           checksum = apr_pstrdup(info->pool, checksum);
406         }
407
408       SVN_ERR(ctx->editor->apply_textdelta(info->baton, checksum,
409                                            info->pool,
410                                            &textdelta,
411                                            &textdelta_baton));
412
413       delta_stream = svn_txdelta_parse_svndiff(textdelta, textdelta_baton,
414                                                TRUE, info->pool);
415       info->stream = svn_base64_decode(delta_stream, info->pool);
416     }
417   else if ((state == OPEN_FILE || state == ADD_FILE) &&
418            strcmp(name.name, "close-file") == 0)
419     {
420       replay_info_t *info = parser->state->private;
421       const char *checksum;
422
423       checksum = svn_xml_get_attr_value("checksum", attrs);
424
425       SVN_ERR(ctx->editor->close_file(info->baton, checksum, scratch_pool));
426
427       svn_ra_serf__xml_pop_state(parser);
428
429       svn_pool_destroy(info->pool);
430     }
431   else if (((state == OPEN_FILE || state == ADD_FILE) &&
432             strcmp(name.name, "change-file-prop") == 0) ||
433            ((state == OPEN_DIR || state == ADD_DIR) &&
434             strcmp(name.name, "change-dir-prop") == 0))
435     {
436       const char *prop_name;
437       prop_info_t *info;
438
439       prop_name = svn_xml_get_attr_value("name", attrs);
440       if (!prop_name)
441         {
442           return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
443                                    _("Missing name attr in %s element"),
444                                    name.name);
445         }
446
447       info = push_state(parser, ctx, CHANGE_PROP);
448
449
450       if (svn_xml_get_attr_value("del", attrs))
451         info->del_prop = TRUE;
452       else
453         info->del_prop = FALSE;
454
455       info->name = apr_pstrdup(info->pool, prop_name);
456       if (state == OPEN_FILE || state == ADD_FILE)
457         {
458           info->change = ctx->editor->change_file_prop;
459         }
460       else
461         {
462           info->change = ctx->editor->change_dir_prop;
463         }
464
465     }
466
467   return SVN_NO_ERROR;
468 }
469
470 static svn_error_t *
471 end_replay(svn_ra_serf__xml_parser_t *parser,
472            svn_ra_serf__dav_props_t name,
473            apr_pool_t *scratch_pool)
474 {
475   replay_context_t *ctx = parser->user_data;
476   replay_state_e state;
477
478   state = parser->state->current_state;
479
480   if (state == REPORT &&
481       strcmp(name.name, "editor-report") == 0)
482     {
483       svn_ra_serf__xml_pop_state(parser);
484       if (ctx->revfinish_func)
485         {
486           SVN_ERR(ctx->revfinish_func(ctx->revision, ctx->replay_baton,
487                                       ctx->editor, ctx->editor_baton,
488                                       ctx->props,
489                                       ctx->dst_rev_pool));
490         }
491       svn_pool_destroy(ctx->dst_rev_pool);
492     }
493   else if (state == OPEN_DIR && strcmp(name.name, "open-directory") == 0)
494     {
495       /* Don't do anything. */
496     }
497   else if (state == ADD_DIR && strcmp(name.name, "add-directory") == 0)
498     {
499       /* Don't do anything. */
500     }
501   else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
502     {
503       /* Don't do anything. */
504     }
505   else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
506     {
507       /* Don't do anything. */
508     }
509   else if ((state == OPEN_FILE || state == ADD_FILE) &&
510            strcmp(name.name, "close-file") == 0)
511     {
512       /* Don't do anything. */
513     }
514   else if ((state == APPLY_TEXTDELTA) &&
515            strcmp(name.name, "apply-textdelta") == 0)
516     {
517       replay_info_t *info = parser->state->private;
518       SVN_ERR(svn_stream_close(info->stream));
519       svn_ra_serf__xml_pop_state(parser);
520     }
521   else if (state == CHANGE_PROP &&
522            (strcmp(name.name, "change-file-prop") == 0 ||
523             strcmp(name.name, "change-dir-prop") == 0))
524     {
525       prop_info_t *info = parser->state->private;
526       const svn_string_t *prop_val;
527
528       if (info->del_prop)
529         {
530           prop_val = NULL;
531         }
532       else
533         {
534           const svn_string_t *morph;
535
536           morph = svn_stringbuf__morph_into_string(info->prop_value);
537 #ifdef SVN_DEBUG
538           info->prop_value = NULL;  /* morph killed the stringbuf.  */
539 #endif
540
541           prop_val = svn_base64_decode_string(morph, info->pool);
542         }
543
544       SVN_ERR(info->change(info->parent->baton, info->name, prop_val,
545                            info->parent->pool));
546       svn_ra_serf__xml_pop_state(parser);
547
548       svn_pool_destroy(info->pool);
549     }
550
551   return SVN_NO_ERROR;
552 }
553
554 static svn_error_t *
555 cdata_replay(svn_ra_serf__xml_parser_t *parser,
556              const char *data,
557              apr_size_t len,
558              apr_pool_t *scratch_pool)
559 {
560   replay_context_t *replay_ctx = parser->user_data;
561   replay_state_e state;
562
563   UNUSED_CTX(replay_ctx);
564
565   state = parser->state->current_state;
566
567   if (state == APPLY_TEXTDELTA)
568     {
569       replay_info_t *info = parser->state->private;
570       apr_size_t written;
571
572       written = len;
573
574       SVN_ERR(svn_stream_write(info->stream, data, &written));
575
576       if (written != len)
577         return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
578                                 _("Error writing stream: unexpected EOF"));
579     }
580   else if (state == CHANGE_PROP)
581     {
582       prop_info_t *info = parser->state->private;
583
584       svn_stringbuf_appendbytes(info->prop_value, data, len);
585     }
586
587   return SVN_NO_ERROR;
588 }
589
590 static svn_error_t *
591 create_replay_body(serf_bucket_t **bkt,
592                    void *baton,
593                    serf_bucket_alloc_t *alloc,
594                    apr_pool_t *pool)
595 {
596   replay_context_t *ctx = baton;
597   serf_bucket_t *body_bkt;
598
599   body_bkt = serf_bucket_aggregate_create(alloc);
600
601   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
602                                     "S:replay-report",
603                                     "xmlns:S", SVN_XML_NAMESPACE,
604                                     NULL);
605
606   /* If we have a non-NULL include path, we add it to the body and
607      omit the revision; otherwise, the reverse. */
608   if (ctx->include_path)
609     {
610       svn_ra_serf__add_tag_buckets(body_bkt,
611                                    "S:include-path",
612                                    ctx->include_path,
613                                    alloc);
614     }
615   else
616     {
617       svn_ra_serf__add_tag_buckets(body_bkt,
618                                    "S:revision",
619                                    apr_ltoa(ctx->src_rev_pool, ctx->revision),
620                                    alloc);
621     }
622   svn_ra_serf__add_tag_buckets(body_bkt,
623                                "S:low-water-mark",
624                                apr_ltoa(ctx->src_rev_pool, ctx->low_water_mark),
625                                alloc);
626
627   svn_ra_serf__add_tag_buckets(body_bkt,
628                                "S:send-deltas",
629                                apr_ltoa(ctx->src_rev_pool, ctx->send_deltas),
630                                alloc);
631
632   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "S:replay-report");
633
634   *bkt = body_bkt;
635   return SVN_NO_ERROR;
636 }
637
638 svn_error_t *
639 svn_ra_serf__replay(svn_ra_session_t *ra_session,
640                     svn_revnum_t revision,
641                     svn_revnum_t low_water_mark,
642                     svn_boolean_t send_deltas,
643                     const svn_delta_editor_t *editor,
644                     void *edit_baton,
645                     apr_pool_t *pool)
646 {
647   replay_context_t *replay_ctx;
648   svn_ra_serf__session_t *session = ra_session->priv;
649   svn_ra_serf__handler_t *handler;
650   svn_ra_serf__xml_parser_t *parser_ctx;
651   svn_error_t *err;
652   const char *report_target;
653
654   SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
655
656   replay_ctx = apr_pcalloc(pool, sizeof(*replay_ctx));
657   replay_ctx->src_rev_pool = pool;
658   replay_ctx->editor = editor;
659   replay_ctx->editor_baton = edit_baton;
660   replay_ctx->done = FALSE;
661   replay_ctx->revision = revision;
662   replay_ctx->low_water_mark = low_water_mark;
663   replay_ctx->send_deltas = send_deltas;
664   replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
665
666   handler = apr_pcalloc(pool, sizeof(*handler));
667
668   handler->handler_pool = pool;
669   handler->method = "REPORT";
670   handler->path = session->session_url.path;
671   handler->body_delegate = create_replay_body;
672   handler->body_delegate_baton = replay_ctx;
673   handler->body_type = "text/xml";
674   handler->conn = session->conns[0];
675   handler->session = session;
676
677   parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
678
679   parser_ctx->pool = pool;
680   parser_ctx->user_data = replay_ctx;
681   parser_ctx->start = start_replay;
682   parser_ctx->end = end_replay;
683   parser_ctx->cdata = cdata_replay;
684   parser_ctx->done = &replay_ctx->done;
685
686   handler->response_handler = svn_ra_serf__handle_xml_parser;
687   handler->response_baton = parser_ctx;
688
689   /* This is only needed to handle errors during XML parsing. */
690   replay_ctx->parser_ctx = parser_ctx;
691   replay_ctx->report_handler = handler; /* unused */
692
693   svn_ra_serf__request_create(handler);
694
695   err = svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool);
696
697   SVN_ERR(svn_error_compose_create(
698               svn_ra_serf__error_on_status(handler->sline,
699                                            handler->path,
700                                            handler->location),
701               err));
702
703   return SVN_NO_ERROR;
704 }
705
706 /* The maximum number of outstanding requests at any time. When this
707  * number is reached, ra_serf will stop sending requests until
708  * responses on the previous requests are received and handled.
709  *
710  * Some observations about serf which lead us to the current value.
711  * ----------------------------------------------------------------
712  *
713  * We aim to keep serf's outgoing queue filled with enough requests so
714  * the network bandwidth and server capacity is used
715  * optimally. Originally we used 5 as the max. number of outstanding
716  * requests, but this turned out to be too low.
717  *
718  * Serf doesn't exit out of the svn_ra_serf__context_run_wait loop as long as
719  * it has data to send or receive. With small responses (revs of a few
720  * kB), serf doesn't come out of this loop at all. So with
721  * MAX_OUTSTANDING_REQUESTS set to a low number, there's a big chance
722  * that serf handles those requests completely in its internal loop,
723  * and only then gives us a chance to create new requests. This
724  * results in hiccups, slowing down the whole process.
725  *
726  * With a larger MAX_OUTSTANDING_REQUESTS, like 100 or more, there's
727  * more chance that serf can come out of its internal loop so we can
728  * replenish the outgoing request queue.  There's no real disadvantage
729  * of using a large number here, besides the memory used to store the
730  * message, parser and handler objects (approx. 250 bytes).
731  *
732  * In my test setup peak performance was reached at max. 30-35
733  * requests. So I added a small margin and chose 50.
734  */
735 #define MAX_OUTSTANDING_REQUESTS 50
736
737 svn_error_t *
738 svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
739                           svn_revnum_t start_revision,
740                           svn_revnum_t end_revision,
741                           svn_revnum_t low_water_mark,
742                           svn_boolean_t send_deltas,
743                           svn_ra_replay_revstart_callback_t revstart_func,
744                           svn_ra_replay_revfinish_callback_t revfinish_func,
745                           void *replay_baton,
746                           apr_pool_t *pool)
747 {
748   svn_ra_serf__session_t *session = ra_session->priv;
749   svn_revnum_t rev = start_revision;
750   const char *report_target;
751   int active_reports = 0;
752   const char *include_path;
753
754   SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
755
756   /* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests
757      aimed at the session URL.  But that's incorrect -- these reports
758      aren't about specific resources -- they are above revisions.  The
759      path-based filtering offered by this API is just that: a filter
760      applied to the full set of changes made in the revision.  As
761      such, the correct target for these REPORT requests is the "me
762      resource" (or, pre-http-v2, the default VCC).
763
764      Our server should have told us if it supported this protocol
765      correction.  If so, we aimed our report at the correct resource
766      and include the filtering path as metadata within the report
767      body.  Otherwise, we fall back to the pre-1.8 behavior and just
768      wish for the best.
769
770      See issue #4287:
771      http://subversion.tigris.org/issues/show_bug.cgi?id=4287
772   */
773   if (session->supports_rev_rsrc_replay)
774     {
775       SVN_ERR(svn_ra_serf__get_relative_path(&include_path,
776                                              session->session_url.path,
777                                              session, session->conns[0],
778                                              pool));
779     }
780   else
781     {
782       include_path = NULL;
783     }
784
785   while (active_reports || rev <= end_revision)
786     {
787       svn_ra_serf__list_t *done_list;
788       svn_ra_serf__list_t *done_reports = NULL;
789       replay_context_t *replay_ctx;
790
791       if (session->cancel_func)
792         SVN_ERR(session->cancel_func(session->cancel_baton));
793
794       /* Send pending requests, if any. Limit the number of outstanding
795          requests to MAX_OUTSTANDING_REQUESTS. */
796       if (rev <= end_revision  && active_reports < MAX_OUTSTANDING_REQUESTS)
797         {
798           svn_ra_serf__handler_t *handler;
799           svn_ra_serf__xml_parser_t *parser_ctx;
800           apr_pool_t *ctx_pool = svn_pool_create(pool);
801           const char *replay_target;
802
803           replay_ctx = apr_pcalloc(ctx_pool, sizeof(*replay_ctx));
804           replay_ctx->src_rev_pool = ctx_pool;
805           replay_ctx->revstart_func = revstart_func;
806           replay_ctx->revfinish_func = revfinish_func;
807           replay_ctx->replay_baton = replay_baton;
808           replay_ctx->done = FALSE;
809           replay_ctx->include_path = include_path;
810           replay_ctx->revision = rev;
811           replay_ctx->low_water_mark = low_water_mark;
812           replay_ctx->send_deltas = send_deltas;
813           replay_ctx->done_item.data = replay_ctx;
814
815           /* Request all properties of a certain revision. */
816           replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
817
818           if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
819             {
820               replay_ctx->revprop_target = apr_psprintf(pool, "%s/%ld",
821                                                         session->rev_stub, rev);
822               replay_ctx->revprop_rev = SVN_INVALID_REVNUM;
823             }
824           else
825             {
826               replay_ctx->revprop_target = report_target;
827               replay_ctx->revprop_rev = rev;
828             }
829
830           SVN_ERR(svn_ra_serf__deliver_props(&replay_ctx->propfind_handler,
831                                              replay_ctx->revs_props, session,
832                                              session->conns[0],
833                                              replay_ctx->revprop_target,
834                                              replay_ctx->revprop_rev,
835                                              "0", all_props,
836                                              NULL,
837                                              replay_ctx->src_rev_pool));
838
839           /* Spin up the serf request for the PROPFIND.  */
840           svn_ra_serf__request_create(replay_ctx->propfind_handler);
841
842           /* Send the replay REPORT request. */
843           if (session->supports_rev_rsrc_replay)
844             {
845               replay_target = apr_psprintf(pool, "%s/%ld",
846                                            session->rev_stub, rev);
847             }
848           else
849             {
850               replay_target = session->session_url.path;
851             }
852
853           handler = apr_pcalloc(replay_ctx->src_rev_pool, sizeof(*handler));
854
855           handler->handler_pool = replay_ctx->src_rev_pool;
856           handler->method = "REPORT";
857           handler->path = replay_target;
858           handler->body_delegate = create_replay_body;
859           handler->body_delegate_baton = replay_ctx;
860           handler->conn = session->conns[0];
861           handler->session = session;
862
863           parser_ctx = apr_pcalloc(replay_ctx->src_rev_pool,
864                                    sizeof(*parser_ctx));
865
866           /* Setup the XML parser context.
867              Because we have not one but a list of requests, the 'done' property
868              on the replay_ctx is not of much use. Instead, use 'done_list'.
869              On each handled response (succesfully or not), the parser will add
870              done_item to done_list, so by keeping track of the state of
871              done_list we know how many requests have been handled completely.
872           */
873           parser_ctx->pool = replay_ctx->src_rev_pool;
874           parser_ctx->user_data = replay_ctx;
875           parser_ctx->start = start_replay;
876           parser_ctx->end = end_replay;
877           parser_ctx->cdata = cdata_replay;
878           parser_ctx->done = &replay_ctx->done;
879           parser_ctx->done_list = &done_reports;
880           parser_ctx->done_item = &replay_ctx->done_item;
881           handler->response_handler = svn_ra_serf__handle_xml_parser;
882           handler->response_baton = parser_ctx;
883           replay_ctx->report_handler = handler;
884
885           /* This is only needed to handle errors during XML parsing. */
886           replay_ctx->parser_ctx = parser_ctx;
887
888           svn_ra_serf__request_create(handler);
889
890           rev++;
891           active_reports++;
892         }
893
894       /* Run the serf loop. */
895       SVN_ERR(svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool));
896
897       /* Substract the number of completely handled responses from our
898          total nr. of open requests', so we'll know when to stop this loop.
899          Since the message is completely handled, we can destroy its pool. */
900       done_list = done_reports;
901       while (done_list)
902         {
903           replay_context_t *ctx = (replay_context_t *)done_list->data;
904           svn_ra_serf__handler_t *done_handler = ctx->report_handler;
905
906           done_list = done_list->next;
907           SVN_ERR(svn_ra_serf__error_on_status(done_handler->sline,
908                                                done_handler->path,
909                                                done_handler->location));
910           svn_pool_destroy(ctx->src_rev_pool);
911           active_reports--;
912         }
913
914       done_reports = NULL;
915     }
916
917   return SVN_NO_ERROR;
918 }
919 #undef MAX_OUTSTANDING_REQUESTS