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