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