]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_ra_serf/update.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 / update.c
1 /*
2  * update.c :  entry point for update 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 #define APR_WANT_STRFUNC
27 #include <apr_version.h>
28 #include <apr_want.h>
29
30 #include <apr_uri.h>
31
32 #include <serf.h>
33
34 #include "svn_hash.h"
35 #include "svn_pools.h"
36 #include "svn_ra.h"
37 #include "svn_dav.h"
38 #include "svn_xml.h"
39 #include "svn_delta.h"
40 #include "svn_path.h"
41 #include "svn_base64.h"
42 #include "svn_props.h"
43
44 #include "svn_private_config.h"
45 #include "private/svn_dep_compat.h"
46 #include "private/svn_fspath.h"
47 #include "private/svn_string_private.h"
48
49 #include "ra_serf.h"
50 #include "../libsvn_ra/ra_loader.h"
51
52 \f
53 /*
54  * This enum represents the current state of our XML parsing for a REPORT.
55  *
56  * A little explanation of how the parsing works.  Every time we see
57  * an open-directory tag, we enter the OPEN_DIR state.  Likewise, for
58  * add-directory, open-file, etc.  When we see the closing variant of the
59  * open-directory tag, we'll 'pop' out of that state.
60  *
61  * Each state has a pool associated with it that can have temporary
62  * allocations that will live as long as the tag is opened.  Once
63  * the tag is 'closed', the pool will be reused.
64  */
65 typedef enum report_state_e {
66     NONE = 0,
67     INITIAL = 0,
68     UPDATE_REPORT,
69     TARGET_REVISION,
70     OPEN_DIR,
71     ADD_DIR,
72     ABSENT_DIR,
73     OPEN_FILE,
74     ADD_FILE,
75     ABSENT_FILE,
76     PROP,
77     IGNORE_PROP_NAME,
78     NEED_PROP_NAME,
79     TXDELTA
80 } report_state_e;
81
82
83 /* While we process the REPORT response, we will queue up GET and PROPFIND
84    requests. For a very large checkout, it is very easy to queue requests
85    faster than they are resolved. Thus, we need to pause the XML processing
86    (which queues more requests) to avoid queueing too many, with their
87    attendant memory costs. When the queue count drops low enough, we will
88    resume XML processing.
89
90    Note that we don't want the count to drop to zero. We have multiple
91    connections that we want to keep busy. These are also heuristic numbers
92    since network and parsing behavior (ie. it doesn't pause immediately)
93    can make the measurements quite imprecise.
94
95    We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and
96    NUM_ACTIVE_PROPFINDS in the report_context_t structure.  */
97 #define REQUEST_COUNT_TO_PAUSE 50
98 #define REQUEST_COUNT_TO_RESUME 40
99
100
101 /* Forward-declare our report context. */
102 typedef struct report_context_t report_context_t;
103
104 /*
105  * This structure represents the information for a directory.
106  */
107 typedef struct report_dir_t
108 {
109   /* Our parent directory.
110    *
111    * This value is NULL when we are the root.
112    */
113   struct report_dir_t *parent_dir;
114
115   apr_pool_t *pool;
116
117   /* Pointer back to our original report context. */
118   report_context_t *report_context;
119
120   /* Our name sans any parents. */
121   const char *base_name;
122
123   /* the expanded directory name (including all parent names) */
124   const char *name;
125
126   /* the canonical url for this directory after updating. (received) */
127   const char *url;
128
129   /* The original repos_relpath of this url (from the working copy)
130      or NULL if the repos_relpath can be calculated from the edit root. */
131   const char *repos_relpath;
132
133   /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */
134   svn_revnum_t base_rev;
135
136   /* controlling dir baton - this is only created in ensure_dir_opened() */
137   void *dir_baton;
138   apr_pool_t *dir_baton_pool;
139
140   /* How many references to this directory do we still have open? */
141   apr_size_t ref_count;
142
143   /* Namespace list allocated out of this ->pool. */
144   svn_ra_serf__ns_t *ns_list;
145
146   /* hashtable for all of the properties (shared within a dir) */
147   apr_hash_t *props;
148
149   /* hashtable for all to-be-removed properties (shared within a dir) */
150   apr_hash_t *removed_props;
151
152   /* The propfind request for our current directory */
153   svn_ra_serf__handler_t *propfind_handler;
154
155   /* Has the server told us to fetch the dir props? */
156   svn_boolean_t fetch_props;
157
158   /* Have we closed the directory tag (meaning no more additions)? */
159   svn_boolean_t tag_closed;
160
161   /* The children of this directory  */
162   struct report_dir_t *children;
163
164   /* The next sibling of this directory */
165   struct report_dir_t *sibling;
166 } report_dir_t;
167
168 /*
169  * This structure represents the information for a file.
170  *
171  * A directory may have a report_info_t associated with it as well.
172  *
173  * This structure is created as we parse the REPORT response and
174  * once the element is completed, we create a report_fetch_t structure
175  * to give to serf to retrieve this file.
176  */
177 typedef struct report_info_t
178 {
179   apr_pool_t *pool;
180
181   /* The enclosing directory.
182    *
183    * If this structure refers to a directory, the dir it points to will be
184    * itself.
185    */
186   report_dir_t *dir;
187
188   /* Our name sans any directory info. */
189   const char *base_name;
190
191   /* the expanded file name (including all parent directory names) */
192   const char *name;
193
194   /* the canonical url for this file. */
195   const char *url;
196
197   /* lock token, if we had one to start off with. */
198   const char *lock_token;
199
200   /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */
201   svn_revnum_t base_rev;
202
203   /* our delta base, if present (NULL if we're adding the file) */
204   const char *delta_base;
205
206   /* Path of original item if add with history */
207   const char *copyfrom_path;
208
209   /* Revision of original item if add with history */
210   svn_revnum_t copyfrom_rev;
211
212   /* The propfind request for our current file (if present) */
213   svn_ra_serf__handler_t *propfind_handler;
214
215   /* Has the server told us to fetch the file props? */
216   svn_boolean_t fetch_props;
217
218   /* Has the server told us to go fetch - only valid if we had it already */
219   svn_boolean_t fetch_file;
220
221   /* The properties for this file */
222   apr_hash_t *props;
223
224   /* pool passed to update->add_file, etc. */
225   apr_pool_t *editor_pool;
226
227   /* controlling file_baton and textdelta handler */
228   void *file_baton;
229   const char *base_checksum;
230   const char *final_sha1_checksum;
231   svn_txdelta_window_handler_t textdelta;
232   void *textdelta_baton;
233   svn_stream_t *svndiff_decoder;
234   svn_stream_t *base64_decoder;
235
236   /* Checksum for close_file */
237   const char *final_checksum;
238
239   /* Stream containing file contents already cached in the working
240      copy (which may be used to avoid a GET request for the same). */
241   svn_stream_t *cached_contents;
242
243   /* temporary property for this file which is currently being parsed
244    * It will eventually be stored in our parent directory's property hash.
245    */
246   const char *prop_ns;
247   const char *prop_name;
248   svn_stringbuf_t *prop_value;
249   const char *prop_encoding;
250 } report_info_t;
251
252 /*
253  * This structure represents a single request to GET (fetch) a file with
254  * its associated Serf session/connection.
255  */
256 typedef struct report_fetch_t {
257
258   /* The handler representing this particular fetch.  */
259   svn_ra_serf__handler_t *handler;
260
261   /* The session we should use to fetch the file. */
262   svn_ra_serf__session_t *sess;
263
264   /* The connection we should use to fetch file. */
265   svn_ra_serf__connection_t *conn;
266
267   /* Stores the information for the file we want to fetch. */
268   report_info_t *info;
269
270   /* Have we read our response headers yet? */
271   svn_boolean_t read_headers;
272
273   /* This flag is set when our response is aborted before we reach the
274    * end and we decide to requeue this request.
275    */
276   svn_boolean_t aborted_read;
277   apr_off_t aborted_read_size;
278
279   /* This is the amount of data that we have read so far. */
280   apr_off_t read_size;
281
282   /* If we're receiving an svndiff, this will be non-NULL. */
283   svn_stream_t *delta_stream;
284
285   /* If we're writing this file to a stream, this will be non-NULL. */
286   svn_stream_t *target_stream;
287
288   /* Are we done fetching this file? */
289   svn_boolean_t done;
290
291   /* Discard the rest of the content? */
292   svn_boolean_t discard;
293
294   svn_ra_serf__list_t **done_list;
295   svn_ra_serf__list_t done_item;
296
297 } report_fetch_t;
298
299 /*
300  * The master structure for a REPORT request and response.
301  */
302 struct report_context_t {
303   apr_pool_t *pool;
304
305   svn_ra_serf__session_t *sess;
306   svn_ra_serf__connection_t *conn;
307
308   /* Source path and destination path */
309   const char *source;
310   const char *destination;
311
312   /* Our update target. */
313   const char *update_target;
314
315   /* What is the target revision that we want for this REPORT? */
316   svn_revnum_t target_rev;
317
318   /* Have we been asked to ignore ancestry or textdeltas? */
319   svn_boolean_t ignore_ancestry;
320   svn_boolean_t text_deltas;
321
322   /* Do we want the server to send copyfrom args or not? */
323   svn_boolean_t send_copyfrom_args;
324
325   /* Is the server sending everything in one response? */
326   svn_boolean_t send_all_mode;
327
328   /* Is the server including properties inline for newly added
329      files/dirs? */
330   svn_boolean_t add_props_included;
331
332   /* Path -> const char *repos_relpath mapping */
333   apr_hash_t *switched_paths;
334
335   /* Boolean indicating whether "" is switched.
336      (This indicates that the we are updating a single file) */
337   svn_boolean_t root_is_switched;
338
339   /* Our master update editor and baton. */
340   const svn_delta_editor_t *update_editor;
341   void *update_baton;
342
343   /* The file holding request body for the REPORT.
344    *
345    * ### todo: It will be better for performance to store small
346    * request bodies (like 4k) in memory and bigger bodies on disk.
347    */
348   apr_file_t *body_file;
349
350   /* root directory object */
351   report_dir_t *root_dir;
352
353   /* number of pending GET requests */
354   unsigned int num_active_fetches;
355
356   /* completed fetches (contains report_fetch_t) */
357   svn_ra_serf__list_t *done_fetches;
358
359   /* number of pending PROPFIND requests */
360   unsigned int num_active_propfinds;
361
362   /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */
363   svn_ra_serf__list_t *done_propfinds;
364   svn_ra_serf__list_t *done_dir_propfinds;
365
366   /* list of outstanding prop changes (contains report_dir_t) */
367   svn_ra_serf__list_t *active_dir_propfinds;
368
369   /* list of files that only have prop changes (contains report_info_t) */
370   svn_ra_serf__list_t *file_propchanges_only;
371
372   /* The path to the REPORT request */
373   const char *path;
374
375   /* Are we done parsing the REPORT response? */
376   svn_boolean_t done;
377
378   /* Did we receive all data from the network? */
379   svn_boolean_t report_received;
380
381   /* Did we get a complete (non-truncated) report? */
382   svn_boolean_t report_completed;
383
384   /* The XML parser context for the REPORT response.  */
385   svn_ra_serf__xml_parser_t *parser_ctx;
386
387   /* Did we close the root directory? */
388   svn_boolean_t closed_root;
389 };
390
391
392 #ifdef NOT_USED_YET
393
394 #define D_ "DAV:"
395 #define S_ SVN_XML_NAMESPACE
396 static const svn_ra_serf__xml_transition_t update_ttable[] = {
397   { INITIAL, S_, "update-report", UPDATE_REPORT,
398     FALSE, { NULL }, FALSE },
399
400   { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
401     FALSE, { "rev", NULL }, TRUE },
402
403   { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
404     FALSE, { "rev", NULL }, TRUE },
405
406   { OPEN_DIR, S_, "open-directory", OPEN_DIR,
407     FALSE, { "rev", "name", NULL }, TRUE },
408
409   { OPEN_DIR, S_, "add-directory", ADD_DIR,
410     FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
411
412   { ADD_DIR, S_, "add-directory", ADD_DIR,
413     FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
414
415   { OPEN_DIR, S_, "open-file", OPEN_FILE,
416     FALSE, { "rev", "name", NULL }, TRUE },
417
418   { OPEN_DIR, S_, "add-file", ADD_FILE,
419     FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
420
421   { ADD_DIR, S_, "add-file", ADD_FILE,
422     FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
423
424   { OPEN_DIR, S_, "delete-entry", OPEN_FILE,
425     FALSE, { "?rev", "name", NULL }, TRUE },
426
427   { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
428     FALSE, { "name", NULL }, TRUE },
429
430   { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
431     FALSE, { "name", NULL }, TRUE },
432
433   { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
434     FALSE, { "name", NULL }, TRUE },
435
436   { ADD_DIR, S_, "absent-file", ABSENT_FILE,
437     FALSE, { "name", NULL }, TRUE },
438
439   { 0 }
440 };
441
442
443
444 /* Conforms to svn_ra_serf__xml_opened_t  */
445 static svn_error_t *
446 update_opened(svn_ra_serf__xml_estate_t *xes,
447               void *baton,
448               int entered_state,
449               const svn_ra_serf__dav_props_t *tag,
450               apr_pool_t *scratch_pool)
451 {
452   report_context_t *ctx = baton;
453
454   return SVN_NO_ERROR;
455 }
456
457
458
459 /* Conforms to svn_ra_serf__xml_closed_t  */
460 static svn_error_t *
461 update_closed(svn_ra_serf__xml_estate_t *xes,
462               void *baton,
463               int leaving_state,
464               const svn_string_t *cdata,
465               apr_hash_t *attrs,
466               apr_pool_t *scratch_pool)
467 {
468   report_context_t *ctx = baton;
469
470   if (leaving_state == TARGET_REVISION)
471     {
472       const char *rev = svn_hash_gets(attrs, "rev");
473
474       SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
475                                                       SVN_STR_TO_REV(rev),
476                                                       ctx->sess->pool));
477     }
478
479   return SVN_NO_ERROR;
480 }
481
482
483 /* Conforms to svn_ra_serf__xml_cdata_t  */
484 static svn_error_t *
485 update_cdata(svn_ra_serf__xml_estate_t *xes,
486              void *baton,
487              int current_state,
488              const char *data,
489              apr_size_t len,
490              apr_pool_t *scratch_pool)
491 {
492   report_context_t *ctx = baton;
493
494   return SVN_NO_ERROR;
495 }
496
497 #endif /* NOT_USED_YET */
498
499
500 /* Returns best connection for fetching files/properties. */
501 static svn_ra_serf__connection_t *
502 get_best_connection(report_context_t *ctx)
503 {
504   svn_ra_serf__connection_t *conn;
505   int first_conn = 1;
506
507   /* Skip the first connection if the REPORT response hasn't been completely
508      received yet or if we're being told to limit our connections to
509      2 (because this could be an attempt to ensure that we do all our
510      auxiliary GETs/PROPFINDs on a single connection).
511
512      ### FIXME: This latter requirement (max_connections > 2) is
513      ### really just a hack to work around the fact that some update
514      ### editor implementations (such as svnrdump's dump editor)
515      ### simply can't handle the way ra_serf violates the editor v1
516      ### drive ordering requirements.
517      ###
518      ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
519   */
520   if (ctx->report_received && (ctx->sess->max_connections > 2))
521     first_conn = 0;
522
523   /* Currently, we just cycle connections.  In the future we could
524      store the number of pending requests on each connection, or
525      perform other heuristics, to achieve better connection usage.
526      (As an optimization, if there's only one available auxiliary
527      connection to use, don't bother doing all the cur_conn math --
528      just return that one connection.)  */
529   if (ctx->sess->num_conns - first_conn == 1)
530     {
531       conn = ctx->sess->conns[first_conn];
532     }
533   else
534     {
535       conn = ctx->sess->conns[ctx->sess->cur_conn];
536       ctx->sess->cur_conn++;
537       if (ctx->sess->cur_conn >= ctx->sess->num_conns)
538         ctx->sess->cur_conn = first_conn;
539     }
540   return conn;
541 }
542
543 \f
544 /** Report state management helper **/
545
546 static report_info_t *
547 push_state(svn_ra_serf__xml_parser_t *parser,
548            report_context_t *ctx,
549            report_state_e state)
550 {
551   report_info_t *info;
552   apr_pool_t *info_parent_pool;
553
554   svn_ra_serf__xml_push_state(parser, state);
555
556   info = parser->state->private;
557
558   /* Our private pool needs to be disjoint from the state pool. */
559   if (!info)
560     {
561       info_parent_pool = ctx->pool;
562     }
563   else
564     {
565       info_parent_pool = info->pool;
566     }
567
568   if (state == OPEN_DIR || state == ADD_DIR)
569     {
570       report_info_t *new_info;
571
572       new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
573       new_info->pool = svn_pool_create(info_parent_pool);
574       new_info->lock_token = NULL;
575       new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
576
577       new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
578       new_info->dir->pool = new_info->pool;
579
580       /* Create the root property tree. */
581       new_info->dir->props = apr_hash_make(new_info->pool);
582       new_info->props = new_info->dir->props;
583       new_info->dir->removed_props = apr_hash_make(new_info->pool);
584
585       new_info->dir->report_context = ctx;
586
587       if (info)
588         {
589           info->dir->ref_count++;
590
591           new_info->dir->parent_dir = info->dir;
592
593           /* Point our ns_list at our parents to try to reuse it. */
594           new_info->dir->ns_list = info->dir->ns_list;
595
596           /* Add ourselves to our parent's list */
597           new_info->dir->sibling = info->dir->children;
598           info->dir->children = new_info->dir;
599         }
600       else
601         {
602           /* Allow us to be found later. */
603           ctx->root_dir = new_info->dir;
604         }
605
606       parser->state->private = new_info;
607     }
608   else if (state == OPEN_FILE || state == ADD_FILE)
609     {
610       report_info_t *new_info;
611
612       new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
613       new_info->pool = svn_pool_create(info_parent_pool);
614       new_info->file_baton = NULL;
615       new_info->lock_token = NULL;
616       new_info->fetch_file = FALSE;
617       new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
618
619       /* Point at our parent's directory state. */
620       new_info->dir = info->dir;
621       info->dir->ref_count++;
622
623       new_info->props = apr_hash_make(new_info->pool);
624
625       parser->state->private = new_info;
626     }
627
628   return parser->state->private;
629 }
630
631 \f
632 /** Wrappers around our various property walkers **/
633
634 static svn_error_t *
635 set_file_props(void *baton,
636                const char *ns,
637                const char *name,
638                const svn_string_t *val,
639                apr_pool_t *scratch_pool)
640 {
641   report_info_t *info = baton;
642   const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
643   const char *prop_name;
644
645   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
646   if (prop_name != NULL)
647     return svn_error_trace(editor->change_file_prop(info->file_baton,
648                                                     prop_name,
649                                                     val,
650                                                     scratch_pool));
651   return SVN_NO_ERROR;
652 }
653
654
655 static svn_error_t *
656 set_dir_props(void *baton,
657               const char *ns,
658               const char *name,
659               const svn_string_t *val,
660               apr_pool_t *scratch_pool)
661 {
662   report_dir_t *dir = baton;
663   const svn_delta_editor_t *editor = dir->report_context->update_editor;
664   const char *prop_name;
665
666   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
667   if (prop_name != NULL)
668     return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
669                                                    prop_name,
670                                                    val,
671                                                    scratch_pool));
672   return SVN_NO_ERROR;
673 }
674
675
676 static svn_error_t *
677 remove_file_props(void *baton,
678                   const char *ns,
679                   const char *name,
680                   const svn_string_t *val,
681                   apr_pool_t *scratch_pool)
682 {
683   report_info_t *info = baton;
684   const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
685   const char *prop_name;
686
687   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
688   if (prop_name != NULL)
689     return svn_error_trace(editor->change_file_prop(info->file_baton,
690                                                     prop_name,
691                                                     NULL,
692                                                     scratch_pool));
693   return SVN_NO_ERROR;
694 }
695
696
697 static svn_error_t *
698 remove_dir_props(void *baton,
699                  const char *ns,
700                  const char *name,
701                  const svn_string_t *val,
702                  apr_pool_t *scratch_pool)
703 {
704   report_dir_t *dir = baton;
705   const svn_delta_editor_t *editor = dir->report_context->update_editor;
706   const char *prop_name;
707
708   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
709   if (prop_name != NULL)
710     return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
711                                                    prop_name,
712                                                    NULL,
713                                                    scratch_pool));
714   return SVN_NO_ERROR;
715 }
716
717 \f
718 /** Helpers to open and close directories */
719
720 static svn_error_t*
721 ensure_dir_opened(report_dir_t *dir)
722 {
723   report_context_t *ctx = dir->report_context;
724
725   /* if we're already open, return now */
726   if (dir->dir_baton)
727     {
728       return SVN_NO_ERROR;
729     }
730
731   if (dir->base_name[0] == '\0')
732     {
733       dir->dir_baton_pool = svn_pool_create(dir->pool);
734
735       if (ctx->destination
736           && ctx->sess->wc_callbacks->invalidate_wc_props)
737         {
738           SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
739                       ctx->sess->wc_callback_baton,
740                       ctx->update_target,
741                       SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
742         }
743
744       SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev,
745                                             dir->dir_baton_pool,
746                                             &dir->dir_baton));
747     }
748   else
749     {
750       SVN_ERR(ensure_dir_opened(dir->parent_dir));
751
752       dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool);
753
754       if (SVN_IS_VALID_REVNUM(dir->base_rev))
755         {
756           SVN_ERR(ctx->update_editor->open_directory(dir->name,
757                                                      dir->parent_dir->dir_baton,
758                                                      dir->base_rev,
759                                                      dir->dir_baton_pool,
760                                                      &dir->dir_baton));
761         }
762       else
763         {
764           SVN_ERR(ctx->update_editor->add_directory(dir->name,
765                                                     dir->parent_dir->dir_baton,
766                                                     NULL, SVN_INVALID_REVNUM,
767                                                     dir->dir_baton_pool,
768                                                     &dir->dir_baton));
769         }
770     }
771
772   return SVN_NO_ERROR;
773 }
774
775 static svn_error_t *
776 close_dir(report_dir_t *dir)
777 {
778   report_dir_t *prev;
779   report_dir_t *sibling;
780
781   /* ### is there a better pool... this is tossed at end-of-func  */
782   apr_pool_t *scratch_pool = dir->dir_baton_pool;
783
784   SVN_ERR_ASSERT(! dir->ref_count);
785
786   SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name,
787                                       dir->base_rev,
788                                       set_dir_props, dir,
789                                       scratch_pool));
790
791   SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name,
792                                       dir->base_rev, remove_dir_props, dir,
793                                       scratch_pool));
794
795   if (dir->fetch_props)
796     {
797       SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url,
798                                           dir->report_context->target_rev,
799                                           set_dir_props, dir,
800                                           scratch_pool));
801     }
802
803   SVN_ERR(dir->report_context->update_editor->close_directory(
804             dir->dir_baton, scratch_pool));
805
806   /* remove us from our parent's children list */
807   if (dir->parent_dir)
808     {
809       prev = NULL;
810       sibling = dir->parent_dir->children;
811
812       while (sibling != dir)
813         {
814           prev = sibling;
815           sibling = sibling->sibling;
816           if (!sibling)
817             SVN_ERR_MALFUNCTION();
818         }
819
820       if (!prev)
821         {
822           dir->parent_dir->children = dir->sibling;
823         }
824       else
825         {
826           prev->sibling = dir->sibling;
827         }
828     }
829
830   svn_pool_destroy(dir->dir_baton_pool);
831   svn_pool_destroy(dir->pool);
832
833   return SVN_NO_ERROR;
834 }
835
836 static svn_error_t *close_all_dirs(report_dir_t *dir)
837 {
838   while (dir->children)
839     {
840       SVN_ERR(close_all_dirs(dir->children));
841       dir->ref_count--;
842     }
843
844   SVN_ERR_ASSERT(! dir->ref_count);
845
846   SVN_ERR(ensure_dir_opened(dir));
847
848   return close_dir(dir);
849 }
850
851 \f
852 /** Routines called when we are fetching a file */
853
854 /* This function works around a bug in some older versions of
855  * mod_dav_svn in that it will not send remove-prop in the update
856  * report when a lock property disappears when send-all is false.
857  *
858  * Therefore, we'll try to look at our properties and see if there's
859  * an active lock.  If not, then we'll assume there isn't a lock
860  * anymore.
861  */
862 static void
863 check_lock(report_info_t *info)
864 {
865   const char *lock_val;
866
867   lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
868                                        info->dir->report_context->target_rev,
869                                        "DAV:", "lockdiscovery");
870
871   if (lock_val)
872     {
873       char *new_lock;
874       new_lock = apr_pstrdup(info->editor_pool, lock_val);
875       apr_collapse_spaces(new_lock, new_lock);
876       lock_val = new_lock;
877     }
878
879   if (!lock_val || lock_val[0] == '\0')
880     {
881       svn_string_t *str;
882
883       str = svn_string_ncreate("", 1, info->editor_pool);
884
885       svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name,
886                                 info->base_rev, "DAV:", "lock-token",
887                                 str, info->dir->pool);
888     }
889 }
890
891 static svn_error_t *
892 headers_fetch(serf_bucket_t *headers,
893               void *baton,
894               apr_pool_t *pool)
895 {
896   report_fetch_t *fetch_ctx = baton;
897
898   /* note that we have old VC URL */
899   if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) &&
900       fetch_ctx->info->delta_base)
901     {
902       serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
903                                fetch_ctx->info->delta_base);
904       serf_bucket_headers_setn(headers, "Accept-Encoding",
905                                "svndiff1;q=0.9,svndiff;q=0.8");
906     }
907   else if (fetch_ctx->sess->using_compression)
908     {
909       serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
910     }
911
912   return SVN_NO_ERROR;
913 }
914
915 static svn_error_t *
916 cancel_fetch(serf_request_t *request,
917              serf_bucket_t *response,
918              int status_code,
919              void *baton)
920 {
921   report_fetch_t *fetch_ctx = baton;
922
923   /* Uh-oh.  Our connection died on us.
924    *
925    * The core ra_serf layer will requeue our request - we just need to note
926    * that we got cut off in the middle of our song.
927    */
928   if (!response)
929     {
930       /* If we already started the fetch and opened the file handle, we need
931        * to hold subsequent read() ops until we get back to where we were
932        * before the close and we can then resume the textdelta() calls.
933        */
934       if (fetch_ctx->read_headers)
935         {
936           if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
937             {
938               fetch_ctx->aborted_read = TRUE;
939               fetch_ctx->aborted_read_size = fetch_ctx->read_size;
940             }
941           fetch_ctx->read_size = 0;
942         }
943
944       return SVN_NO_ERROR;
945     }
946
947   /* We have no idea what went wrong. */
948   SVN_ERR_MALFUNCTION();
949 }
950
951 static svn_error_t *
952 error_fetch(serf_request_t *request,
953             report_fetch_t *fetch_ctx,
954             svn_error_t *err)
955 {
956   fetch_ctx->done = TRUE;
957
958   fetch_ctx->done_item.data = fetch_ctx;
959   fetch_ctx->done_item.next = *fetch_ctx->done_list;
960   *fetch_ctx->done_list = &fetch_ctx->done_item;
961
962   /* Discard the rest of this request
963      (This makes sure it doesn't error when the request is aborted later) */
964   serf_request_set_handler(request,
965                            svn_ra_serf__response_discard_handler, NULL);
966
967   /* Some errors would be handled by serf; make sure they really make
968      the update fail by wrapping it in a different error. */
969   if (!SERF_BUCKET_READ_ERROR(err->apr_err))
970     return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
971
972   return err;
973 }
974
975 /* Wield the editor referenced by INFO to open (or add) the file
976    file also associated with INFO, setting properties on the file and
977    calling the editor's apply_textdelta() function on it if necessary
978    (or if FORCE_APPLY_TEXTDELTA is set).
979
980    Callers will probably want to also see the function that serves
981    the opposite purpose of this one, close_updated_file().  */
982 static svn_error_t *
983 open_updated_file(report_info_t *info,
984                   svn_boolean_t force_apply_textdelta,
985                   apr_pool_t *scratch_pool)
986 {
987   report_context_t *ctx = info->dir->report_context;
988   const svn_delta_editor_t *update_editor = ctx->update_editor;
989
990   /* Ensure our parent is open. */
991   SVN_ERR(ensure_dir_opened(info->dir));
992   info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
993
994   /* Expand our full name now if we haven't done so yet. */
995   if (!info->name)
996     {
997       info->name = svn_relpath_join(info->dir->name, info->base_name,
998                                     info->editor_pool);
999     }
1000
1001   /* Open (or add) the file. */
1002   if (SVN_IS_VALID_REVNUM(info->base_rev))
1003     {
1004       SVN_ERR(update_editor->open_file(info->name,
1005                                        info->dir->dir_baton,
1006                                        info->base_rev,
1007                                        info->editor_pool,
1008                                        &info->file_baton));
1009     }
1010   else
1011     {
1012       SVN_ERR(update_editor->add_file(info->name,
1013                                       info->dir->dir_baton,
1014                                       info->copyfrom_path,
1015                                       info->copyfrom_rev,
1016                                       info->editor_pool,
1017                                       &info->file_baton));
1018     }
1019
1020   /* Check for lock information. */
1021   if (info->lock_token)
1022     check_lock(info);
1023
1024   /* Get (maybe) a textdelta window handler for transmitting file
1025      content changes. */
1026   if (info->fetch_file || force_apply_textdelta)
1027     {
1028       SVN_ERR(update_editor->apply_textdelta(info->file_baton,
1029                                              info->base_checksum,
1030                                              info->editor_pool,
1031                                              &info->textdelta,
1032                                              &info->textdelta_baton));
1033     }
1034
1035   return SVN_NO_ERROR;
1036 }
1037
1038 /* Close the file associated with INFO->file_baton, and cleanup other
1039    bits of that structure managed by open_updated_file(). */
1040 static svn_error_t *
1041 close_updated_file(report_info_t *info,
1042                    apr_pool_t *scratch_pool)
1043 {
1044   report_context_t *ctx = info->dir->report_context;
1045
1046   /* Set all of the properties we received */
1047   SVN_ERR(svn_ra_serf__walk_all_props(info->props,
1048                                       info->base_name,
1049                                       info->base_rev,
1050                                       set_file_props, info,
1051                                       scratch_pool));
1052   SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
1053                                       info->base_name,
1054                                       info->base_rev,
1055                                       remove_file_props, info,
1056                                       scratch_pool));
1057   if (info->fetch_props)
1058     {
1059       SVN_ERR(svn_ra_serf__walk_all_props(info->props,
1060                                           info->url,
1061                                           ctx->target_rev,
1062                                           set_file_props, info,
1063                                           scratch_pool));
1064     }
1065
1066   /* Close the file via the editor. */
1067   SVN_ERR(info->dir->report_context->update_editor->close_file(
1068             info->file_baton, info->final_checksum, scratch_pool));
1069
1070   /* We're done with our editor pool. */
1071   svn_pool_destroy(info->editor_pool);
1072
1073   return SVN_NO_ERROR;
1074 }
1075
1076 /* Implements svn_ra_serf__response_handler_t */
1077 static svn_error_t *
1078 handle_fetch(serf_request_t *request,
1079              serf_bucket_t *response,
1080              void *handler_baton,
1081              apr_pool_t *pool)
1082 {
1083   const char *data;
1084   apr_size_t len;
1085   apr_status_t status;
1086   report_fetch_t *fetch_ctx = handler_baton;
1087   svn_error_t *err;
1088
1089   /* ### new field. make sure we didn't miss some initialization.  */
1090   SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1091
1092   if (!fetch_ctx->read_headers)
1093     {
1094       serf_bucket_t *hdrs;
1095       const char *val;
1096       report_info_t *info;
1097
1098       hdrs = serf_bucket_response_get_headers(response);
1099       val = serf_bucket_headers_get(hdrs, "Content-Type");
1100       info = fetch_ctx->info;
1101
1102       if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
1103         {
1104           fetch_ctx->delta_stream =
1105               svn_txdelta_parse_svndiff(info->textdelta,
1106                                         info->textdelta_baton,
1107                                         TRUE, info->editor_pool);
1108
1109           /* Validate the delta base claimed by the server matches
1110              what we asked for! */
1111           val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
1112           if (val && (strcmp(val, info->delta_base) != 0))
1113             {
1114               err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1115                                       _("GET request returned unexpected "
1116                                         "delta base: %s"), val);
1117               return error_fetch(request, fetch_ctx, err);
1118             }
1119         }
1120       else
1121         {
1122           fetch_ctx->delta_stream = NULL;
1123         }
1124
1125       fetch_ctx->read_headers = TRUE;
1126     }
1127
1128   /* If the error code wasn't 200, something went wrong. Don't use the returned
1129      data as its probably an error message. Just bail out instead. */
1130   if (fetch_ctx->handler->sline.code != 200)
1131     {
1132       err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1133                               _("GET request failed: %d %s"),
1134                               fetch_ctx->handler->sline.code,
1135                               fetch_ctx->handler->sline.reason);
1136       return error_fetch(request, fetch_ctx, err);
1137     }
1138
1139   while (1)
1140     {
1141       svn_txdelta_window_t delta_window = { 0 };
1142       svn_txdelta_op_t delta_op;
1143       svn_string_t window_data;
1144
1145       status = serf_bucket_read(response, 8000, &data, &len);
1146       if (SERF_BUCKET_READ_ERROR(status))
1147         {
1148           return svn_ra_serf__wrap_err(status, NULL);
1149         }
1150
1151       fetch_ctx->read_size += len;
1152
1153       if (fetch_ctx->aborted_read)
1154         {
1155           apr_off_t skip;
1156           /* We haven't caught up to where we were before. */
1157           if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1158             {
1159               /* Eek.  What did the file shrink or something? */
1160               if (APR_STATUS_IS_EOF(status))
1161                 {
1162                   SVN_ERR_MALFUNCTION();
1163                 }
1164
1165               /* Skip on to the next iteration of this loop. */
1166               if (APR_STATUS_IS_EAGAIN(status))
1167                 {
1168                   return svn_ra_serf__wrap_err(status, NULL);
1169                 }
1170               continue;
1171             }
1172
1173           /* Woo-hoo.  We're back. */
1174           fetch_ctx->aborted_read = FALSE;
1175
1176           /* Update data and len to just provide the new data. */
1177           skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
1178           data += skip;
1179           len -= skip;
1180         }
1181
1182       if (fetch_ctx->delta_stream)
1183         {
1184           err = svn_stream_write(fetch_ctx->delta_stream, data, &len);
1185           if (err)
1186             {
1187               return error_fetch(request, fetch_ctx, err);
1188             }
1189         }
1190       /* otherwise, manually construct the text delta window. */
1191       else if (len)
1192         {
1193           window_data.data = data;
1194           window_data.len = len;
1195
1196           delta_op.action_code = svn_txdelta_new;
1197           delta_op.offset = 0;
1198           delta_op.length = len;
1199
1200           delta_window.tview_len = len;
1201           delta_window.num_ops = 1;
1202           delta_window.ops = &delta_op;
1203           delta_window.new_data = &window_data;
1204
1205           /* write to the file located in the info. */
1206           err = fetch_ctx->info->textdelta(&delta_window,
1207                                            fetch_ctx->info->textdelta_baton);
1208           if (err)
1209             {
1210               return error_fetch(request, fetch_ctx, err);
1211             }
1212         }
1213
1214       if (APR_STATUS_IS_EOF(status))
1215         {
1216           report_info_t *info = fetch_ctx->info;
1217
1218           if (fetch_ctx->delta_stream)
1219             err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream));
1220           else
1221             err = svn_error_trace(info->textdelta(NULL,
1222                                                   info->textdelta_baton));
1223           if (err)
1224             {
1225               return error_fetch(request, fetch_ctx, err);
1226             }
1227
1228           err = close_updated_file(info, info->pool);
1229           if (err)
1230             {
1231               return svn_error_trace(error_fetch(request, fetch_ctx, err));
1232             }
1233
1234           fetch_ctx->done = TRUE;
1235
1236           fetch_ctx->done_item.data = fetch_ctx;
1237           fetch_ctx->done_item.next = *fetch_ctx->done_list;
1238           *fetch_ctx->done_list = &fetch_ctx->done_item;
1239
1240           /* We're done with our pool. */
1241           svn_pool_destroy(info->pool);
1242
1243           if (status)
1244             return svn_ra_serf__wrap_err(status, NULL);
1245         }
1246       if (APR_STATUS_IS_EAGAIN(status))
1247         {
1248           return svn_ra_serf__wrap_err(status, NULL);
1249         }
1250     }
1251   /* not reached */
1252 }
1253
1254 /* Implements svn_ra_serf__response_handler_t */
1255 static svn_error_t *
1256 handle_stream(serf_request_t *request,
1257               serf_bucket_t *response,
1258               void *handler_baton,
1259               apr_pool_t *pool)
1260 {
1261   report_fetch_t *fetch_ctx = handler_baton;
1262   svn_error_t *err;
1263   apr_status_t status;
1264
1265   /* ### new field. make sure we didn't miss some initialization.  */
1266   SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1267
1268   err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline,
1269                                      fetch_ctx->info->name,
1270                                      fetch_ctx->handler->location);
1271   if (err)
1272     {
1273       fetch_ctx->handler->done = TRUE;
1274
1275       err = svn_error_compose_create(
1276                   err,
1277                   svn_ra_serf__handle_discard_body(request, response, NULL, pool));
1278
1279       return svn_error_trace(err);
1280     }
1281
1282   while (1)
1283     {
1284       const char *data;
1285       apr_size_t len;
1286
1287       status = serf_bucket_read(response, 8000, &data, &len);
1288       if (SERF_BUCKET_READ_ERROR(status))
1289         {
1290           return svn_ra_serf__wrap_err(status, NULL);
1291         }
1292
1293       fetch_ctx->read_size += len;
1294
1295       if (fetch_ctx->aborted_read)
1296         {
1297           /* We haven't caught up to where we were before. */
1298           if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1299             {
1300               /* Eek.  What did the file shrink or something? */
1301               if (APR_STATUS_IS_EOF(status))
1302                 {
1303                   SVN_ERR_MALFUNCTION();
1304                 }
1305
1306               /* Skip on to the next iteration of this loop. */
1307               if (APR_STATUS_IS_EAGAIN(status))
1308                 {
1309                   return svn_ra_serf__wrap_err(status, NULL);
1310                 }
1311               continue;
1312             }
1313
1314           /* Woo-hoo.  We're back. */
1315           fetch_ctx->aborted_read = FALSE;
1316
1317           /* Increment data and len by the difference. */
1318           data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1319           len += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1320         }
1321
1322       if (len)
1323         {
1324           apr_size_t written_len;
1325
1326           written_len = len;
1327
1328           SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data,
1329                                    &written_len));
1330         }
1331
1332       if (APR_STATUS_IS_EOF(status))
1333         {
1334           fetch_ctx->done = TRUE;
1335         }
1336
1337       if (status)
1338         {
1339           return svn_ra_serf__wrap_err(status, NULL);
1340         }
1341     }
1342   /* not reached */
1343 }
1344
1345 /* Close the directory represented by DIR -- and any suitable parents
1346    thereof -- if we are able to do so.  This is the case whenever:
1347
1348      - there are no remaining open items within the directory, and
1349      - the directory's XML close tag has been processed (so we know
1350        there are no more children to worry about in the future), and
1351      - either:
1352          - we aren't fetching properties for this directory, or
1353          - we've already finished fetching those properties.
1354 */
1355 static svn_error_t *
1356 maybe_close_dir_chain(report_dir_t *dir)
1357 {
1358   report_dir_t *cur_dir = dir;
1359
1360   SVN_ERR(ensure_dir_opened(cur_dir));
1361
1362   while (cur_dir
1363          && !cur_dir->ref_count
1364          && cur_dir->tag_closed
1365          && (!cur_dir->fetch_props || cur_dir->propfind_handler->done))
1366     {
1367       report_dir_t *parent = cur_dir->parent_dir;
1368       report_context_t *report_context = cur_dir->report_context;
1369       svn_boolean_t propfind_in_done_list = FALSE;
1370       svn_ra_serf__list_t *done_list;
1371
1372       /* Make sure there are no references to this dir in the
1373          active_dir_propfinds list.  If there are, don't close the
1374          directory -- which would delete the pool from which the
1375          relevant active_dir_propfinds list item is allocated -- and
1376          of course don't crawl upward to check the parents for
1377          a closure opportunity, either.  */
1378       done_list = report_context->active_dir_propfinds;
1379       while (done_list)
1380         {
1381           if (done_list->data == cur_dir)
1382             {
1383               propfind_in_done_list = TRUE;
1384               break;
1385             }
1386           done_list = done_list->next;
1387         }
1388       if (propfind_in_done_list)
1389         break;
1390
1391       SVN_ERR(close_dir(cur_dir));
1392       if (parent)
1393         {
1394           parent->ref_count--;
1395         }
1396       else
1397         {
1398           report_context->closed_root = TRUE;
1399         }
1400       cur_dir = parent;
1401     }
1402
1403   return SVN_NO_ERROR;
1404 }
1405
1406 /* Open the file associated with INFO for editing, pass along any
1407    propchanges we've recorded for it, and then close the file. */
1408 static svn_error_t *
1409 handle_propchange_only(report_info_t *info,
1410                        apr_pool_t *scratch_pool)
1411 {
1412   SVN_ERR(open_updated_file(info, FALSE, scratch_pool));
1413   SVN_ERR(close_updated_file(info, scratch_pool));
1414
1415   /* We're done with our pool. */
1416   svn_pool_destroy(info->pool);
1417
1418   info->dir->ref_count--;
1419
1420   /* See if the parent directory of this file (and perhaps even
1421      parents of that) can be closed now.  */
1422   SVN_ERR(maybe_close_dir_chain(info->dir));
1423
1424   return SVN_NO_ERROR;
1425 }
1426
1427 /* "Fetch" a file whose contents were made available via the
1428    get_wc_contents() callback (as opposed to requiring a GET to the
1429    server), and feed the information through the associated update
1430    editor.  In editor-speak, this will add/open the file, transmit any
1431    property changes, handle the contents, and then close the file.  */
1432 static svn_error_t *
1433 handle_local_content(report_info_t *info,
1434                      apr_pool_t *scratch_pool)
1435 {
1436   SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta,
1437                                   info->textdelta_baton, NULL, scratch_pool));
1438   SVN_ERR(svn_stream_close(info->cached_contents));
1439   info->cached_contents = NULL;
1440   SVN_ERR(close_updated_file(info, scratch_pool));
1441
1442   /* We're done with our pool. */
1443   svn_pool_destroy(info->pool);
1444
1445   info->dir->ref_count--;
1446
1447   /* See if the parent directory of this fetched item (and
1448      perhaps even parents of that) can be closed now. */
1449   SVN_ERR(maybe_close_dir_chain(info->dir));
1450
1451   return SVN_NO_ERROR;
1452 }
1453
1454 /* --------------------------------------------------------- */
1455
1456 static svn_error_t *
1457 fetch_file(report_context_t *ctx, report_info_t *info)
1458 {
1459   svn_ra_serf__connection_t *conn;
1460   svn_ra_serf__handler_t *handler;
1461
1462   /* What connection should we go on? */
1463   conn = get_best_connection(ctx);
1464
1465   /* If needed, create the PROPFIND to retrieve the file's properties. */
1466   info->propfind_handler = NULL;
1467   if (info->fetch_props)
1468     {
1469       SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props,
1470                                          ctx->sess, conn, info->url,
1471                                          ctx->target_rev, "0", all_props,
1472                                          &ctx->done_propfinds,
1473                                          info->dir->pool));
1474       SVN_ERR_ASSERT(info->propfind_handler);
1475
1476       /* Create a serf request for the PROPFIND.  */
1477       svn_ra_serf__request_create(info->propfind_handler);
1478
1479       ctx->num_active_propfinds++;
1480     }
1481
1482   /* If we've been asked to fetch the file or it's an add, do so.
1483    * Otherwise, handle the case where only the properties changed.
1484    */
1485   if (info->fetch_file && ctx->text_deltas)
1486     {
1487       svn_stream_t *contents = NULL;
1488
1489       /* Open the file for editing. */
1490       SVN_ERR(open_updated_file(info, FALSE, info->pool));
1491
1492       if (info->textdelta == svn_delta_noop_window_handler)
1493         {
1494           /* There is nobody looking for an actual stream.
1495
1496              Just report an empty stream instead of fetching
1497              to be ingored data */
1498           info->cached_contents = svn_stream_empty(info->pool);
1499         }
1500       else if (ctx->sess->wc_callbacks->get_wc_contents
1501                && info->final_sha1_checksum)
1502         {
1503           svn_error_t *err = NULL;
1504           svn_checksum_t *checksum = NULL;
1505
1506           /* Parse the optional SHA1 checksum (1.7+) */
1507           err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
1508                                        info->final_sha1_checksum,
1509                                        info->pool);
1510
1511           /* Okay so far?  Let's try to get a stream on some readily
1512              available matching content. */
1513           if (!err && checksum)
1514             {
1515               err = ctx->sess->wc_callbacks->get_wc_contents(
1516                         ctx->sess->wc_callback_baton, &contents,
1517                         checksum, info->pool);
1518
1519               if (! err)
1520                 info->cached_contents = contents;
1521             }
1522
1523           if (err)
1524             {
1525               /* Meh.  Maybe we'll care one day why we're in an
1526                  errorful state, but this codepath is optional.  */
1527               svn_error_clear(err);
1528             }
1529         }
1530
1531       /* If the working copy can provide cached contents for this
1532          file, we don't have to fetch them from the server. */
1533       if (info->cached_contents)
1534         {
1535           /* If we'll be doing a PROPFIND for this file... */
1536           if (info->propfind_handler)
1537             {
1538               /* ... then we'll just leave ourselves a little "todo"
1539                  about that fact (and we'll deal with the file content
1540                  stuff later, after we've handled that PROPFIND
1541                  response. */
1542               svn_ra_serf__list_t *list_item;
1543
1544               list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1545               list_item->data = info;
1546               list_item->next = ctx->file_propchanges_only;
1547               ctx->file_propchanges_only = list_item;
1548             }
1549           else
1550             {
1551               /* Otherwise, if we've no PROPFIND to do, we might as
1552                  well take care of those locally accessible file
1553                  contents now. */
1554               SVN_ERR(handle_local_content(info, info->pool));
1555             }
1556         }
1557       else
1558         {
1559           /* Otherwise, we use a GET request for the file's contents. */
1560           report_fetch_t *fetch_ctx;
1561
1562           fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
1563           fetch_ctx->info = info;
1564           fetch_ctx->done_list = &ctx->done_fetches;
1565           fetch_ctx->sess = ctx->sess;
1566           fetch_ctx->conn = conn;
1567
1568           handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
1569
1570           handler->handler_pool = info->dir->pool;
1571           handler->method = "GET";
1572           handler->path = fetch_ctx->info->url;
1573
1574           handler->conn = conn;
1575           handler->session = ctx->sess;
1576
1577           handler->custom_accept_encoding = TRUE;
1578           handler->header_delegate = headers_fetch;
1579           handler->header_delegate_baton = fetch_ctx;
1580
1581           handler->response_handler = handle_fetch;
1582           handler->response_baton = fetch_ctx;
1583
1584           handler->response_error = cancel_fetch;
1585           handler->response_error_baton = fetch_ctx;
1586
1587           fetch_ctx->handler = handler;
1588
1589           svn_ra_serf__request_create(handler);
1590
1591           ctx->num_active_fetches++;
1592         }
1593     }
1594   else if (info->propfind_handler)
1595     {
1596       svn_ra_serf__list_t *list_item;
1597
1598       list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1599       list_item->data = info;
1600       list_item->next = ctx->file_propchanges_only;
1601       ctx->file_propchanges_only = list_item;
1602     }
1603   else
1604     {
1605       /* No propfind or GET request.  Just handle the prop changes now. */
1606       SVN_ERR(handle_propchange_only(info, info->pool));
1607     }
1608
1609   if (ctx->num_active_fetches + ctx->num_active_propfinds
1610       > REQUEST_COUNT_TO_PAUSE)
1611     ctx->parser_ctx->paused = TRUE;
1612
1613   return SVN_NO_ERROR;
1614 }
1615
1616 \f
1617 /** XML callbacks for our update-report response parsing */
1618
1619 static svn_error_t *
1620 start_report(svn_ra_serf__xml_parser_t *parser,
1621              svn_ra_serf__dav_props_t name,
1622              const char **attrs,
1623              apr_pool_t *scratch_pool)
1624 {
1625   report_context_t *ctx = parser->user_data;
1626   report_state_e state;
1627
1628   state = parser->state->current_state;
1629
1630   if (state == NONE && strcmp(name.name, "update-report") == 0)
1631     {
1632       const char *val;
1633
1634       val = svn_xml_get_attr_value("inline-props", attrs);
1635       if (val && (strcmp(val, "true") == 0))
1636         ctx->add_props_included = TRUE;
1637
1638       val = svn_xml_get_attr_value("send-all", attrs);
1639       if (val && (strcmp(val, "true") == 0))
1640         {
1641           ctx->send_all_mode = TRUE;
1642
1643           /* All properties are included in send-all mode. */
1644           ctx->add_props_included = TRUE;
1645         }
1646     }
1647   else if (state == NONE && strcmp(name.name, "target-revision") == 0)
1648     {
1649       const char *rev;
1650
1651       rev = svn_xml_get_attr_value("rev", attrs);
1652
1653       if (!rev)
1654         {
1655           return svn_error_create(
1656             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1657             _("Missing revision attr in target-revision element"));
1658         }
1659
1660       SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
1661                                                       SVN_STR_TO_REV(rev),
1662                                                       ctx->sess->pool));
1663     }
1664   else if (state == NONE && strcmp(name.name, "open-directory") == 0)
1665     {
1666       const char *rev;
1667       report_info_t *info;
1668
1669       rev = svn_xml_get_attr_value("rev", attrs);
1670
1671       if (!rev)
1672         {
1673           return svn_error_create(
1674             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1675             _("Missing revision attr in open-directory element"));
1676         }
1677
1678       info = push_state(parser, ctx, OPEN_DIR);
1679
1680       info->base_rev = SVN_STR_TO_REV(rev);
1681       info->dir->base_rev = info->base_rev;
1682       info->fetch_props = TRUE;
1683
1684       info->dir->base_name = "";
1685       info->dir->name = "";
1686
1687       info->base_name = info->dir->base_name;
1688       info->name = info->dir->name;
1689
1690       info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, "");
1691
1692       if (!info->dir->repos_relpath)
1693         SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath,
1694                                                ctx->sess->session_url.path,
1695                                                ctx->sess, ctx->conn,
1696                                                info->dir->pool));
1697     }
1698   else if (state == NONE)
1699     {
1700       /* do nothing as we haven't seen our valid start tag yet. */
1701     }
1702   else if ((state == OPEN_DIR || state == ADD_DIR) &&
1703            strcmp(name.name, "open-directory") == 0)
1704     {
1705       const char *rev, *dirname;
1706       report_dir_t *dir;
1707       report_info_t *info;
1708
1709       rev = svn_xml_get_attr_value("rev", attrs);
1710
1711       if (!rev)
1712         {
1713           return svn_error_create(
1714             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1715             _("Missing revision attr in open-directory element"));
1716         }
1717
1718       dirname = svn_xml_get_attr_value("name", attrs);
1719
1720       if (!dirname)
1721         {
1722           return svn_error_create(
1723             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1724             _("Missing name attr in open-directory element"));
1725         }
1726
1727       info = push_state(parser, ctx, OPEN_DIR);
1728
1729       dir = info->dir;
1730
1731       info->base_rev = SVN_STR_TO_REV(rev);
1732       dir->base_rev = info->base_rev;
1733
1734       info->fetch_props = FALSE;
1735
1736       dir->base_name = apr_pstrdup(dir->pool, dirname);
1737       info->base_name = dir->base_name;
1738
1739       /* Expand our name. */
1740       dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
1741                                    dir->pool);
1742       info->name = dir->name;
1743
1744       dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name);
1745
1746       if (!dir->repos_relpath)
1747         dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
1748                                                dir->base_name, dir->pool);
1749     }
1750   else if ((state == OPEN_DIR || state == ADD_DIR) &&
1751            strcmp(name.name, "add-directory") == 0)
1752     {
1753       const char *dir_name, *cf, *cr;
1754       report_dir_t *dir;
1755       report_info_t *info;
1756
1757       dir_name = svn_xml_get_attr_value("name", attrs);
1758       if (!dir_name)
1759         {
1760           return svn_error_create(
1761             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1762             _("Missing name attr in add-directory element"));
1763         }
1764       cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1765       cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1766
1767       info = push_state(parser, ctx, ADD_DIR);
1768
1769       dir = info->dir;
1770
1771       dir->base_name = apr_pstrdup(dir->pool, dir_name);
1772       info->base_name = dir->base_name;
1773
1774       /* Expand our name. */
1775       dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
1776                                    dir->pool);
1777       info->name = dir->name;
1778
1779       info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1780       info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
1781
1782       /* Mark that we don't have a base. */
1783       info->base_rev = SVN_INVALID_REVNUM;
1784       dir->base_rev = info->base_rev;
1785
1786       /* If the server isn't included properties for added items,
1787          we'll need to fetch them ourselves. */
1788       if (! ctx->add_props_included)
1789         dir->fetch_props = TRUE;
1790
1791       dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
1792                                             dir->base_name, dir->pool);
1793     }
1794   else if ((state == OPEN_DIR || state == ADD_DIR) &&
1795            strcmp(name.name, "open-file") == 0)
1796     {
1797       const char *file_name, *rev;
1798       report_info_t *info;
1799
1800       file_name = svn_xml_get_attr_value("name", attrs);
1801
1802       if (!file_name)
1803         {
1804           return svn_error_create(
1805             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1806             _("Missing name attr in open-file element"));
1807         }
1808
1809       rev = svn_xml_get_attr_value("rev", attrs);
1810
1811       if (!rev)
1812         {
1813           return svn_error_create(
1814             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1815             _("Missing revision attr in open-file element"));
1816         }
1817
1818       info = push_state(parser, ctx, OPEN_FILE);
1819
1820       info->base_rev = SVN_STR_TO_REV(rev);
1821       info->fetch_props = FALSE;
1822
1823       info->base_name = apr_pstrdup(info->pool, file_name);
1824       info->name = NULL;
1825     }
1826   else if ((state == OPEN_DIR || state == ADD_DIR) &&
1827            strcmp(name.name, "add-file") == 0)
1828     {
1829       const char *file_name, *cf, *cr;
1830       report_info_t *info;
1831
1832       file_name = svn_xml_get_attr_value("name", attrs);
1833       cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1834       cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1835
1836       if (!file_name)
1837         {
1838           return svn_error_create(
1839             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1840             _("Missing name attr in add-file element"));
1841         }
1842
1843       info = push_state(parser, ctx, ADD_FILE);
1844
1845       info->base_rev = SVN_INVALID_REVNUM;
1846
1847       /* If the server isn't in "send-all" mode, we should expect to
1848          fetch contents for added files. */
1849       if (! ctx->send_all_mode)
1850         info->fetch_file = TRUE;
1851
1852       /* If the server isn't included properties for added items,
1853          we'll need to fetch them ourselves. */
1854       if (! ctx->add_props_included)
1855         info->fetch_props = TRUE;
1856
1857       info->base_name = apr_pstrdup(info->pool, file_name);
1858       info->name = NULL;
1859
1860       info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1861       info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
1862
1863       info->final_sha1_checksum =
1864         svn_xml_get_attr_value("sha1-checksum", attrs);
1865       if (info->final_sha1_checksum)
1866         info->final_sha1_checksum = apr_pstrdup(info->pool,
1867                                                 info->final_sha1_checksum);
1868     }
1869   else if ((state == OPEN_DIR || state == ADD_DIR) &&
1870            strcmp(name.name, "delete-entry") == 0)
1871     {
1872       const char *file_name;
1873       const char *rev_str;
1874       report_info_t *info;
1875       apr_pool_t *tmppool;
1876       const char *full_path;
1877       svn_revnum_t delete_rev = SVN_INVALID_REVNUM;
1878
1879       file_name = svn_xml_get_attr_value("name", attrs);
1880
1881       if (!file_name)
1882         {
1883           return svn_error_create(
1884             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1885             _("Missing name attr in delete-entry element"));
1886         }
1887
1888       rev_str = svn_xml_get_attr_value("rev", attrs);
1889       if (rev_str) /* Not available on older repositories! */
1890         delete_rev = SVN_STR_TO_REV(rev_str);
1891
1892       info = parser->state->private;
1893
1894       SVN_ERR(ensure_dir_opened(info->dir));
1895
1896       tmppool = svn_pool_create(info->dir->dir_baton_pool);
1897
1898       full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
1899
1900       SVN_ERR(ctx->update_editor->delete_entry(full_path,
1901                                                delete_rev,
1902                                                info->dir->dir_baton,
1903                                                tmppool));
1904
1905       svn_pool_destroy(tmppool);
1906     }
1907   else if ((state == OPEN_DIR || state == ADD_DIR) &&
1908            strcmp(name.name, "absent-directory") == 0)
1909     {
1910       const char *file_name;
1911       report_info_t *info;
1912
1913       file_name = svn_xml_get_attr_value("name", attrs);
1914
1915       if (!file_name)
1916         {
1917           return svn_error_create(
1918             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1919             _("Missing name attr in absent-directory element"));
1920         }
1921
1922       info = parser->state->private;
1923
1924       SVN_ERR(ensure_dir_opened(info->dir));
1925
1926       SVN_ERR(ctx->update_editor->absent_directory(
1927                                         svn_relpath_join(info->name, file_name,
1928                                                          info->dir->pool),
1929                                         info->dir->dir_baton,
1930                                         info->dir->pool));
1931     }
1932   else if ((state == OPEN_DIR || state == ADD_DIR) &&
1933            strcmp(name.name, "absent-file") == 0)
1934     {
1935       const char *file_name;
1936       report_info_t *info;
1937
1938       file_name = svn_xml_get_attr_value("name", attrs);
1939
1940       if (!file_name)
1941         {
1942           return svn_error_create(
1943             SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1944             _("Missing name attr in absent-file element"));
1945         }
1946
1947       info = parser->state->private;
1948
1949       SVN_ERR(ensure_dir_opened(info->dir));
1950
1951       SVN_ERR(ctx->update_editor->absent_file(
1952                                         svn_relpath_join(info->name, file_name,
1953                                                          info->dir->pool),
1954                                         info->dir->dir_baton,
1955                                         info->dir->pool));
1956     }
1957   else if (state == OPEN_DIR || state == ADD_DIR)
1958     {
1959       report_info_t *info;
1960
1961       if (strcmp(name.name, "checked-in") == 0)
1962         {
1963           info = push_state(parser, ctx, IGNORE_PROP_NAME);
1964           info->prop_ns = name.namespace;
1965           info->prop_name = apr_pstrdup(parser->state->pool, name.name);
1966           info->prop_encoding = NULL;
1967           svn_stringbuf_setempty(info->prop_value);
1968         }
1969       else if (strcmp(name.name, "set-prop") == 0 ||
1970                strcmp(name.name, "remove-prop") == 0)
1971         {
1972           const char *full_prop_name;
1973           const char *colon;
1974
1975           info = push_state(parser, ctx, PROP);
1976
1977           full_prop_name = svn_xml_get_attr_value("name", attrs);
1978           if (!full_prop_name)
1979             {
1980               return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1981                                        _("Missing name attr in %s element"),
1982                                        name.name);
1983             }
1984
1985           colon = strchr(full_prop_name, ':');
1986
1987           if (colon)
1988             colon++;
1989           else
1990             colon = full_prop_name;
1991
1992           info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
1993                                          colon - full_prop_name);
1994           info->prop_name = apr_pstrdup(parser->state->pool, colon);
1995           info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
1996           svn_stringbuf_setempty(info->prop_value);
1997         }
1998       else if (strcmp(name.name, "prop") == 0)
1999         {
2000           /* need to fetch it. */
2001           push_state(parser, ctx, NEED_PROP_NAME);
2002         }
2003       else if (strcmp(name.name, "fetch-props") == 0)
2004         {
2005           info = parser->state->private;
2006
2007           info->dir->fetch_props = TRUE;
2008         }
2009       else
2010         {
2011           return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2012                                    _("Unknown tag '%s' while at state %d"),
2013                                    name.name, state);
2014         }
2015
2016     }
2017   else if (state == OPEN_FILE || state == ADD_FILE)
2018     {
2019       report_info_t *info;
2020
2021       if (strcmp(name.name, "checked-in") == 0)
2022         {
2023           info = push_state(parser, ctx, IGNORE_PROP_NAME);
2024           info->prop_ns = name.namespace;
2025           info->prop_name = apr_pstrdup(parser->state->pool, name.name);
2026           info->prop_encoding = NULL;
2027           svn_stringbuf_setempty(info->prop_value);
2028         }
2029       else if (strcmp(name.name, "prop") == 0)
2030         {
2031           /* need to fetch it. */
2032           push_state(parser, ctx, NEED_PROP_NAME);
2033         }
2034       else if (strcmp(name.name, "fetch-props") == 0)
2035         {
2036           info = parser->state->private;
2037
2038           info->fetch_props = TRUE;
2039         }
2040       else if (strcmp(name.name, "fetch-file") == 0)
2041         {
2042           info = parser->state->private;
2043           info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs);
2044
2045           if (info->base_checksum)
2046             info->base_checksum = apr_pstrdup(info->pool, info->base_checksum);
2047
2048           info->final_sha1_checksum =
2049             svn_xml_get_attr_value("sha1-checksum", attrs);
2050           if (info->final_sha1_checksum)
2051             info->final_sha1_checksum = apr_pstrdup(info->pool,
2052                                                     info->final_sha1_checksum);
2053
2054           info->fetch_file = TRUE;
2055         }
2056       else if (strcmp(name.name, "set-prop") == 0 ||
2057                strcmp(name.name, "remove-prop") == 0)
2058         {
2059           const char *full_prop_name;
2060           const char *colon;
2061
2062           info = push_state(parser, ctx, PROP);
2063
2064           full_prop_name = svn_xml_get_attr_value("name", attrs);
2065           if (!full_prop_name)
2066             {
2067               return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2068                                        _("Missing name attr in %s element"),
2069                                        name.name);
2070             }
2071           colon = strchr(full_prop_name, ':');
2072
2073           if (colon)
2074             colon++;
2075           else
2076             colon = full_prop_name;
2077
2078           info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
2079                                          colon - full_prop_name);
2080           info->prop_name = apr_pstrdup(parser->state->pool, colon);
2081           info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2082           svn_stringbuf_setempty(info->prop_value);
2083         }
2084       else if (strcmp(name.name, "txdelta") == 0)
2085         {
2086           /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
2087              addition to <fetch-file>s and such) when *not* in
2088              "send-all" mode.  As a client, we're smart enough to know
2089              that's wrong, so we'll just ignore these tags. */
2090           if (ctx->send_all_mode)
2091             {
2092               const svn_delta_editor_t *update_editor = ctx->update_editor;
2093
2094               info = push_state(parser, ctx, TXDELTA);
2095
2096               if (! info->file_baton)
2097                 {
2098                   SVN_ERR(open_updated_file(info, FALSE, info->pool));
2099                 }
2100
2101               info->base_checksum = svn_xml_get_attr_value("base-checksum",
2102                                                            attrs);
2103               SVN_ERR(update_editor->apply_textdelta(info->file_baton,
2104                                                      info->base_checksum,
2105                                                      info->editor_pool,
2106                                                      &info->textdelta,
2107                                                      &info->textdelta_baton));
2108               info->svndiff_decoder = svn_txdelta_parse_svndiff(
2109                                           info->textdelta,
2110                                           info->textdelta_baton,
2111                                           TRUE, info->pool);
2112               info->base64_decoder = svn_base64_decode(info->svndiff_decoder,
2113                                                        info->pool);
2114             }
2115         }
2116       else
2117         {
2118           return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2119                                    _("Unknown tag '%s' while at state %d"),
2120                                    name.name, state);
2121         }
2122     }
2123   else if (state == IGNORE_PROP_NAME)
2124     {
2125       report_info_t *info = push_state(parser, ctx, PROP);
2126       info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2127     }
2128   else if (state == NEED_PROP_NAME)
2129     {
2130       report_info_t *info;
2131
2132       info = push_state(parser, ctx, PROP);
2133
2134       info->prop_ns = name.namespace;
2135       info->prop_name = apr_pstrdup(parser->state->pool, name.name);
2136       info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2137       svn_stringbuf_setempty(info->prop_value);
2138     }
2139
2140   return SVN_NO_ERROR;
2141 }
2142
2143 static svn_error_t *
2144 end_report(svn_ra_serf__xml_parser_t *parser,
2145            svn_ra_serf__dav_props_t name,
2146            apr_pool_t *scratch_pool)
2147 {
2148   report_context_t *ctx = parser->user_data;
2149   report_state_e state;
2150
2151   state = parser->state->current_state;
2152
2153   if (state == NONE)
2154     {
2155       if (strcmp(name.name, "update-report") == 0)
2156         {
2157           ctx->report_completed = TRUE;
2158         }
2159       else
2160         {
2161           /* nothing to close yet. */
2162           return SVN_NO_ERROR;
2163         }
2164     }
2165
2166   if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
2167        (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0))))
2168     {
2169       const char *checked_in_url;
2170       report_info_t *info = parser->state->private;
2171
2172       /* We've now closed this directory; note it. */
2173       info->dir->tag_closed = TRUE;
2174
2175       /* go fetch info->file_name from DAV:checked-in */
2176       checked_in_url =
2177           svn_ra_serf__get_ver_prop(info->dir->props, info->base_name,
2178                                     info->base_rev, "DAV:", "checked-in");
2179
2180       /* If we were expecting to have the properties and we aren't able to
2181        * get it, bail.
2182        */
2183       if (!checked_in_url &&
2184           (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
2185         {
2186           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2187                                   _("The REPORT or PROPFIND response did not "
2188                                     "include the requested checked-in value"));
2189         }
2190
2191       info->dir->url = checked_in_url;
2192
2193       /* At this point, we should have the checked-in href.
2194        * If needed, create the PROPFIND to retrieve the dir's properties.
2195        */
2196       if (info->dir->fetch_props)
2197         {
2198           svn_ra_serf__list_t *list_item;
2199
2200           SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler,
2201                                              info->dir->props, ctx->sess,
2202                                              get_best_connection(ctx),
2203                                              info->dir->url,
2204                                              ctx->target_rev, "0",
2205                                              all_props,
2206                                              &ctx->done_dir_propfinds,
2207                                              info->dir->pool));
2208           SVN_ERR_ASSERT(info->dir->propfind_handler);
2209
2210           /* Create a serf request for the PROPFIND.  */
2211           svn_ra_serf__request_create(info->dir->propfind_handler);
2212
2213           ctx->num_active_propfinds++;
2214
2215           list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
2216           list_item->data = info->dir;
2217           list_item->next = ctx->active_dir_propfinds;
2218           ctx->active_dir_propfinds = list_item;
2219
2220           if (ctx->num_active_fetches + ctx->num_active_propfinds
2221               > REQUEST_COUNT_TO_PAUSE)
2222             ctx->parser_ctx->paused = TRUE;
2223         }
2224       else
2225         {
2226           info->dir->propfind_handler = NULL;
2227         }
2228
2229       /* See if this directory (and perhaps even parents of that) can
2230          be closed now.  This is likely to be the case only if we
2231          didn't need to contact the server for supplemental
2232          information required to handle any of this directory's
2233          children.  */
2234       SVN_ERR(maybe_close_dir_chain(info->dir));
2235       svn_ra_serf__xml_pop_state(parser);
2236     }
2237   else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
2238     {
2239       report_info_t *info = parser->state->private;
2240
2241       /* Expand our full name now if we haven't done so yet. */
2242       if (!info->name)
2243         {
2244           info->name = svn_relpath_join(info->dir->name, info->base_name,
2245                                         info->pool);
2246         }
2247
2248       if (info->lock_token && !info->fetch_props)
2249         info->fetch_props = TRUE;
2250
2251       /* If possible, we'd like to fetch only a delta against a
2252        * version of the file we already have in our working copy,
2253        * rather than fetching a fulltext.
2254        *
2255        * In HTTP v2, we can simply construct the URL we need given the
2256        * repos_relpath and base revision number.
2257        */
2258       if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
2259         {
2260           const char *repos_relpath;
2261
2262           /* If this file is switched vs the editor root we should provide
2263              its real url instead of the one calculated from the session root.
2264            */
2265           repos_relpath = svn_hash_gets(ctx->switched_paths, info->name);
2266
2267           if (!repos_relpath)
2268             {
2269               if (ctx->root_is_switched)
2270                 {
2271                   /* We are updating a direct target (most likely a file)
2272                      that is switched vs its parent url */
2273                   SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool)
2274                                     == '\0');
2275
2276                   repos_relpath = svn_hash_gets(ctx->switched_paths, "");
2277                 }
2278               else
2279                 repos_relpath = svn_relpath_join(info->dir->repos_relpath,
2280                                                  info->base_name, info->pool);
2281             }
2282
2283           info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s",
2284                                           ctx->sess->rev_root_stub,
2285                                           info->base_rev,
2286                                           svn_path_uri_encode(repos_relpath,
2287                                                               info->pool));
2288         }
2289       else if (ctx->sess->wc_callbacks->get_wc_prop)
2290         {
2291           /* If we have a WC, we might be able to dive all the way into the WC
2292            * to get the previous URL so we can do a differential GET with the
2293            * base URL.
2294            */
2295           const svn_string_t *value = NULL;
2296           SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
2297             ctx->sess->wc_callback_baton, info->name,
2298             SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool));
2299
2300           info->delta_base = value ? value->data : NULL;
2301         }
2302
2303       /* go fetch info->name from DAV:checked-in */
2304       info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
2305                                             info->base_rev, "DAV:", "checked-in");
2306       if (!info->url)
2307         {
2308           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2309                                   _("The REPORT or PROPFIND response did not "
2310                                     "include the requested checked-in value"));
2311         }
2312
2313       /* If the server is in "send-all" mode, we might have opened the
2314          file when we started seeing content for it.  If we didn't get
2315          any content for it, we still need to open the file.  But in
2316          any case, we can then immediately close it.  */
2317       if (ctx->send_all_mode)
2318         {
2319           if (! info->file_baton)
2320             {
2321               SVN_ERR(open_updated_file(info, FALSE, info->pool));
2322             }
2323           SVN_ERR(close_updated_file(info, info->pool));
2324           info->dir->ref_count--;
2325         }
2326       /* Otherwise, if the server is *not* in "send-all" mode, we
2327          should be at a point where we can queue up any auxiliary
2328          content-fetching requests.  */
2329       else
2330         {
2331           SVN_ERR(fetch_file(ctx, info));
2332         }
2333
2334       svn_ra_serf__xml_pop_state(parser);
2335     }
2336   else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
2337     {
2338       report_info_t *info = parser->state->private;
2339
2340       /* go fetch info->name from DAV:checked-in */
2341       info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
2342                                             info->base_rev, "DAV:", "checked-in");
2343       if (!info->url)
2344         {
2345           return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2346                                   _("The REPORT or PROPFIND response did not "
2347                                     "include the requested checked-in value"));
2348         }
2349
2350       /* If the server is in "send-all" mode, we might have opened the
2351          file when we started seeing content for it.  If we didn't get
2352          any content for it, we still need to open the file.  But in
2353          any case, we can then immediately close it.  */
2354       if (ctx->send_all_mode)
2355         {
2356           if (! info->file_baton)
2357             {
2358               SVN_ERR(open_updated_file(info, FALSE, info->pool));
2359             }
2360           SVN_ERR(close_updated_file(info, info->pool));
2361           info->dir->ref_count--;
2362         }
2363       /* Otherwise, if the server is *not* in "send-all" mode, we
2364          should be at a point where we can queue up any auxiliary
2365          content-fetching requests.  */
2366       else
2367         {
2368           SVN_ERR(fetch_file(ctx, info));
2369         }
2370
2371       svn_ra_serf__xml_pop_state(parser);
2372     }
2373   else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0)
2374     {
2375       report_info_t *info = parser->state->private;
2376
2377       /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2378          <fetch-file>s and such) when *not* in "send-all" mode.  As a
2379          client, we're smart enough to know that's wrong, so when not
2380          in "receiving-all" mode, we'll ignore these tags. */
2381       if (ctx->send_all_mode)
2382         {
2383           SVN_ERR(svn_stream_close(info->base64_decoder));
2384         }
2385
2386       svn_ra_serf__xml_pop_state(parser);
2387     }
2388   else if (state == PROP)
2389     {
2390       /* We need to move the prop_ns, prop_name, and prop_value into the
2391        * same lifetime as the dir->pool.
2392        */
2393       svn_ra_serf__ns_t *ns, *ns_name_match;
2394       svn_boolean_t found = FALSE;
2395       report_info_t *info;
2396       report_dir_t *dir;
2397       apr_hash_t *props;
2398       const svn_string_t *set_val_str;
2399       apr_pool_t *pool;
2400
2401       info = parser->state->private;
2402       dir = info->dir;
2403
2404       /* We're going to be slightly tricky.  We don't care what the ->url
2405        * field is here at this point.  So, we're going to stick a single
2406        * copy of the property name inside of the ->url field.
2407        */
2408       ns_name_match = NULL;
2409       for (ns = dir->ns_list; ns; ns = ns->next)
2410         {
2411           if (strcmp(ns->namespace, info->prop_ns) == 0)
2412             {
2413               ns_name_match = ns;
2414               if (strcmp(ns->url, info->prop_name) == 0)
2415                 {
2416                   found = TRUE;
2417                   break;
2418                 }
2419             }
2420         }
2421
2422       if (!found)
2423         {
2424           ns = apr_palloc(dir->pool, sizeof(*ns));
2425           if (!ns_name_match)
2426             {
2427               ns->namespace = apr_pstrdup(dir->pool, info->prop_ns);
2428             }
2429           else
2430             {
2431               ns->namespace = ns_name_match->namespace;
2432             }
2433           ns->url = apr_pstrdup(dir->pool, info->prop_name);
2434
2435           ns->next = dir->ns_list;
2436           dir->ns_list = ns;
2437         }
2438
2439       if (strcmp(name.name, "remove-prop") != 0)
2440         {
2441           props = info->props;
2442           pool = info->pool;
2443         }
2444       else
2445         {
2446           props = dir->removed_props;
2447           pool = dir->pool;
2448           svn_stringbuf_setempty(info->prop_value);
2449         }
2450
2451       if (info->prop_encoding)
2452         {
2453           if (strcmp(info->prop_encoding, "base64") == 0)
2454             {
2455               svn_string_t tmp;
2456
2457               /* Don't use morph_info_string cuz we need prop_value to
2458                  remain usable.  */
2459               tmp.data = info->prop_value->data;
2460               tmp.len = info->prop_value->len;
2461
2462               set_val_str = svn_base64_decode_string(&tmp, pool);
2463             }
2464           else
2465             {
2466               return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
2467                                        NULL,
2468                                        _("Got unrecognized encoding '%s'"),
2469                                        info->prop_encoding);
2470             }
2471         }
2472       else
2473         {
2474           set_val_str = svn_string_create_from_buf(info->prop_value, pool);
2475         }
2476
2477       svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
2478                                 ns->namespace, ns->url, set_val_str, pool);
2479
2480       /* Advance handling:  if we spotted the md5-checksum property on
2481          the wire, remember it's value. */
2482       if (strcmp(ns->url, "md5-checksum") == 0
2483           && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0)
2484         info->final_checksum = apr_pstrdup(info->pool, set_val_str->data);
2485
2486       svn_ra_serf__xml_pop_state(parser);
2487     }
2488   else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
2489     {
2490       svn_ra_serf__xml_pop_state(parser);
2491     }
2492
2493   return SVN_NO_ERROR;
2494 }
2495
2496 static svn_error_t *
2497 cdata_report(svn_ra_serf__xml_parser_t *parser,
2498              const char *data,
2499              apr_size_t len,
2500              apr_pool_t *scratch_pool)
2501 {
2502   report_context_t *ctx = parser->user_data;
2503
2504   UNUSED_CTX(ctx);
2505
2506   if (parser->state->current_state == PROP)
2507     {
2508       report_info_t *info = parser->state->private;
2509
2510       svn_stringbuf_appendbytes(info->prop_value, data, len);
2511     }
2512   else if (parser->state->current_state == TXDELTA)
2513     {
2514       /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2515          <fetch-file>s and such) when *not* in "send-all" mode.  As a
2516          client, we're smart enough to know that's wrong, so when not
2517          in "receiving-all" mode, we'll ignore these tags. */
2518       if (ctx->send_all_mode)
2519         {
2520           apr_size_t nlen = len;
2521           report_info_t *info = parser->state->private;
2522
2523           SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen));
2524           if (nlen != len)
2525             {
2526               /* Short write without associated error?  "Can't happen." */
2527               return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
2528                                        _("Error writing to '%s': unexpected EOF"),
2529                                        info->name);
2530             }
2531         }
2532     }
2533
2534   return SVN_NO_ERROR;
2535 }
2536
2537 \f
2538 /** Editor callbacks given to callers to create request body */
2539
2540 /* Helper to create simple xml tag without attributes. */
2541 static void
2542 make_simple_xml_tag(svn_stringbuf_t **buf_p,
2543                     const char *tagname,
2544                     const char *cdata,
2545                     apr_pool_t *pool)
2546 {
2547   svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL);
2548   svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
2549   svn_xml_make_close_tag(buf_p, pool, tagname);
2550 }
2551
2552 static svn_error_t *
2553 set_path(void *report_baton,
2554          const char *path,
2555          svn_revnum_t revision,
2556          svn_depth_t depth,
2557          svn_boolean_t start_empty,
2558          const char *lock_token,
2559          apr_pool_t *pool)
2560 {
2561   report_context_t *report = report_baton;
2562   svn_stringbuf_t *buf = NULL;
2563
2564   svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2565                         "rev", apr_ltoa(pool, revision),
2566                         "lock-token", lock_token,
2567                         "depth", svn_depth_to_word(depth),
2568                         "start-empty", start_empty ? "true" : NULL,
2569                         NULL);
2570   svn_xml_escape_cdata_cstring(&buf, path, pool);
2571   svn_xml_make_close_tag(&buf, pool, "S:entry");
2572
2573   SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2574                                  NULL, pool));
2575
2576   return SVN_NO_ERROR;
2577 }
2578
2579 static svn_error_t *
2580 delete_path(void *report_baton,
2581             const char *path,
2582             apr_pool_t *pool)
2583 {
2584   report_context_t *report = report_baton;
2585   svn_stringbuf_t *buf = NULL;
2586
2587   make_simple_xml_tag(&buf, "S:missing", path, pool);
2588
2589   SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2590                                  NULL, pool));
2591
2592   return SVN_NO_ERROR;
2593 }
2594
2595 static svn_error_t *
2596 link_path(void *report_baton,
2597           const char *path,
2598           const char *url,
2599           svn_revnum_t revision,
2600           svn_depth_t depth,
2601           svn_boolean_t start_empty,
2602           const char *lock_token,
2603           apr_pool_t *pool)
2604 {
2605   report_context_t *report = report_baton;
2606   const char *link, *report_target;
2607   apr_uri_t uri;
2608   apr_status_t status;
2609   svn_stringbuf_t *buf = NULL;
2610
2611   /* We need to pass in the baseline relative path.
2612    *
2613    * TODO Confirm that it's on the same server?
2614    */
2615   status = apr_uri_parse(pool, url, &uri);
2616   if (status)
2617     {
2618       return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2619                                _("Unable to parse URL '%s'"), url);
2620     }
2621
2622   SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess,
2623                                        NULL, pool));
2624   SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess,
2625                                          NULL, pool));
2626
2627   link = apr_pstrcat(pool, "/", link, (char *)NULL);
2628
2629   svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2630                         "rev", apr_ltoa(pool, revision),
2631                         "lock-token", lock_token,
2632                         "depth", svn_depth_to_word(depth),
2633                         "linkpath", link,
2634                         "start-empty", start_empty ? "true" : NULL,
2635                         NULL);
2636   svn_xml_escape_cdata_cstring(&buf, path, pool);
2637   svn_xml_make_close_tag(&buf, pool, "S:entry");
2638
2639   SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2640                                  NULL, pool));
2641
2642   /* Store the switch roots to allow generating repos_relpaths from just
2643      the working copy paths. (Needed for HTTPv2) */
2644   path = apr_pstrdup(report->pool, path);
2645   svn_hash_sets(report->switched_paths,
2646                 path, apr_pstrdup(report->pool, link + 1));
2647
2648   if (!*path)
2649     report->root_is_switched = TRUE;
2650
2651   return APR_SUCCESS;
2652 }
2653
2654 /** Minimum nr. of outstanding requests needed before a new connection is
2655  *  opened. */
2656 #define REQS_PER_CONN 8
2657
2658 /** This function creates a new connection for this serf session, but only
2659  * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
2660  * only one main connection open.
2661  */
2662 static svn_error_t *
2663 open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
2664 {
2665   /* For each REQS_PER_CONN outstanding requests open a new connection, with
2666    * a minimum of 1 extra connection. */
2667   if (sess->num_conns == 1 ||
2668       ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
2669     {
2670       int cur = sess->num_conns;
2671       apr_status_t status;
2672
2673       sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
2674       sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
2675                                                                  NULL, NULL);
2676       sess->conns[cur]->last_status_code = -1;
2677       sess->conns[cur]->session = sess;
2678       status = serf_connection_create2(&sess->conns[cur]->conn,
2679                                        sess->context,
2680                                        sess->session_url,
2681                                        svn_ra_serf__conn_setup,
2682                                        sess->conns[cur],
2683                                        svn_ra_serf__conn_closed,
2684                                        sess->conns[cur],
2685                                        sess->pool);
2686       if (status)
2687         return svn_ra_serf__wrap_err(status, NULL);
2688
2689       sess->num_conns++;
2690     }
2691
2692   return SVN_NO_ERROR;
2693 }
2694
2695 /* Serf callback to create update request body bucket. */
2696 static svn_error_t *
2697 create_update_report_body(serf_bucket_t **body_bkt,
2698                           void *baton,
2699                           serf_bucket_alloc_t *alloc,
2700                           apr_pool_t *pool)
2701 {
2702   report_context_t *report = baton;
2703   apr_off_t offset;
2704
2705   offset = 0;
2706   apr_file_seek(report->body_file, APR_SET, &offset);
2707
2708   *body_bkt = serf_bucket_file_create(report->body_file, alloc);
2709
2710   return SVN_NO_ERROR;
2711 }
2712
2713 /* Serf callback to setup update request headers. */
2714 static svn_error_t *
2715 setup_update_report_headers(serf_bucket_t *headers,
2716                             void *baton,
2717                             apr_pool_t *pool)
2718 {
2719   report_context_t *report = baton;
2720
2721   if (report->sess->using_compression)
2722     {
2723       serf_bucket_headers_setn(headers, "Accept-Encoding",
2724                                "gzip,svndiff1;q=0.9,svndiff;q=0.8");
2725     }
2726   else
2727     {
2728       serf_bucket_headers_setn(headers, "Accept-Encoding",
2729                                "svndiff1;q=0.9,svndiff;q=0.8");
2730     }
2731
2732   return SVN_NO_ERROR;
2733 }
2734
2735 static svn_error_t *
2736 finish_report(void *report_baton,
2737               apr_pool_t *pool)
2738 {
2739   report_context_t *report = report_baton;
2740   svn_ra_serf__session_t *sess = report->sess;
2741   svn_ra_serf__handler_t *handler;
2742   svn_ra_serf__xml_parser_t *parser_ctx;
2743   const char *report_target;
2744   svn_stringbuf_t *buf = NULL;
2745   apr_pool_t *iterpool = svn_pool_create(pool);
2746   svn_error_t *err;
2747   apr_interval_time_t waittime_left = sess->timeout;
2748
2749   svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
2750   SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2751                                  NULL, iterpool));
2752
2753   /* We need to flush the file, make it unbuffered (so that it can be
2754    * zero-copied via mmap), and reset the position before attempting to
2755    * deliver the file.
2756    *
2757    * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
2758    * and zero-copy the PUT body.  However, on older APR versions, we can't
2759    * check the buffer status; but serf will fall through and create a file
2760    * bucket for us on the buffered svndiff handle.
2761    */
2762   apr_file_flush(report->body_file);
2763 #if APR_VERSION_AT_LEAST(1, 3, 0)
2764   apr_file_buffer_set(report->body_file, NULL, 0);
2765 #endif
2766
2767   SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool));
2768
2769   /* create and deliver request */
2770   report->path = report_target;
2771
2772   handler = apr_pcalloc(pool, sizeof(*handler));
2773
2774   handler->handler_pool = pool;
2775   handler->method = "REPORT";
2776   handler->path = report->path;
2777   handler->body_delegate = create_update_report_body;
2778   handler->body_delegate_baton = report;
2779   handler->body_type = "text/xml";
2780   handler->custom_accept_encoding = TRUE;
2781   handler->header_delegate = setup_update_report_headers;
2782   handler->header_delegate_baton = report;
2783   handler->conn = sess->conns[0];
2784   handler->session = sess;
2785
2786   parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
2787
2788   parser_ctx->pool = pool;
2789   parser_ctx->response_type = "update-report";
2790   parser_ctx->user_data = report;
2791   parser_ctx->start = start_report;
2792   parser_ctx->end = end_report;
2793   parser_ctx->cdata = cdata_report;
2794   parser_ctx->done = &report->done;
2795
2796   handler->response_handler = svn_ra_serf__handle_xml_parser;
2797   handler->response_baton = parser_ctx;
2798
2799   report->parser_ctx = parser_ctx;
2800
2801   svn_ra_serf__request_create(handler);
2802
2803   /* Open the first extra connection. */
2804   SVN_ERR(open_connection_if_needed(sess, 0));
2805
2806   sess->cur_conn = 1;
2807
2808   /* Note that we may have no active GET or PROPFIND requests, yet the
2809      processing has not been completed. This could be from a delay on the
2810      network or because we've spooled the entire response into our "pending"
2811      content of the XML parser. The DONE flag will get set when all the
2812      XML content has been received *and* parsed.  */
2813   while (!report->done
2814          || report->num_active_fetches
2815          || report->num_active_propfinds)
2816     {
2817       apr_pool_t *iterpool_inner;
2818       svn_ra_serf__list_t *done_list;
2819       int i;
2820       apr_status_t status;
2821
2822       /* Note: this throws out the old ITERPOOL_INNER.  */
2823       svn_pool_clear(iterpool);
2824
2825       if (sess->cancel_func)
2826         SVN_ERR(sess->cancel_func(sess->cancel_baton));
2827
2828       /* We need to be careful between the outer and inner ITERPOOLs,
2829          and what items are allocated within.  */
2830       iterpool_inner = svn_pool_create(iterpool);
2831
2832       status = serf_context_run(sess->context,
2833                                 SVN_RA_SERF__CONTEXT_RUN_DURATION,
2834                                 iterpool_inner);
2835
2836       err = sess->pending_error;
2837       sess->pending_error = SVN_NO_ERROR;
2838
2839       if (!err && handler->done && handler->server_error)
2840         {
2841           err = handler->server_error->error;
2842         }
2843
2844       /* If the context duration timeout is up, we'll subtract that
2845          duration from the total time alloted for such things.  If
2846          there's no time left, we fail with a message indicating that
2847          the connection timed out.  */
2848       if (APR_STATUS_IS_TIMEUP(status))
2849         {
2850           svn_error_clear(err);
2851           err = SVN_NO_ERROR;
2852           status = 0;
2853
2854           if (sess->timeout)
2855             {
2856               if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
2857                 {
2858                   waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
2859                 }
2860               else
2861                 {
2862                   return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
2863                                           _("Connection timed out"));
2864                 }
2865             }
2866         }
2867       else
2868         {
2869           waittime_left = sess->timeout;
2870         }
2871
2872       if (status && handler->sline.code != 200)
2873         {
2874           return svn_error_trace(
2875                     svn_error_compose_create(
2876                         svn_ra_serf__error_on_status(handler->sline,
2877                                                      handler->path,
2878                                                      handler->location),
2879                         err));
2880         }
2881       SVN_ERR(err);
2882       if (status)
2883         {
2884           return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
2885         }
2886
2887       /* Open extra connections if we have enough requests to send. */
2888       if (sess->num_conns < sess->max_connections)
2889         SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches +
2890                                           report->num_active_propfinds));
2891
2892       /* Prune completed file PROPFINDs. */
2893       done_list = report->done_propfinds;
2894       while (done_list)
2895         {
2896           svn_ra_serf__list_t *next_done = done_list->next;
2897
2898           svn_pool_clear(iterpool_inner);
2899
2900           report->num_active_propfinds--;
2901
2902           /* If we have some files that we won't be fetching the content
2903            * for, ensure that we update the file with any altered props.
2904            */
2905           if (report->file_propchanges_only)
2906             {
2907               svn_ra_serf__list_t *cur, *prev;
2908
2909               prev = NULL;
2910               cur = report->file_propchanges_only;
2911
2912               while (cur)
2913                 {
2914                   report_info_t *item = cur->data;
2915
2916                   if (item->propfind_handler == done_list->data)
2917                     {
2918                       break;
2919                     }
2920
2921                   prev = cur;
2922                   cur = cur->next;
2923                 }
2924
2925               /* If we found a match, set the new props and remove this
2926                * propchange from our list.
2927                */
2928               if (cur)
2929                 {
2930                   report_info_t *info = cur->data;
2931
2932                   if (!prev)
2933                     {
2934                       report->file_propchanges_only = cur->next;
2935                     }
2936                   else
2937                     {
2938                       prev->next = cur->next;
2939                     }
2940
2941                   /* If we've got cached file content for this file,
2942                      take care of the locally collected properties and
2943                      file content at once.  Otherwise, just deal with
2944                      the collected properties.
2945
2946                      NOTE:  These functions below could delete
2947                      info->dir->pool (via maybe_close_dir_chain()),
2948                      from which is allocated the list item in
2949                      report->file_propchanges_only.
2950                   */
2951                   if (info->cached_contents)
2952                     {
2953                       SVN_ERR(handle_local_content(info, iterpool_inner));
2954                     }
2955                   else
2956                     {
2957                       SVN_ERR(handle_propchange_only(info, iterpool_inner));
2958                     }
2959                 }
2960             }
2961
2962           done_list = next_done;
2963         }
2964       report->done_propfinds = NULL;
2965
2966       /* Prune completed fetches from our list. */
2967       done_list = report->done_fetches;
2968       while (done_list)
2969         {
2970           report_fetch_t *done_fetch = done_list->data;
2971           svn_ra_serf__list_t *next_done = done_list->next;
2972           report_dir_t *cur_dir;
2973
2974           /* Decrease the refcount in the parent directory of the file
2975              whose fetch has completed. */
2976           cur_dir = done_fetch->info->dir;
2977           cur_dir->ref_count--;
2978
2979           /* Decrement our active fetch count. */
2980           report->num_active_fetches--;
2981
2982           /* See if the parent directory of this fetched item (and
2983              perhaps even parents of that) can be closed now.
2984
2985              NOTE:  This could delete cur_dir->pool, from which is
2986              allocated the list item in report->done_fetches.
2987           */
2988           SVN_ERR(maybe_close_dir_chain(cur_dir));
2989
2990           done_list = next_done;
2991         }
2992       report->done_fetches = NULL;
2993
2994       /* Prune completed directory PROPFINDs. */
2995       done_list = report->done_dir_propfinds;
2996       while (done_list)
2997         {
2998           svn_ra_serf__list_t *next_done = done_list->next;
2999
3000           report->num_active_propfinds--;
3001
3002           if (report->active_dir_propfinds)
3003             {
3004               svn_ra_serf__list_t *cur, *prev;
3005
3006               prev = NULL;
3007               cur = report->active_dir_propfinds;
3008
3009               while (cur)
3010                 {
3011                   report_dir_t *item = cur->data;
3012
3013                   if (item->propfind_handler == done_list->data)
3014                     {
3015                       break;
3016                     }
3017
3018                   prev = cur;
3019                   cur = cur->next;
3020                 }
3021               SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */
3022
3023               /* If we found a match, set the new props and remove this
3024                * propchange from our list.
3025                */
3026               if (cur)
3027                 {
3028                   report_dir_t *cur_dir = cur->data;
3029
3030                   if (!prev)
3031                     {
3032                       report->active_dir_propfinds = cur->next;
3033                     }
3034                   else
3035                     {
3036                       prev->next = cur->next;
3037                     }
3038
3039                   /* See if this directory (and perhaps even parents of that)
3040                      can be closed now.
3041
3042                      NOTE:  This could delete cur_dir->pool, from which is
3043                      allocated the list item in report->active_dir_propfinds.
3044                   */
3045                   SVN_ERR(maybe_close_dir_chain(cur_dir));
3046                 }
3047             }
3048
3049           done_list = next_done;
3050         }
3051       report->done_dir_propfinds = NULL;
3052
3053       /* If the parser is paused, and the number of active requests has
3054          dropped far enough, then resume parsing.  */
3055       if (parser_ctx->paused
3056           && (report->num_active_fetches + report->num_active_propfinds
3057               < REQUEST_COUNT_TO_RESUME))
3058         parser_ctx->paused = FALSE;
3059
3060       /* If we have not paused the parser and it looks like data MAY be
3061          present (we can't know for sure because of the private structure),
3062          then go process the pending content.  */
3063       if (!parser_ctx->paused && parser_ctx->pending != NULL)
3064         SVN_ERR(svn_ra_serf__process_pending(parser_ctx,
3065                                              &report->report_received,
3066                                              iterpool_inner));
3067
3068       /* Debugging purposes only! */
3069       for (i = 0; i < sess->num_conns; i++)
3070         {
3071           serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
3072         }
3073     }
3074
3075   /* If we got a complete report, close the edit.  Otherwise, abort it. */
3076   if (report->report_completed)
3077     {
3078       /* Ensure that we opened and closed our root dir and that we closed
3079        * all of our children. */
3080       if (!report->closed_root && report->root_dir != NULL)
3081         {
3082           SVN_ERR(close_all_dirs(report->root_dir));
3083         }
3084
3085       err = report->update_editor->close_edit(report->update_baton, iterpool);
3086     }
3087   else
3088     {
3089       /* Tell the editor that something failed */
3090       err = report->update_editor->abort_edit(report->update_baton, iterpool);
3091
3092       err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
3093                              _("Missing update-report close tag"));
3094     }
3095
3096   svn_pool_destroy(iterpool);
3097   return svn_error_trace(err);
3098 }
3099
3100
3101 static svn_error_t *
3102 abort_report(void *report_baton,
3103              apr_pool_t *pool)
3104 {
3105 #if 0
3106   report_context_t *report = report_baton;
3107 #endif
3108
3109   /* Should we perform some cleanup here? */
3110
3111   return SVN_NO_ERROR;
3112 }
3113
3114 static const svn_ra_reporter3_t ra_serf_reporter = {
3115   set_path,
3116   delete_path,
3117   link_path,
3118   finish_report,
3119   abort_report
3120 };
3121
3122 \f
3123 /** RA function implementations and body */
3124
3125 static svn_error_t *
3126 make_update_reporter(svn_ra_session_t *ra_session,
3127                      const svn_ra_reporter3_t **reporter,
3128                      void **report_baton,
3129                      svn_revnum_t revision,
3130                      const char *src_path,
3131                      const char *dest_path,
3132                      const char *update_target,
3133                      svn_depth_t depth,
3134                      svn_boolean_t ignore_ancestry,
3135                      svn_boolean_t text_deltas,
3136                      svn_boolean_t send_copyfrom_args,
3137                      const svn_delta_editor_t *update_editor,
3138                      void *update_baton,
3139                      apr_pool_t *result_pool,
3140                      apr_pool_t *scratch_pool)
3141 {
3142   report_context_t *report;
3143   const svn_delta_editor_t *filter_editor;
3144   void *filter_baton;
3145   svn_boolean_t has_target = *update_target != '\0';
3146   svn_boolean_t server_supports_depth;
3147   svn_ra_serf__session_t *sess = ra_session->priv;
3148   svn_stringbuf_t *buf = NULL;
3149   svn_boolean_t use_bulk_updates;
3150
3151   SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
3152                                       SVN_RA_CAPABILITY_DEPTH, scratch_pool));
3153   /* We can skip the depth filtering when the user requested
3154      depth_files or depth_infinity because the server will
3155      transmit the right stuff anyway. */
3156   if ((depth != svn_depth_files)
3157       && (depth != svn_depth_infinity)
3158       && ! server_supports_depth)
3159     {
3160       SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
3161                                             &filter_baton,
3162                                             update_editor,
3163                                             update_baton,
3164                                             depth, has_target,
3165                                             sess->pool));
3166       update_editor = filter_editor;
3167       update_baton = filter_baton;
3168     }
3169
3170   report = apr_pcalloc(result_pool, sizeof(*report));
3171   report->pool = result_pool;
3172   report->sess = sess;
3173   report->conn = report->sess->conns[0];
3174   report->target_rev = revision;
3175   report->ignore_ancestry = ignore_ancestry;
3176   report->send_copyfrom_args = send_copyfrom_args;
3177   report->text_deltas = text_deltas;
3178   report->switched_paths = apr_hash_make(report->pool);
3179
3180   report->source = src_path;
3181   report->destination = dest_path;
3182   report->update_target = update_target;
3183
3184   report->update_editor = update_editor;
3185   report->update_baton = update_baton;
3186   report->done = FALSE;
3187
3188   *reporter = &ra_serf_reporter;
3189   *report_baton = report;
3190
3191   SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL,
3192                                    svn_io_file_del_on_pool_cleanup,
3193                                    report->pool, scratch_pool));
3194
3195   if (sess->bulk_updates == svn_tristate_true)
3196     {
3197       /* User would like to use bulk updates. */
3198       use_bulk_updates = TRUE;
3199     }
3200   else if (sess->bulk_updates == svn_tristate_false)
3201     {
3202       /* User doesn't want bulk updates. */
3203       use_bulk_updates = FALSE;
3204     }
3205   else
3206     {
3207       /* User doesn't have any preferences on bulk updates. Decide on server
3208          preferences and capabilities. */
3209       if (sess->server_allows_bulk)
3210         {
3211           if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
3212             {
3213               /* Server doesn't want bulk updates */
3214               use_bulk_updates = FALSE;
3215             }
3216           else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
3217             {
3218               /* Server prefers bulk updates, and we respect that */
3219               use_bulk_updates = TRUE;
3220             }
3221           else
3222             {
3223               /* Server allows bulk updates, but doesn't dictate its use. Do
3224                  whatever is the default. */
3225               use_bulk_updates = FALSE;
3226             }
3227         }
3228       else
3229         {
3230           /* Pre-1.8 server didn't send the bulk_updates header. Check if server
3231              supports inlining properties in update editor report. */
3232           if (sess->supports_inline_props)
3233             {
3234               /* Inline props supported: do not use bulk updates. */
3235               use_bulk_updates = FALSE;
3236             }
3237           else
3238             {
3239               /* Inline props are not supported: use bulk updates to avoid
3240                * PROPFINDs for every added node. */
3241               use_bulk_updates = TRUE;
3242             }
3243         }
3244     }
3245
3246   if (use_bulk_updates)
3247     {
3248       svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
3249                             "S:update-report",
3250                             "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
3251                             NULL);
3252     }
3253   else
3254     {
3255       svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
3256                             "S:update-report",
3257                             "xmlns:S", SVN_XML_NAMESPACE,
3258                             NULL);
3259       /* Subversion 1.8+ servers can be told to send properties for newly
3260          added items inline even when doing a skelta response. */
3261       make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
3262     }
3263
3264   make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
3265
3266   if (SVN_IS_VALID_REVNUM(report->target_rev))
3267     {
3268       make_simple_xml_tag(&buf, "S:target-revision",
3269                           apr_ltoa(scratch_pool, report->target_rev),
3270                           scratch_pool);
3271     }
3272
3273   if (report->destination && *report->destination)
3274     {
3275       make_simple_xml_tag(&buf, "S:dst-path", report->destination,
3276                           scratch_pool);
3277     }
3278
3279   if (report->update_target && *report->update_target)
3280     {
3281       make_simple_xml_tag(&buf, "S:update-target", report->update_target,
3282                           scratch_pool);
3283     }
3284
3285   if (report->ignore_ancestry)
3286     {
3287       make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
3288     }
3289
3290   if (report->send_copyfrom_args)
3291     {
3292       make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
3293     }
3294
3295   /* Old servers know "recursive" but not "depth"; help them DTRT. */
3296   if (depth == svn_depth_files || depth == svn_depth_empty)
3297     {
3298       make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
3299     }
3300
3301   /* When in 'send-all' mode, mod_dav_svn will assume that it should
3302      calculate and transmit real text-deltas (instead of empty windows
3303      that merely indicate "text is changed") unless it finds this
3304      element.
3305
3306      NOTE: Do NOT count on servers actually obeying this, as some exist
3307      which obey send-all, but do not check for this directive at all!
3308
3309      NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
3310      override our request and send text-deltas. */
3311   if (! text_deltas)
3312     {
3313       make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
3314     }
3315
3316   make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
3317
3318   SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
3319                                  NULL, scratch_pool));
3320
3321   return SVN_NO_ERROR;
3322 }
3323
3324 svn_error_t *
3325 svn_ra_serf__do_update(svn_ra_session_t *ra_session,
3326                        const svn_ra_reporter3_t **reporter,
3327                        void **report_baton,
3328                        svn_revnum_t revision_to_update_to,
3329                        const char *update_target,
3330                        svn_depth_t depth,
3331                        svn_boolean_t send_copyfrom_args,
3332                        svn_boolean_t ignore_ancestry,
3333                        const svn_delta_editor_t *update_editor,
3334                        void *update_baton,
3335                        apr_pool_t *result_pool,
3336                        apr_pool_t *scratch_pool)
3337 {
3338   svn_ra_serf__session_t *session = ra_session->priv;
3339
3340   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3341                                revision_to_update_to,
3342                                session->session_url.path, NULL, update_target,
3343                                depth, ignore_ancestry, TRUE /* text_deltas */,
3344                                send_copyfrom_args,
3345                                update_editor, update_baton,
3346                                result_pool, scratch_pool));
3347   return SVN_NO_ERROR;
3348 }
3349
3350 svn_error_t *
3351 svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
3352                      const svn_ra_reporter3_t **reporter,
3353                      void **report_baton,
3354                      svn_revnum_t revision,
3355                      const char *diff_target,
3356                      svn_depth_t depth,
3357                      svn_boolean_t ignore_ancestry,
3358                      svn_boolean_t text_deltas,
3359                      const char *versus_url,
3360                      const svn_delta_editor_t *diff_editor,
3361                      void *diff_baton,
3362                      apr_pool_t *pool)
3363 {
3364   svn_ra_serf__session_t *session = ra_session->priv;
3365   apr_pool_t *scratch_pool = svn_pool_create(pool);
3366
3367   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3368                                revision,
3369                                session->session_url.path, versus_url, diff_target,
3370                                depth, ignore_ancestry, text_deltas, FALSE,
3371                                diff_editor, diff_baton,
3372                                pool, scratch_pool));
3373   svn_pool_destroy(scratch_pool);
3374   return SVN_NO_ERROR;
3375 }
3376
3377 svn_error_t *
3378 svn_ra_serf__do_status(svn_ra_session_t *ra_session,
3379                        const svn_ra_reporter3_t **reporter,
3380                        void **report_baton,
3381                        const char *status_target,
3382                        svn_revnum_t revision,
3383                        svn_depth_t depth,
3384                        const svn_delta_editor_t *status_editor,
3385                        void *status_baton,
3386                        apr_pool_t *pool)
3387 {
3388   svn_ra_serf__session_t *session = ra_session->priv;
3389   apr_pool_t *scratch_pool = svn_pool_create(pool);
3390
3391   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3392                                revision,
3393                                session->session_url.path, NULL, status_target,
3394                                depth, FALSE, FALSE, FALSE,
3395                                status_editor, status_baton,
3396                                pool, scratch_pool));
3397   svn_pool_destroy(scratch_pool);
3398   return SVN_NO_ERROR;
3399 }
3400
3401 svn_error_t *
3402 svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
3403                        const svn_ra_reporter3_t **reporter,
3404                        void **report_baton,
3405                        svn_revnum_t revision_to_switch_to,
3406                        const char *switch_target,
3407                        svn_depth_t depth,
3408                        const char *switch_url,
3409                        svn_boolean_t send_copyfrom_args,
3410                        svn_boolean_t ignore_ancestry,
3411                        const svn_delta_editor_t *switch_editor,
3412                        void *switch_baton,
3413                        apr_pool_t *result_pool,
3414                        apr_pool_t *scratch_pool)
3415 {
3416   svn_ra_serf__session_t *session = ra_session->priv;
3417
3418   return make_update_reporter(ra_session, reporter, report_baton,
3419                               revision_to_switch_to,
3420                               session->session_url.path,
3421                               switch_url, switch_target,
3422                               depth,
3423                               ignore_ancestry,
3424                               TRUE /* text_deltas */,
3425                               send_copyfrom_args,
3426                               switch_editor, switch_baton,
3427                               result_pool, scratch_pool);
3428 }
3429
3430 /* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
3431  * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
3432  * present in PROPS.
3433  *
3434  * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
3435  *
3436  * Performs all temporary allocations in POOL.
3437  */
3438 static svn_error_t *
3439 try_get_wc_contents(svn_boolean_t *found_p,
3440                     svn_ra_serf__session_t *session,
3441                     apr_hash_t *props,
3442                     svn_stream_t *dst_stream,
3443                     apr_pool_t *pool)
3444 {
3445   apr_hash_t *svn_props;
3446   const char *sha1_checksum_prop;
3447   svn_checksum_t *checksum;
3448   svn_stream_t *wc_stream;
3449   svn_error_t *err;
3450
3451   /* No contents found by default. */
3452   *found_p = FALSE;
3453
3454   if (!session->wc_callbacks->get_wc_contents)
3455     {
3456       /* No callback, nothing to do. */
3457       return SVN_NO_ERROR;
3458     }
3459
3460
3461   svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
3462   if (!svn_props)
3463     {
3464       /* No properties -- therefore no checksum property -- in response. */
3465       return SVN_NO_ERROR;
3466     }
3467
3468   sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum");
3469   if (sha1_checksum_prop == NULL)
3470     {
3471       /* No checksum property in response. */
3472       return SVN_NO_ERROR;
3473     }
3474
3475   SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
3476                                  sha1_checksum_prop, pool));
3477
3478   err = session->wc_callbacks->get_wc_contents(
3479           session->wc_callback_baton, &wc_stream, checksum, pool);
3480
3481   if (err)
3482     {
3483       svn_error_clear(err);
3484
3485       /* Ignore errors for now. */
3486       return SVN_NO_ERROR;
3487     }
3488
3489   if (wc_stream)
3490     {
3491         SVN_ERR(svn_stream_copy3(wc_stream,
3492                                  svn_stream_disown(dst_stream, pool),
3493                                  NULL, NULL, pool));
3494       *found_p = TRUE;
3495     }
3496
3497   return SVN_NO_ERROR;
3498 }
3499
3500 svn_error_t *
3501 svn_ra_serf__get_file(svn_ra_session_t *ra_session,
3502                       const char *path,
3503                       svn_revnum_t revision,
3504                       svn_stream_t *stream,
3505                       svn_revnum_t *fetched_rev,
3506                       apr_hash_t **props,
3507                       apr_pool_t *pool)
3508 {
3509   svn_ra_serf__session_t *session = ra_session->priv;
3510   svn_ra_serf__connection_t *conn;
3511   const char *fetch_url;
3512   apr_hash_t *fetch_props;
3513   svn_node_kind_t res_kind;
3514   const svn_ra_serf__dav_props_t *which_props;
3515
3516   /* What connection should we go on? */
3517   conn = session->conns[session->cur_conn];
3518
3519   /* Fetch properties. */
3520
3521   fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
3522
3523   /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
3524    *
3525    * Otherwise, we need to get the baseline version for this particular
3526    * revision and then fetch that file.
3527    */
3528   if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
3529     {
3530       SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
3531                                           session, conn,
3532                                           fetch_url, revision,
3533                                           pool, pool));
3534       revision = SVN_INVALID_REVNUM;
3535     }
3536   /* REVISION is always SVN_INVALID_REVNUM  */
3537   SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
3538
3539   if (props)
3540     {
3541       which_props = all_props;
3542     }
3543   else if (stream && session->wc_callbacks->get_wc_contents)
3544     {
3545       which_props = type_and_checksum_props;
3546     }
3547   else
3548     {
3549       which_props = check_path_props;
3550     }
3551
3552   SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url,
3553                                         SVN_INVALID_REVNUM,
3554                                         which_props,
3555                                         pool, pool));
3556
3557   /* Verify that resource type is not collection. */
3558   SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props));
3559   if (res_kind != svn_node_file)
3560     {
3561       return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
3562                               _("Can't get text contents of a directory"));
3563     }
3564
3565   /* TODO Filter out all of our props into a usable format. */
3566   if (props)
3567     {
3568       /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
3569          ### put them into POOL, so we're okay.  */
3570       SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props,
3571                                          pool, pool));
3572     }
3573
3574   if (stream)
3575     {
3576       svn_boolean_t found;
3577       SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool));
3578
3579       /* No contents found in the WC, let's fetch from server. */
3580       if (!found)
3581         {
3582           report_fetch_t *stream_ctx;
3583           svn_ra_serf__handler_t *handler;
3584
3585           /* Create the fetch context. */
3586           stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
3587           stream_ctx->target_stream = stream;
3588           stream_ctx->sess = session;
3589           stream_ctx->conn = conn;
3590           stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
3591           stream_ctx->info->name = fetch_url;
3592
3593           handler = apr_pcalloc(pool, sizeof(*handler));
3594
3595           handler->handler_pool = pool;
3596           handler->method = "GET";
3597           handler->path = fetch_url;
3598           handler->conn = conn;
3599           handler->session = session;
3600
3601           handler->custom_accept_encoding = TRUE;
3602           handler->header_delegate = headers_fetch;
3603           handler->header_delegate_baton = stream_ctx;
3604
3605           handler->response_handler = handle_stream;
3606           handler->response_baton = stream_ctx;
3607
3608           handler->response_error = cancel_fetch;
3609           handler->response_error_baton = stream_ctx;
3610
3611           stream_ctx->handler = handler;
3612
3613           svn_ra_serf__request_create(handler);
3614
3615           SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
3616         }
3617     }
3618
3619   return SVN_NO_ERROR;
3620 }