]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/update.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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
53 \f
54 /*
55  * This enum represents the current state of our XML parsing for a REPORT.
56  *
57  * A little explanation of how the parsing works.  Every time we see
58  * an open-directory tag, we enter the OPEN_DIR state.  Likewise, for
59  * add-directory, open-file, etc.  When we see the closing variant of the
60  * open-directory tag, we'll 'pop' out of that state.
61  *
62  * Each state has a pool associated with it that can have temporary
63  * allocations that will live as long as the tag is opened.  Once
64  * the tag is 'closed', the pool will be reused.
65  */
66 typedef enum report_state_e {
67   INITIAL = XML_STATE_INITIAL /* = 0 */,
68   UPDATE_REPORT,
69   TARGET_REVISION,
70
71   OPEN_DIR,
72   ADD_DIR,
73
74   OPEN_FILE,
75   ADD_FILE,
76
77   DELETE_ENTRY,
78   ABSENT_DIR,
79   ABSENT_FILE,
80
81   SET_PROP,
82   REMOVE_PROP,
83
84   PROP,
85
86   FETCH_FILE,
87   FETCH_PROPS,
88   TXDELTA,
89
90   CHECKED_IN,
91   CHECKED_IN_HREF,
92
93   MD5_CHECKSUM,
94
95   VERSION_NAME,
96   CREATIONDATE,
97   CREATOR_DISPLAYNAME
98 } report_state_e;
99
100
101 #define D_ "DAV:"
102 #define S_ SVN_XML_NAMESPACE
103 #define V_ SVN_DAV_PROP_NS_DAV
104 static const svn_ra_serf__xml_transition_t update_ttable[] = {
105   { INITIAL, S_, "update-report", UPDATE_REPORT,
106     FALSE, { "?inline-props", "?send-all", NULL }, TRUE },
107
108   { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
109     FALSE, { "rev", NULL }, TRUE },
110
111   { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
112     FALSE, { "rev", NULL }, TRUE },
113
114   { OPEN_DIR, S_, "open-directory", OPEN_DIR,
115     FALSE, { "rev", "name", NULL }, TRUE },
116
117   { ADD_DIR, S_, "open-directory", OPEN_DIR,
118     FALSE, { "rev", "name", NULL }, TRUE },
119
120   { OPEN_DIR, S_, "add-directory", ADD_DIR,
121     FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/
122               NULL }, TRUE },
123
124   { ADD_DIR, S_, "add-directory", ADD_DIR,
125     FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/
126               NULL }, TRUE },
127
128   { OPEN_DIR, S_, "open-file", OPEN_FILE,
129     FALSE, { "rev", "name", NULL }, TRUE },
130
131   { ADD_DIR, S_, "open-file", OPEN_FILE,
132     FALSE, { "rev", "name", NULL }, TRUE },
133
134   { OPEN_DIR, S_, "add-file", ADD_FILE,
135     FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
136              "?sha1-checksum", NULL }, TRUE },
137
138   { ADD_DIR, S_, "add-file", ADD_FILE,
139     FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
140              "?sha1-checksum", NULL }, TRUE },
141
142   { OPEN_DIR, S_, "delete-entry", DELETE_ENTRY,
143     FALSE, { "?rev", "name", NULL }, TRUE },
144
145   { ADD_DIR, S_, "delete-entry", DELETE_ENTRY,
146     FALSE, { "?rev", "name", NULL }, TRUE },
147
148   { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
149     FALSE, { "name", NULL }, TRUE },
150
151   { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
152     FALSE, { "name", NULL }, TRUE },
153
154   { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
155     FALSE, { "name", NULL }, TRUE },
156
157   { ADD_DIR, S_, "absent-file", ABSENT_FILE,
158     FALSE, { "name", NULL }, TRUE },
159
160
161   { OPEN_DIR, D_, "checked-in", CHECKED_IN,
162     FALSE, { NULL }, FALSE },
163
164   { ADD_DIR, D_, "checked-in", CHECKED_IN,
165     FALSE, { NULL }, FALSE },
166
167   { OPEN_FILE, D_, "checked-in", CHECKED_IN,
168     FALSE, { NULL }, FALSE },
169
170   { ADD_FILE, D_, "checked-in", CHECKED_IN,
171     FALSE, { NULL }, FALSE },
172
173
174   { OPEN_DIR, S_, "set-prop", SET_PROP,
175     TRUE, { "name", "?encoding", NULL }, TRUE },
176
177   { ADD_DIR, S_, "set-prop", SET_PROP,
178     TRUE, { "name", "?encoding", NULL }, TRUE },
179
180   { OPEN_FILE, S_, "set-prop", SET_PROP,
181     TRUE, { "name", "?encoding", NULL }, TRUE },
182
183   { ADD_FILE, S_, "set-prop", SET_PROP,
184     TRUE, { "name", "?encoding", NULL }, TRUE },
185
186
187   { OPEN_DIR, S_, "remove-prop", REMOVE_PROP,
188     TRUE, { "name", NULL }, TRUE },
189
190   { ADD_DIR, S_, "remove-prop", REMOVE_PROP,
191     TRUE, { "name", NULL }, TRUE },
192
193   { OPEN_FILE, S_, "remove-prop", REMOVE_PROP,
194     TRUE, { "name", NULL }, TRUE },
195
196   { ADD_FILE, S_, "remove-prop", REMOVE_PROP,
197     TRUE, { "name", NULL }, TRUE },
198
199   { OPEN_FILE, S_, "prop", PROP,
200     FALSE, { NULL }, FALSE },
201   { OPEN_DIR, S_, "prop", PROP,
202     FALSE, { NULL }, FALSE },
203   { ADD_FILE, S_, "prop", PROP,
204     FALSE, { NULL }, FALSE },
205   { ADD_DIR, S_, "prop", PROP,
206     FALSE, { NULL }, FALSE },
207
208   { OPEN_FILE, S_, "txdelta", TXDELTA,
209     FALSE, { "?base-checksum" }, TRUE },
210
211   { ADD_FILE, S_, "txdelta", TXDELTA,
212     FALSE, { "?base-checksum" }, TRUE },
213
214   { OPEN_FILE, S_, "fetch-file", FETCH_FILE,
215     FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE},
216
217   { ADD_FILE, S_, "fetch-file", FETCH_FILE,
218     FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE },
219
220   { CHECKED_IN, D_, "href", CHECKED_IN_HREF,
221     TRUE, { NULL }, TRUE },
222
223   { PROP, V_, "md5-checksum", MD5_CHECKSUM,
224     TRUE, { NULL }, TRUE },
225
226   /* These are only reported for <= 1.6.x mod_dav_svn */
227   { OPEN_DIR, S_, "fetch-props", FETCH_PROPS,
228     FALSE, { NULL }, FALSE },
229   { OPEN_FILE, S_, "fetch-props", FETCH_PROPS,
230     FALSE, { NULL }, FALSE },
231
232   { PROP, D_, "version-name", VERSION_NAME,
233     TRUE, { NULL }, TRUE },
234   { PROP, D_, "creationdate", CREATIONDATE,
235     TRUE, { NULL }, TRUE },
236   { PROP, D_, "creator-displayname", CREATOR_DISPLAYNAME,
237     TRUE, { NULL }, TRUE },
238   { 0 }
239 };
240
241 /* While we process the REPORT response, we will queue up GET and PROPFIND
242    requests. For a very large checkout, it is very easy to queue requests
243    faster than they are resolved. Thus, we need to pause the XML processing
244    (which queues more requests) to avoid queueing too many, with their
245    attendant memory costs. When the queue count drops low enough, we will
246    resume XML processing.
247
248    Note that we don't want the count to drop to zero. We have multiple
249    connections that we want to keep busy. These are also heuristic numbers
250    since network and parsing behavior (ie. it doesn't pause immediately)
251    can make the measurements quite imprecise.
252
253    We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and
254    NUM_ACTIVE_PROPFINDS in the report_context_t structure.  */
255 #define REQUEST_COUNT_TO_PAUSE 50
256 #define REQUEST_COUNT_TO_RESUME 40
257
258 #define SPILLBUF_BLOCKSIZE 4096
259 #define SPILLBUF_MAXBUFFSIZE 131072
260
261 #define PARSE_CHUNK_SIZE 8000 /* Copied from xml.c ### Needs tuning */
262
263 /* Forward-declare our report context. */
264 typedef struct report_context_t report_context_t;
265 typedef struct body_create_baton_t body_create_baton_t;
266 /*
267  * This structure represents the information for a directory.
268  */
269 typedef struct dir_baton_t
270 {
271   struct dir_baton_t *parent_dir;       /* NULL when root */
272
273   apr_pool_t *pool;                     /* Subpool for this directory */
274
275   /* Pointer back to our original report context. */
276   report_context_t *ctx;
277
278   const char *relpath;                  /* session relative path */
279   const char *base_name;                /* Name of item "" for root */
280
281   /* the canonical url for this directory after updating. (received) */
282   const char *url;
283
284   /* The original repos_relpath of this url (via the reporter)
285   directly, or via an ancestor. */
286   const char *repos_relpath;
287
288   svn_revnum_t base_rev;                /* base revision or NULL for Add */
289
290   const char *copyfrom_path;            /* NULL for open */
291   svn_revnum_t copyfrom_rev;            /* SVN_INVALID_REVNUM for open */
292
293   /* controlling dir baton - this is only created in ensure_dir_opened() */
294   svn_boolean_t dir_opened;
295   void *dir_baton;
296
297   /* How many references to this directory do we still have open? */
298   apr_size_t ref_count;
299
300   svn_boolean_t fetch_props;                 /* Use PROPFIND request? */
301   svn_ra_serf__handler_t *propfind_handler;
302   apr_hash_t *remove_props;
303
304 } dir_baton_t;
305
306 /*
307 * This structure represents the information for a file.
308 *
309 * This structure is created as we parse the REPORT response and
310 * once the element is completed, we may create a fetch_ctx_t structure
311 * to give to serf to retrieve this file.
312 */
313 typedef struct file_baton_t
314 {
315   dir_baton_t *parent_dir;              /* The parent */
316   apr_pool_t *pool;                     /* Subpool for this file*/
317
318   const char *relpath;                  /* session relative path */
319   const char *base_name;
320
321   /* the canonical url for this directory after updating. (received) */
322   const char *url;
323
324   /* The original repos_relpath of this url as reported. */
325   const char *repos_relpath;
326
327   /* lock token, if we had one to start off with. */
328   const char *lock_token;
329
330   svn_revnum_t base_rev;                /* SVN_INVALID_REVNUM for Add */
331
332   const char *copyfrom_path;            /* NULL for open */
333   svn_revnum_t copyfrom_rev;            /* SVN_INVALID_REVNUM for open */
334
335   /* controlling dir baton - this is only created in ensure_file_opened() */
336   svn_boolean_t file_opened;
337   void *file_baton;
338
339   svn_boolean_t fetch_props;            /* Use PROPFIND request? */
340   svn_ra_serf__handler_t *propfind_handler;
341   svn_boolean_t found_lock_prop;
342   apr_hash_t *remove_props;
343
344   /* Has the server told us to go fetch - only valid if we had it already */
345   svn_boolean_t fetch_file;
346
347   /* controlling file_baton and textdelta handler */
348   svn_txdelta_window_handler_t txdelta;
349   void *txdelta_baton;
350
351   svn_checksum_t *base_md5_checksum;
352   svn_checksum_t *final_md5_checksum;
353   svn_checksum_t *final_sha1_checksum;
354
355   svn_stream_t *txdelta_stream;         /* Stream that feeds windows when
356                                            written to within txdelta*/
357 } file_baton_t;
358
359 /*
360  * This structure represents a single request to GET (fetch) a file with
361  * its associated Serf session/connection.
362  */
363 typedef struct fetch_ctx_t {
364
365   /* The handler representing this particular fetch.  */
366   svn_ra_serf__handler_t *handler;
367
368   svn_boolean_t using_compression;
369
370   /* Stores the information for the file we want to fetch. */
371   file_baton_t *file;
372
373   /* Have we read our response headers yet? */
374   svn_boolean_t read_headers;
375
376   /* This flag is set when our response is aborted before we reach the
377    * end and we decide to requeue this request.
378    */
379   svn_boolean_t aborted_read;
380   apr_off_t aborted_read_size;
381
382   /* This is the amount of data that we have read so far. */
383   apr_off_t read_size;
384
385   /* If we're writing this file to a stream, this will be non-NULL. */
386   svn_stream_t *result_stream;
387
388   /* The base-rev header  */
389   const char *delta_base;
390
391 } fetch_ctx_t;
392
393 /*
394  * The master structure for a REPORT request and response.
395  */
396 struct report_context_t {
397   apr_pool_t *pool;
398
399   svn_ra_serf__session_t *sess;
400
401   /* Source path and destination path */
402   const char *source;
403   const char *destination;
404
405   /* Our update target. */
406   const char *update_target;
407
408   /* What is the target revision that we want for this REPORT? */
409   svn_revnum_t target_rev;
410
411   /* Where are we (used while parsing) */
412   dir_baton_t *cur_dir;
413   file_baton_t *cur_file;
414
415   /* Have we been asked to ignore ancestry or textdeltas? */
416   svn_boolean_t ignore_ancestry;
417   svn_boolean_t text_deltas;
418
419   /* Do we want the server to send copyfrom args or not? */
420   svn_boolean_t send_copyfrom_args;
421
422   /* Is the server sending everything in one response? */
423   svn_boolean_t send_all_mode;
424
425   /* Is the server including properties inline for newly added
426      files/dirs? */
427   svn_boolean_t add_props_included;
428
429   /* Path -> const char *repos_relpath mapping */
430   apr_hash_t *switched_paths;
431
432   /* Our master update editor and baton. */
433   const svn_delta_editor_t *editor;
434   void *editor_baton;
435
436   /* The file holding request body for the REPORT.
437    *
438    * ### todo: It will be better for performance to store small
439    * request bodies (like 4k) in memory and bigger bodies on disk.
440    */
441   svn_stream_t *body_template;
442   body_create_baton_t *body;
443
444   /* number of pending GET requests */
445   unsigned int num_active_fetches;
446
447   /* number of pending PROPFIND requests */
448   unsigned int num_active_propfinds;
449
450   /* Are we done parsing the REPORT response? */
451   svn_boolean_t done;
452
453   /* Did we receive all data from the network? */
454   svn_boolean_t report_received;
455
456   /* Did we close the root directory? */
457   svn_boolean_t closed_root;
458 };
459
460 /* Baton for collecting REPORT body. Depending on the size this
461    work is backed by a memory buffer (via serf buckets) or by
462    a file */
463 struct body_create_baton_t
464 {
465   apr_pool_t *result_pool;
466   apr_size_t total_bytes;
467
468   apr_pool_t *scratch_pool;
469
470   serf_bucket_alloc_t *alloc;
471   serf_bucket_t *collect_bucket;
472
473   const void *all_data;
474   apr_file_t *file;
475 };
476
477
478 #define MAX_BODY_IN_RAM (256*1024)
479
480 /* Fold all previously collected data in a single buffer allocated in
481    RESULT_POOL and clear all intermediate state */
482 static const char *
483 body_allocate_all(body_create_baton_t *body,
484                   apr_pool_t *result_pool)
485 {
486   char *buffer = apr_pcalloc(result_pool, body->total_bytes);
487   const char *data;
488   apr_size_t sz;
489   apr_status_t s;
490   apr_size_t remaining = body->total_bytes;
491   char *next = buffer;
492
493   while (!(s = serf_bucket_read(body->collect_bucket, remaining, &data, &sz)))
494     {
495       memcpy(next, data, sz);
496       remaining -= sz;
497       next += sz;
498
499       if (! remaining)
500         break;
501     }
502
503   if (!SERF_BUCKET_READ_ERROR(s))
504     {
505       memcpy(next, data, sz);
506     }
507
508   serf_bucket_destroy(body->collect_bucket);
509   body->collect_bucket = NULL;
510
511   return (s != APR_EOF) ? NULL : buffer;
512 }
513
514 /* Noop function. Make serf take care of freeing in error situations */
515 static void serf_free_no_error(void *unfreed_baton, void *block) {}
516
517 /* Stream write function for body creation */
518 static svn_error_t *
519 body_write_fn(void *baton,
520               const char *data,
521               apr_size_t *len)
522 {
523   body_create_baton_t *bcb = baton;
524
525   if (!bcb->scratch_pool)
526     bcb->scratch_pool = svn_pool_create(bcb->result_pool);
527
528   if (bcb->file)
529     {
530       SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL,
531                                      bcb->scratch_pool));
532       svn_pool_clear(bcb->scratch_pool);
533
534       bcb->total_bytes += *len;
535     }
536   else if (*len + bcb->total_bytes > MAX_BODY_IN_RAM)
537     {
538       SVN_ERR(svn_io_open_unique_file3(&bcb->file, NULL, NULL,
539                                        svn_io_file_del_on_pool_cleanup,
540                                        bcb->result_pool, bcb->scratch_pool));
541
542       if (bcb->total_bytes)
543         {
544           const char *all = body_allocate_all(bcb, bcb->scratch_pool);
545
546           SVN_ERR(svn_io_file_write_full(bcb->file, all, bcb->total_bytes,
547                                          NULL, bcb->scratch_pool));
548         }
549
550       SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL,
551                                      bcb->scratch_pool));
552       bcb->total_bytes += *len;
553     }
554   else
555     {
556       if (!bcb->alloc)
557         bcb->alloc = serf_bucket_allocator_create(bcb->scratch_pool,
558                                                   serf_free_no_error, NULL);
559
560       if (!bcb->collect_bucket)
561         bcb->collect_bucket = serf_bucket_aggregate_create(bcb->alloc);
562
563       serf_bucket_aggregate_append(bcb->collect_bucket,
564                                    serf_bucket_simple_copy_create(data, *len,
565                                                                   bcb->alloc));
566
567       bcb->total_bytes += *len;
568     }
569
570   return SVN_NO_ERROR;
571 }
572
573 /* Stream close function for collecting body */
574 static svn_error_t *
575 body_done_fn(void *baton)
576 {
577   body_create_baton_t *bcb = baton;
578   if (bcb->file)
579     {
580       /* We need to flush the file, make it unbuffered (so that it can be
581         * zero-copied via mmap), and reset the position before attempting
582         * to deliver the file.
583         *
584         * N.B. If we have APR 1.3+, we can unbuffer the file to let us use
585         * mmap and zero-copy the PUT body.  However, on older APR versions,
586         * we can't check the buffer status; but serf will fall through and
587         * create a file bucket for us on the buffered handle.
588         */
589
590       SVN_ERR(svn_io_file_flush(bcb->file, bcb->scratch_pool));
591       apr_file_buffer_set(bcb->file, NULL, 0);
592     }
593   else if (bcb->collect_bucket)
594     bcb->all_data = body_allocate_all(bcb, bcb->result_pool);
595
596   if (bcb->scratch_pool)
597     svn_pool_destroy(bcb->scratch_pool);
598
599   return SVN_NO_ERROR;
600 }
601
602 static svn_error_t *
603 create_dir_baton(dir_baton_t **new_dir,
604                  report_context_t *ctx,
605                  const char *name,
606                  apr_pool_t *scratch_pool)
607 {
608   dir_baton_t *parent = ctx->cur_dir;
609   apr_pool_t *dir_pool;
610   dir_baton_t *dir;
611
612   if (parent)
613     dir_pool = svn_pool_create(parent->pool);
614   else
615     dir_pool = svn_pool_create(ctx->pool);
616
617   dir = apr_pcalloc(dir_pool, sizeof(*dir));
618   dir->pool = dir_pool;
619   dir->ctx = ctx;
620
621   if (parent)
622     {
623       dir->parent_dir = parent;
624       parent->ref_count++;
625     }
626
627   dir->relpath = parent ? svn_relpath_join(parent->relpath, name, dir_pool)
628                         : apr_pstrdup(dir_pool, name);
629   dir->base_name = svn_relpath_basename(dir->relpath, NULL);
630
631   dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->relpath);
632   if (!dir->repos_relpath)
633     {
634       if (parent)
635         dir->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
636                                               dir_pool);
637       else
638         dir->repos_relpath = svn_uri_skip_ancestor(ctx->sess->repos_root_str,
639                                                    ctx->sess->session_url_str,
640                                                    dir_pool);
641     }
642
643   dir->base_rev = SVN_INVALID_REVNUM;
644   dir->copyfrom_rev = SVN_INVALID_REVNUM;
645
646   dir->ref_count = 1;
647
648   ctx->cur_dir = dir;
649
650   *new_dir = dir;
651   return SVN_NO_ERROR;
652 }
653
654 static svn_error_t *
655 create_file_baton(file_baton_t **new_file,
656                   report_context_t *ctx,
657                   const char *name,
658                   apr_pool_t *scratch_pool)
659 {
660   dir_baton_t *parent = ctx->cur_dir;
661   apr_pool_t *file_pool;
662   file_baton_t *file;
663
664   file_pool = svn_pool_create(parent->pool);
665
666   file = apr_pcalloc(file_pool, sizeof(*file));
667   file->pool = file_pool;
668
669   file->parent_dir = parent;
670   parent->ref_count++;
671
672   file->relpath = svn_relpath_join(parent->relpath, name, file_pool);
673   file->base_name = svn_relpath_basename(file->relpath, NULL);
674
675   file->repos_relpath = svn_hash_gets(ctx->switched_paths, file->relpath);
676   if (!file->repos_relpath)
677     file->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
678                                            file_pool);
679
680   /* Sane defaults */
681   file->base_rev = SVN_INVALID_REVNUM;
682   file->copyfrom_rev = SVN_INVALID_REVNUM;
683
684   *new_file = file;
685
686   ctx->cur_file = file;
687
688   return SVN_NO_ERROR;
689 }
690
691 /** Minimum nr. of outstanding requests needed before a new connection is
692  *  opened. */
693 #define REQS_PER_CONN 8
694
695 /** This function creates a new connection for this serf session, but only
696  * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
697  * only one main connection open.
698  */
699 static svn_error_t *
700 open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
701 {
702   /* For each REQS_PER_CONN outstanding requests open a new connection, with
703    * a minimum of 1 extra connection. */
704   if (sess->num_conns == 1 ||
705       ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
706     {
707       int cur = sess->num_conns;
708       apr_status_t status;
709
710       sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
711       sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
712                                                                  NULL, NULL);
713       sess->conns[cur]->last_status_code = -1;
714       sess->conns[cur]->session = sess;
715       status = serf_connection_create2(&sess->conns[cur]->conn,
716                                        sess->context,
717                                        sess->session_url,
718                                        svn_ra_serf__conn_setup,
719                                        sess->conns[cur],
720                                        svn_ra_serf__conn_closed,
721                                        sess->conns[cur],
722                                        sess->pool);
723       if (status)
724         return svn_ra_serf__wrap_err(status, NULL);
725
726       sess->num_conns++;
727     }
728
729   return SVN_NO_ERROR;
730 }
731
732 /* Returns best connection for fetching files/properties. */
733 static svn_ra_serf__connection_t *
734 get_best_connection(report_context_t *ctx)
735 {
736   svn_ra_serf__connection_t *conn;
737   int first_conn = 1;
738
739   /* Skip the first connection if the REPORT response hasn't been completely
740      received yet or if we're being told to limit our connections to
741      2 (because this could be an attempt to ensure that we do all our
742      auxiliary GETs/PROPFINDs on a single connection).
743
744      ### FIXME: This latter requirement (max_connections > 2) is
745      ### really just a hack to work around the fact that some update
746      ### editor implementations (such as svnrdump's dump editor)
747      ### simply can't handle the way ra_serf violates the editor v1
748      ### drive ordering requirements.
749      ###
750      ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
751   */
752   if (ctx->report_received && (ctx->sess->max_connections > 2))
753     first_conn = 0;
754
755   /* If there's only one available auxiliary connection to use, don't bother
756      doing all the cur_conn math -- just return that one connection.  */
757   if (ctx->sess->num_conns - first_conn == 1)
758     {
759       conn = ctx->sess->conns[first_conn];
760     }
761   else
762     {
763 #if SERF_VERSION_AT_LEAST(1, 4, 0)
764       /* Often one connection is slower than others, e.g. because the server
765          process/thread has to do more work for the particular set of requests.
766          In the worst case, when REQUEST_COUNT_TO_RESUME requests are queued
767          on such a slow connection, ra_serf will completely stop sending
768          requests.
769
770          The method used here selects the connection with the least amount of
771          pending requests, thereby giving more work to lightly loaded server
772          processes.
773        */
774       int i, best_conn = first_conn;
775       unsigned int min = INT_MAX;
776       for (i = first_conn; i < ctx->sess->num_conns; i++)
777         {
778           serf_connection_t *sc = ctx->sess->conns[i]->conn;
779           unsigned int pending = serf_connection_pending_requests(sc);
780           if (pending < min)
781             {
782               min = pending;
783               best_conn = i;
784             }
785         }
786       conn = ctx->sess->conns[best_conn];
787 #else
788     /* We don't know how many requests are pending per connection, so just
789        cycle them. */
790       conn = ctx->sess->conns[ctx->sess->cur_conn];
791       ctx->sess->cur_conn++;
792       if (ctx->sess->cur_conn >= ctx->sess->num_conns)
793         ctx->sess->cur_conn = first_conn;
794 #endif
795     }
796   return conn;
797 }
798 \f
799 /** Helpers to open and close directories */
800
801 static svn_error_t*
802 ensure_dir_opened(dir_baton_t *dir,
803                   apr_pool_t *scratch_pool)
804 {
805   report_context_t *ctx = dir->ctx;
806
807   if (dir->dir_opened)
808     return SVN_NO_ERROR;
809
810   if (dir->base_name[0] == '\0')
811     {
812       if (ctx->destination
813           && ctx->sess->wc_callbacks->invalidate_wc_props)
814         {
815           SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
816                       ctx->sess->wc_callback_baton,
817                       ctx->update_target,
818                       SVN_RA_SERF__WC_CHECKED_IN_URL, scratch_pool));
819         }
820
821       SVN_ERR(ctx->editor->open_root(ctx->editor_baton, dir->base_rev,
822                                      dir->pool,
823                                      &dir->dir_baton));
824     }
825   else
826     {
827       SVN_ERR(ensure_dir_opened(dir->parent_dir, scratch_pool));
828
829       if (SVN_IS_VALID_REVNUM(dir->base_rev))
830         {
831           SVN_ERR(ctx->editor->open_directory(dir->relpath,
832                                               dir->parent_dir->dir_baton,
833                                               dir->base_rev,
834                                               dir->pool,
835                                               &dir->dir_baton));
836         }
837       else
838         {
839           SVN_ERR(ctx->editor->add_directory(dir->relpath,
840                                              dir->parent_dir->dir_baton,
841                                              dir->copyfrom_path,
842                                              dir->copyfrom_rev,
843                                              dir->pool,
844                                              &dir->dir_baton));
845         }
846     }
847
848   dir->dir_opened = TRUE;
849
850   return SVN_NO_ERROR;
851 }
852
853 static svn_error_t *
854 maybe_close_dir(dir_baton_t *dir)
855 {
856   apr_pool_t *scratch_pool = dir->pool;
857   dir_baton_t *parent = dir->parent_dir;
858   report_context_t *ctx = dir->ctx;
859
860   if (--dir->ref_count)
861     {
862       return SVN_NO_ERROR;
863     }
864
865   SVN_ERR(ensure_dir_opened(dir, dir->pool));
866
867   if (dir->remove_props)
868     {
869       apr_hash_index_t *hi;
870
871       for (hi = apr_hash_first(scratch_pool, dir->remove_props);
872            hi;
873            hi = apr_hash_next(hi))
874         {
875           SVN_ERR(ctx->editor->change_file_prop(dir->dir_baton,
876                                                 apr_hash_this_key(hi),
877                                                 NULL /* value */,
878                                                 scratch_pool));
879         }
880     }
881
882   SVN_ERR(dir->ctx->editor->close_directory(dir->dir_baton, scratch_pool));
883
884   svn_pool_destroy(dir->pool /* scratch_pool */);
885
886   if (parent)
887     return svn_error_trace(maybe_close_dir(parent));
888   else
889     return SVN_NO_ERROR;
890 }
891
892 static svn_error_t *
893 ensure_file_opened(file_baton_t *file,
894                    apr_pool_t *scratch_pool)
895 {
896   const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
897
898   if (file->file_opened)
899     return SVN_NO_ERROR;
900
901   /* Ensure our parent is open. */
902   SVN_ERR(ensure_dir_opened(file->parent_dir, scratch_pool));
903
904   /* Open (or add) the file. */
905   if (SVN_IS_VALID_REVNUM(file->base_rev))
906     {
907       SVN_ERR(editor->open_file(file->relpath,
908                                 file->parent_dir->dir_baton,
909                                 file->base_rev,
910                                 file->pool,
911                                 &file->file_baton));
912     }
913   else
914     {
915       SVN_ERR(editor->add_file(file->relpath,
916                                file->parent_dir->dir_baton,
917                                file->copyfrom_path,
918                                file->copyfrom_rev,
919                                file->pool,
920                                &file->file_baton));
921     }
922
923   file->file_opened = TRUE;
924
925   return SVN_NO_ERROR;
926 }
927
928 \f
929 /** Routines called when we are fetching a file */
930
931 static svn_error_t *
932 headers_fetch(serf_bucket_t *headers,
933               void *baton,
934               apr_pool_t *pool /* request pool */,
935               apr_pool_t *scratch_pool)
936 {
937   fetch_ctx_t *fetch_ctx = baton;
938
939   /* note that we have old VC URL */
940   if (fetch_ctx->delta_base)
941     {
942       serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
943                                fetch_ctx->delta_base);
944       serf_bucket_headers_setn(headers, "Accept-Encoding",
945                                "svndiff1;q=0.9,svndiff;q=0.8");
946     }
947   else if (fetch_ctx->using_compression)
948     {
949       serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
950     }
951
952   return SVN_NO_ERROR;
953 }
954
955 static svn_error_t *
956 cancel_fetch(serf_request_t *request,
957              serf_bucket_t *response,
958              int status_code,
959              void *baton)
960 {
961   fetch_ctx_t *fetch_ctx = baton;
962
963   /* Uh-oh.  Our connection died on us.
964    *
965    * The core ra_serf layer will requeue our request - we just need to note
966    * that we got cut off in the middle of our song.
967    */
968   if (!response)
969     {
970       /* If we already started the fetch and opened the file handle, we need
971        * to hold subsequent read() ops until we get back to where we were
972        * before the close and we can then resume the textdelta() calls.
973        */
974       if (fetch_ctx->read_headers)
975         {
976           if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
977             {
978               fetch_ctx->aborted_read = TRUE;
979               fetch_ctx->aborted_read_size = fetch_ctx->read_size;
980             }
981           fetch_ctx->read_size = 0;
982         }
983
984       return SVN_NO_ERROR;
985     }
986
987   /* We have no idea what went wrong. */
988   SVN_ERR_MALFUNCTION();
989 }
990
991 /* Wield the editor referenced by INFO to open (or add) the file
992    file also associated with INFO, setting properties on the file and
993    calling the editor's apply_textdelta() function on it if necessary
994    (or if FORCE_APPLY_TEXTDELTA is set).
995
996    Callers will probably want to also see the function that serves
997    the opposite purpose of this one, close_updated_file().  */
998 static svn_error_t *
999 open_file_txdelta(file_baton_t *file,
1000                   apr_pool_t *scratch_pool)
1001 {
1002   const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
1003
1004   SVN_ERR_ASSERT(file->txdelta == NULL);
1005
1006   SVN_ERR(ensure_file_opened(file, scratch_pool));
1007
1008   /* Get (maybe) a textdelta window handler for transmitting file
1009      content changes. */
1010   SVN_ERR(editor->apply_textdelta(file->file_baton,
1011                                   svn_checksum_to_cstring(
1012                                                   file->base_md5_checksum,
1013                                                   scratch_pool),
1014                                   file->pool,
1015                                   &file->txdelta,
1016                                   &file->txdelta_baton));
1017
1018   return SVN_NO_ERROR;
1019 }
1020
1021 /* Close the file, handling loose ends and cleanup */
1022 static svn_error_t *
1023 close_file(file_baton_t *file,
1024            apr_pool_t *scratch_pool)
1025 {
1026   dir_baton_t *parent_dir = file->parent_dir;
1027   report_context_t *ctx = parent_dir->ctx;
1028
1029   SVN_ERR(ensure_file_opened(file, scratch_pool));
1030
1031   /* Set all of the properties we received */
1032   if (file->remove_props)
1033     {
1034       apr_hash_index_t *hi;
1035
1036       for (hi = apr_hash_first(scratch_pool, file->remove_props);
1037            hi;
1038            hi = apr_hash_next(hi))
1039         {
1040           SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1041                                                 apr_hash_this_key(hi),
1042                                                 NULL /* value */,
1043                                                 scratch_pool));
1044         }
1045     }
1046
1047   /* Check for lock information. */
1048
1049   /* This works around a bug in some older versions of mod_dav_svn in that it
1050    * will not send remove-prop in the update report when a lock property
1051    * disappears when send-all is false.
1052
1053    ### Given that we only fetch props on additions, is this really necessary?
1054        Or is it covering up old local copy bugs where we copied locks to other
1055        paths? */
1056   if (!ctx->add_props_included
1057       && file->lock_token && !file->found_lock_prop
1058       && SVN_IS_VALID_REVNUM(file->base_rev) /* file_is_added */)
1059     {
1060       SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1061                                             SVN_PROP_ENTRY_LOCK_TOKEN,
1062                                             NULL,
1063                                             scratch_pool));
1064     }
1065
1066   if (file->url)
1067     {
1068       SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1069                                             SVN_RA_SERF__WC_CHECKED_IN_URL,
1070                                             svn_string_create(file->url,
1071                                                               scratch_pool),
1072                                             scratch_pool));
1073     }
1074
1075   /* Close the file via the editor. */
1076   SVN_ERR(ctx->editor->close_file(file->file_baton,
1077                                   svn_checksum_to_cstring(
1078                                         file->final_md5_checksum,
1079                                         scratch_pool),
1080                                   scratch_pool));
1081
1082   svn_pool_destroy(file->pool);
1083
1084   SVN_ERR(maybe_close_dir(parent_dir)); /* Remove reference */
1085
1086   return SVN_NO_ERROR;
1087 }
1088
1089 /* Implements svn_ra_serf__response_handler_t */
1090 static svn_error_t *
1091 handle_fetch(serf_request_t *request,
1092              serf_bucket_t *response,
1093              void *handler_baton,
1094              apr_pool_t *pool)
1095 {
1096   const char *data;
1097   apr_size_t len;
1098   apr_status_t status;
1099   fetch_ctx_t *fetch_ctx = handler_baton;
1100   file_baton_t *file = fetch_ctx->file;
1101
1102   /* ### new field. make sure we didn't miss some initialization.  */
1103   SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1104
1105   if (!fetch_ctx->read_headers)
1106     {
1107       serf_bucket_t *hdrs;
1108       const char *val;
1109
1110       /* If the error code wasn't 200, something went wrong. Don't use the
1111        * returned data as its probably an error message. Just bail out instead.
1112        */
1113       if (fetch_ctx->handler->sline.code != 200)
1114         {
1115           fetch_ctx->handler->discard_body = TRUE;
1116           return SVN_NO_ERROR; /* Will return an error in the DONE handler */
1117         }
1118
1119       hdrs = serf_bucket_response_get_headers(response);
1120       val = serf_bucket_headers_get(hdrs, "Content-Type");
1121
1122       if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
1123         {
1124           fetch_ctx->result_stream =
1125               svn_txdelta_parse_svndiff(file->txdelta,
1126                                         file->txdelta_baton,
1127                                         TRUE, file->pool);
1128
1129           /* Validate the delta base claimed by the server matches
1130              what we asked for! */
1131           val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
1132           if (val && fetch_ctx->delta_base == NULL)
1133             {
1134               /* We recieved response with delta base header while we didn't
1135                  requested it -- report it as error. */
1136               return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1137                                        _("GET request returned unexpected "
1138                                          "delta base: %s"), val);
1139             }
1140           else if (val && (strcmp(val, fetch_ctx->delta_base) != 0))
1141             {
1142               return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1143                                        _("GET request returned unexpected "
1144                                          "delta base: %s"), val);
1145             }
1146         }
1147       else
1148         {
1149           fetch_ctx->result_stream = NULL;
1150         }
1151
1152       fetch_ctx->read_headers = TRUE;
1153     }
1154
1155   while (TRUE)
1156     {
1157       svn_txdelta_window_t delta_window = { 0 };
1158       svn_txdelta_op_t delta_op;
1159       svn_string_t window_data;
1160
1161       status = serf_bucket_read(response, 8000, &data, &len);
1162       if (SERF_BUCKET_READ_ERROR(status))
1163         {
1164           return svn_ra_serf__wrap_err(status, NULL);
1165         }
1166
1167       fetch_ctx->read_size += len;
1168
1169       if (fetch_ctx->aborted_read)
1170         {
1171           apr_off_t skip;
1172           /* We haven't caught up to where we were before. */
1173           if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1174             {
1175               /* Eek.  What did the file shrink or something? */
1176               if (APR_STATUS_IS_EOF(status))
1177                 {
1178                   SVN_ERR_MALFUNCTION();
1179                 }
1180
1181               /* Skip on to the next iteration of this loop. */
1182               if (status /* includes EAGAIN */)
1183                 return svn_ra_serf__wrap_err(status, NULL);
1184
1185               continue;
1186             }
1187
1188           /* Woo-hoo.  We're back. */
1189           fetch_ctx->aborted_read = FALSE;
1190
1191           /* Update data and len to just provide the new data. */
1192           skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
1193           data += skip;
1194           len -= (apr_size_t)skip;
1195         }
1196
1197       if (fetch_ctx->result_stream)
1198         SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &len));
1199
1200       /* otherwise, manually construct the text delta window. */
1201       else if (len)
1202         {
1203           window_data.data = data;
1204           window_data.len = len;
1205
1206           delta_op.action_code = svn_txdelta_new;
1207           delta_op.offset = 0;
1208           delta_op.length = len;
1209
1210           delta_window.tview_len = len;
1211           delta_window.num_ops = 1;
1212           delta_window.ops = &delta_op;
1213           delta_window.new_data = &window_data;
1214
1215           /* write to the file located in the info. */
1216           SVN_ERR(file->txdelta(&delta_window, file->txdelta_baton));
1217         }
1218
1219       if (APR_STATUS_IS_EOF(status))
1220         {
1221           if (fetch_ctx->result_stream)
1222             SVN_ERR(svn_stream_close(fetch_ctx->result_stream));
1223           else
1224             SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
1225         }
1226
1227       /* Report EOF, EEAGAIN and other special errors to serf */
1228       if (status)
1229         return svn_ra_serf__wrap_err(status, NULL);
1230     }
1231 }
1232
1233 /* --------------------------------------------------------- */
1234
1235 /** Wrappers around our various property walkers **/
1236
1237 /* Implements svn_ra_serf__prop_func */
1238 static svn_error_t *
1239 set_file_props(void *baton,
1240                const char *path,
1241                const char *ns,
1242                const char *name,
1243                const svn_string_t *val,
1244                apr_pool_t *scratch_pool)
1245 {
1246   file_baton_t *file = baton;
1247   report_context_t *ctx = file->parent_dir->ctx;
1248   const char *prop_name;
1249
1250   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
1251
1252   if (!prop_name)
1253     {
1254       /* This works around a bug in some older versions of
1255        * mod_dav_svn in that it will not send remove-prop in the update
1256        * report when a lock property disappears when send-all is false.
1257        *
1258        * Therefore, we'll try to look at our properties and see if there's
1259        * an active lock.  If not, then we'll assume there isn't a lock
1260        * anymore.
1261        */
1262       /* assert(!ctx->add_props_included); // Or we wouldn't be here */
1263       if (file->lock_token
1264           && !file->found_lock_prop
1265           && val
1266           && strcmp(ns, "DAV:") == 0
1267           && strcmp(name, "lockdiscovery") == 0)
1268         {
1269           char *new_lock;
1270           new_lock = apr_pstrdup(scratch_pool, val->data);
1271           apr_collapse_spaces(new_lock, new_lock);
1272
1273           if (new_lock[0] != '\0')
1274             file->found_lock_prop = TRUE;
1275         }
1276
1277       return SVN_NO_ERROR;
1278     }
1279
1280   SVN_ERR(ensure_file_opened(file, scratch_pool));
1281
1282   SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1283                                         prop_name, val,
1284                                         scratch_pool));
1285
1286   return SVN_NO_ERROR;
1287 }
1288
1289 /* Implements svn_ra_serf__response_done_delegate_t */
1290 static svn_error_t *
1291 file_props_done(serf_request_t *request,
1292                 void *baton,
1293                 apr_pool_t *scratch_pool)
1294 {
1295   file_baton_t *file = baton;
1296   svn_ra_serf__handler_t *handler = file->propfind_handler;
1297
1298   if (handler->server_error)
1299       return svn_error_trace(svn_ra_serf__server_error_create(handler,
1300                                                               scratch_pool));
1301
1302   if (handler->sline.code != 207)
1303     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1304
1305   file->parent_dir->ctx->num_active_propfinds--;
1306
1307   file->fetch_props = FALSE;
1308
1309   if (file->fetch_file)
1310     return SVN_NO_ERROR; /* Still processing file request */
1311
1312   /* Closing the file will automatically deliver the propfind props.
1313    *
1314    * Note that closing the directory may dispose the pool containing the
1315    * handler, which is only a valid operation in this callback, as only
1316    * after this callback our serf plumbing assumes the request is done. */
1317
1318   return svn_error_trace(close_file(file, scratch_pool));
1319 }
1320
1321 static svn_error_t *
1322 file_fetch_done(serf_request_t *request,
1323                 void *baton,
1324                 apr_pool_t *scratch_pool)
1325 {
1326   fetch_ctx_t *fetch_ctx = baton;
1327   file_baton_t *file = fetch_ctx->file;
1328   svn_ra_serf__handler_t *handler = fetch_ctx->handler;
1329
1330   if (handler->server_error)
1331       return svn_error_trace(svn_ra_serf__server_error_create(handler,
1332                                                               scratch_pool));
1333
1334   if (handler->sline.code != 200)
1335     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1336
1337   file->parent_dir->ctx->num_active_fetches--;
1338
1339   file->fetch_file = FALSE;
1340
1341   if (file->fetch_props)
1342     return SVN_NO_ERROR; /* Still processing PROPFIND request */
1343
1344   /* Closing the file will automatically deliver the propfind props.
1345    *
1346    * Note that closing the directory may dispose the pool containing the
1347    * handler, fetch_ctx, etc. which is only a valid operation in this
1348    * callback, as only after this callback our serf plumbing assumes the
1349    * request is done. */
1350   return svn_error_trace(close_file(file, scratch_pool));
1351 }
1352
1353 /* Initiates additional requests needed for a file when not in "send-all" mode.
1354  */
1355 static svn_error_t *
1356 fetch_for_file(file_baton_t *file,
1357                apr_pool_t *scratch_pool)
1358 {
1359   report_context_t *ctx = file->parent_dir->ctx;
1360   svn_ra_serf__connection_t *conn;
1361   svn_ra_serf__handler_t *handler;
1362
1363   /* Open extra connections if we have enough requests to send. */
1364   if (ctx->sess->num_conns < ctx->sess->max_connections)
1365     SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
1366                                                  ctx->num_active_propfinds));
1367
1368   /* What connection should we go on? */
1369   conn = get_best_connection(ctx);
1370
1371   /* Note that we (still) use conn for both requests.. Should we send
1372      them out on different connections? */
1373
1374   if (file->fetch_file)
1375     {
1376       SVN_ERR(open_file_txdelta(file, scratch_pool));
1377
1378       if (!ctx->text_deltas
1379           || file->txdelta == svn_delta_noop_window_handler)
1380         {
1381           SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
1382           file->fetch_file = FALSE;
1383         }
1384
1385       if (file->fetch_file
1386           && file->final_sha1_checksum
1387           && ctx->sess->wc_callbacks->get_wc_contents)
1388         {
1389           svn_error_t *err;
1390           svn_stream_t *cached_contents = NULL;
1391
1392           err = ctx->sess->wc_callbacks->get_wc_contents(
1393                                                 ctx->sess->wc_callback_baton,
1394                                                 &cached_contents,
1395                                                 file->final_sha1_checksum,
1396                                                 scratch_pool);
1397
1398           if (err || !cached_contents)
1399             svn_error_clear(err); /* ### Can we return some/most errors? */
1400           else
1401             {
1402               /* ### For debugging purposes we could validate the md5 here,
1403                      but our implementations in libsvn_client already do that
1404                      for us... */
1405               SVN_ERR(svn_txdelta_send_stream(cached_contents,
1406                                               file->txdelta,
1407                                               file->txdelta_baton,
1408                                               NULL, scratch_pool));
1409               SVN_ERR(svn_stream_close(cached_contents));
1410               file->fetch_file = FALSE;
1411             }
1412         }
1413
1414       if (file->fetch_file)
1415         {
1416           fetch_ctx_t *fetch_ctx;
1417
1418           /* Let's fetch the file with a GET request... */
1419           SVN_ERR_ASSERT(file->url && file->repos_relpath);
1420
1421           /* Otherwise, we use a GET request for the file's contents. */
1422
1423           fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx));
1424           fetch_ctx->file = file;
1425           fetch_ctx->using_compression = ctx->sess->using_compression;
1426
1427           /* Can we somehow get away with just obtaining a DIFF? */
1428           if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
1429             {
1430               /* If this file is switched vs the editor root we should provide
1431                  its real url instead of the one calculated from the session root.
1432               */
1433               if (SVN_IS_VALID_REVNUM(file->base_rev))
1434                 {
1435                   fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
1436                                                        ctx->sess->rev_root_stub,
1437                                                        file->base_rev,
1438                                                        svn_path_uri_encode(
1439                                                           file->repos_relpath,
1440                                                           scratch_pool));
1441                 }
1442               else if (file->copyfrom_path)
1443                 {
1444                   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(file->copyfrom_rev));
1445
1446                   fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
1447                                                        ctx->sess->rev_root_stub,
1448                                                        file->copyfrom_rev,
1449                                                        svn_path_uri_encode(
1450                                                           file->copyfrom_path+1,
1451                                                           scratch_pool));
1452                 }
1453             }
1454           else if (ctx->sess->wc_callbacks->get_wc_prop)
1455             {
1456               /* If we have a WC, we might be able to dive all the way into the WC
1457               * to get the previous URL so we can do a differential GET with the
1458               * base URL.
1459               */
1460               const svn_string_t *value = NULL;
1461               SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
1462                                                 ctx->sess->wc_callback_baton,
1463                                                 file->relpath,
1464                                                 SVN_RA_SERF__WC_CHECKED_IN_URL,
1465                                                 &value, scratch_pool));
1466
1467               fetch_ctx->delta_base = value
1468                                         ? apr_pstrdup(file->pool, value->data)
1469                                         : NULL;
1470             }
1471
1472           handler = svn_ra_serf__create_handler(ctx->sess, file->pool);
1473
1474           handler->method = "GET";
1475           handler->path = file->url;
1476
1477           handler->conn = conn; /* Explicit scheduling */
1478
1479           handler->custom_accept_encoding = TRUE;
1480           handler->no_dav_headers = TRUE;
1481           handler->header_delegate = headers_fetch;
1482           handler->header_delegate_baton = fetch_ctx;
1483
1484           handler->response_handler = handle_fetch;
1485           handler->response_baton = fetch_ctx;
1486
1487           handler->response_error = cancel_fetch;
1488           handler->response_error_baton = fetch_ctx;
1489
1490           handler->done_delegate = file_fetch_done;
1491           handler->done_delegate_baton = fetch_ctx;
1492
1493           fetch_ctx->handler = handler;
1494
1495           svn_ra_serf__request_create(handler);
1496
1497           ctx->num_active_fetches++;
1498         }
1499     }
1500
1501   /* If needed, create the PROPFIND to retrieve the file's properties. */
1502   if (file->fetch_props)
1503     {
1504       SVN_ERR(svn_ra_serf__create_propfind_handler(&file->propfind_handler,
1505                                                    ctx->sess, file->url,
1506                                                    ctx->target_rev, "0",
1507                                                    all_props,
1508                                                    set_file_props, file,
1509                                                    file->pool));
1510       file->propfind_handler->conn = conn; /* Explicit scheduling */
1511
1512       file->propfind_handler->done_delegate = file_props_done;
1513       file->propfind_handler->done_delegate_baton = file;
1514
1515       /* Create a serf request for the PROPFIND.  */
1516       svn_ra_serf__request_create(file->propfind_handler);
1517
1518       ctx->num_active_propfinds++;
1519     }
1520
1521   if (file->fetch_props || file->fetch_file)
1522       return SVN_NO_ERROR;
1523
1524
1525   /* Somehow we are done; probably via the local cache.
1526      Close the file and release memory, etc. */
1527
1528   return svn_error_trace(close_file(file, scratch_pool));
1529 }
1530
1531 /* Implements svn_ra_serf__prop_func */
1532 static svn_error_t *
1533 set_dir_prop(void *baton,
1534              const char *path,
1535              const char *ns,
1536              const char *name,
1537              const svn_string_t *val,
1538              apr_pool_t *scratch_pool)
1539 {
1540   dir_baton_t *dir = baton;
1541   report_context_t *ctx = dir->ctx;
1542   const char *prop_name;
1543
1544   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
1545   if (prop_name == NULL)
1546     return SVN_NO_ERROR;
1547
1548   SVN_ERR(ensure_dir_opened(dir, scratch_pool));
1549
1550   SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
1551                                        prop_name, val,
1552                                        scratch_pool));
1553   return SVN_NO_ERROR;
1554 }
1555
1556 /* Implements svn_ra_serf__response_done_delegate_t */
1557 static svn_error_t *
1558 dir_props_done(serf_request_t *request,
1559                void *baton,
1560                apr_pool_t *scratch_pool)
1561 {
1562   dir_baton_t *dir = baton;
1563   svn_ra_serf__handler_t *handler = dir->propfind_handler;
1564
1565   if (handler->server_error)
1566     return svn_ra_serf__server_error_create(handler, scratch_pool);
1567
1568   if (handler->sline.code != 207)
1569     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1570
1571   dir->ctx->num_active_propfinds--;
1572
1573   /* Closing the directory will automatically deliver the propfind props.
1574    *
1575    * Note that closing the directory may dispose the pool containing the
1576    * handler, which is only a valid operation in this callback, as after
1577    * this callback serf assumes the request is done. */
1578
1579   return svn_error_trace(maybe_close_dir(dir));
1580 }
1581
1582 /* Initiates additional requests needed for a directory when not in "send-all"
1583  * mode */
1584 static svn_error_t *
1585 fetch_for_dir(dir_baton_t *dir,
1586               apr_pool_t *scratch)
1587 {
1588   report_context_t *ctx = dir->ctx;
1589   svn_ra_serf__connection_t *conn;
1590
1591   /* Open extra connections if we have enough requests to send. */
1592   if (ctx->sess->num_conns < ctx->sess->max_connections)
1593     SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
1594                                                  ctx->num_active_propfinds));
1595
1596   /* What connection should we go on? */
1597   conn = get_best_connection(ctx);
1598
1599   /* If needed, create the PROPFIND to retrieve the file's properties. */
1600   if (dir->fetch_props)
1601     {
1602       SVN_ERR(svn_ra_serf__create_propfind_handler(&dir->propfind_handler,
1603                                                    ctx->sess, dir->url,
1604                                                    ctx->target_rev, "0",
1605                                                    all_props,
1606                                                    set_dir_prop, dir,
1607                                                    dir->pool));
1608
1609       dir->propfind_handler->conn = conn;
1610       dir->propfind_handler->done_delegate = dir_props_done;
1611       dir->propfind_handler->done_delegate_baton = dir;
1612
1613       /* Create a serf request for the PROPFIND.  */
1614       svn_ra_serf__request_create(dir->propfind_handler);
1615
1616       ctx->num_active_propfinds++;
1617     }
1618   else
1619     SVN_ERR_MALFUNCTION();
1620
1621   return SVN_NO_ERROR;
1622 }
1623
1624 \f
1625 /** XML callbacks for our update-report response parsing */
1626
1627 /* Conforms to svn_ra_serf__xml_opened_t  */
1628 static svn_error_t *
1629 update_opened(svn_ra_serf__xml_estate_t *xes,
1630               void *baton,
1631               int entered_state,
1632               const svn_ra_serf__dav_props_t *tag,
1633               apr_pool_t *scratch_pool)
1634 {
1635   report_context_t *ctx = baton;
1636   apr_hash_t *attrs;
1637
1638   switch (entered_state)
1639     {
1640       case UPDATE_REPORT:
1641         {
1642           const char *val;
1643
1644           attrs = svn_ra_serf__xml_gather_since(xes, UPDATE_REPORT);
1645           val = svn_hash_gets(attrs, "inline-props");
1646
1647           if (val && (strcmp(val, "true") == 0))
1648             ctx->add_props_included = TRUE;
1649
1650           val = svn_hash_gets(attrs, "send-all");
1651
1652           if (val && (strcmp(val, "true") == 0))
1653             {
1654               ctx->send_all_mode = TRUE;
1655
1656               /* All properties are included in send-all mode. */
1657               ctx->add_props_included = TRUE;
1658             }
1659         }
1660         break;
1661
1662       case OPEN_DIR:
1663       case ADD_DIR:
1664         {
1665           dir_baton_t *dir;
1666           const char *name;
1667           attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1668
1669           name = svn_hash_gets(attrs, "name");
1670           if (!name)
1671             name = "";
1672
1673           SVN_ERR(create_dir_baton(&dir, ctx, name, scratch_pool));
1674
1675           if (entered_state == OPEN_DIR)
1676             {
1677               apr_int64_t base_rev;
1678
1679               SVN_ERR(svn_cstring_atoi64(&base_rev,
1680                                          svn_hash_gets(attrs, "rev")));
1681               dir->base_rev = (svn_revnum_t)base_rev;
1682             }
1683           else
1684             {
1685               dir->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
1686
1687               if (dir->copyfrom_path)
1688                 {
1689                   apr_int64_t copyfrom_rev;
1690                   const char *copyfrom_rev_str;
1691                   dir->copyfrom_path = svn_fspath__canonicalize(
1692                                                         dir->copyfrom_path,
1693                                                         dir->pool);
1694
1695                   copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
1696
1697                   if (!copyfrom_rev_str)
1698                     return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1699                                              NULL,
1700                                             _("Missing '%s' attribute"),
1701                                             "copyfrom-rev");
1702
1703                   SVN_ERR(svn_cstring_atoi64(&copyfrom_rev, copyfrom_rev_str));
1704
1705                   dir->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
1706                 }
1707
1708               if (! ctx->add_props_included)
1709                 dir->fetch_props = TRUE;
1710             }
1711         }
1712         break;
1713       case OPEN_FILE:
1714       case ADD_FILE:
1715         {
1716           file_baton_t *file;
1717
1718           attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1719
1720           SVN_ERR(create_file_baton(&file, ctx, svn_hash_gets(attrs, "name"),
1721                                     scratch_pool));
1722
1723           if (entered_state == OPEN_FILE)
1724             {
1725               apr_int64_t base_rev;
1726
1727               SVN_ERR(svn_cstring_atoi64(&base_rev,
1728                                          svn_hash_gets(attrs, "rev")));
1729               file->base_rev = (svn_revnum_t)base_rev;
1730             }
1731           else
1732             {
1733               const char *sha1_checksum;
1734               file->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
1735
1736               if (file->copyfrom_path)
1737                 {
1738                   apr_int64_t copyfrom_rev;
1739                   const char *copyfrom_rev_str;
1740
1741                   file->copyfrom_path = svn_fspath__canonicalize(
1742                                                         file->copyfrom_path,
1743                                                         file->pool);
1744
1745                   copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
1746
1747                   if (!copyfrom_rev_str)
1748                     return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1749                                              NULL,
1750                                             _("Missing '%s' attribute"),
1751                                             "copyfrom-rev");
1752
1753                   SVN_ERR(svn_cstring_atoi64(&copyfrom_rev, copyfrom_rev_str));
1754
1755                   file->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
1756                 }
1757
1758               sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
1759               if (sha1_checksum)
1760                 {
1761                   SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
1762                                                  svn_checksum_sha1,
1763                                                  sha1_checksum,
1764                                                  file->pool));
1765                 }
1766
1767               /* If the server isn't in "send-all" mode, we should expect to
1768                  fetch contents for added files. */
1769               if (! ctx->send_all_mode)
1770                 file->fetch_file = TRUE;
1771
1772               /* If the server isn't included properties for added items,
1773                  we'll need to fetch them ourselves. */
1774               if (! ctx->add_props_included)
1775                 file->fetch_props = TRUE;
1776             }
1777         }
1778         break;
1779
1780       case TXDELTA:
1781         {
1782           file_baton_t *file = ctx->cur_file;
1783           const char *base_checksum;
1784
1785           /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
1786              addition to <fetch-file>s and such) when *not* in
1787              "send-all" mode.  As a client, we're smart enough to know
1788              that's wrong, so we'll just ignore these tags. */
1789           if (! ctx->send_all_mode)
1790             break;
1791
1792           file->fetch_file = FALSE;
1793
1794           attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1795           base_checksum = svn_hash_gets(attrs, "base-checksum");
1796
1797           if (base_checksum)
1798             SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
1799                                            svn_checksum_md5, base_checksum,
1800                                            file->pool));
1801
1802           SVN_ERR(open_file_txdelta(ctx->cur_file, scratch_pool));
1803
1804           if (ctx->cur_file->txdelta != svn_delta_noop_window_handler)
1805             {
1806               svn_stream_t *decoder;
1807
1808               decoder = svn_txdelta_parse_svndiff(file->txdelta,
1809                                                   file->txdelta_baton,
1810                                                   TRUE /* error early close*/,
1811                                                   file->pool);
1812
1813               file->txdelta_stream = svn_base64_decode(decoder, file->pool);
1814             }
1815         }
1816         break;
1817
1818       case FETCH_PROPS:
1819         {
1820           /* Subversion <= 1.6 servers will return a fetch-props element on
1821              open-file and open-dir when non entry props were changed in
1822              !send-all mode. In turn we fetch the full set of properties
1823              and send all of those as *changes* to the editor. So these
1824              editors have to be aware that they receive-non property changes.
1825              (In case of incomplete directories they have to be aware anyway)
1826
1827              In r1063337 this behavior was changed in mod_dav_svn to always
1828              send property changes inline in these cases. (See issue #3657)
1829
1830              Note that before that change the property changes to the last_*
1831              entry props were already inlined via specific xml elements. */
1832           if (ctx->cur_file)
1833             ctx->cur_file->fetch_props = TRUE;
1834           else if (ctx->cur_dir)
1835             ctx->cur_dir->fetch_props = TRUE;
1836         }
1837         break;
1838     }
1839
1840   return SVN_NO_ERROR;
1841 }
1842
1843
1844
1845 /* Conforms to svn_ra_serf__xml_closed_t  */
1846 static svn_error_t *
1847 update_closed(svn_ra_serf__xml_estate_t *xes,
1848               void *baton,
1849               int leaving_state,
1850               const svn_string_t *cdata,
1851               apr_hash_t *attrs,
1852               apr_pool_t *scratch_pool)
1853 {
1854   report_context_t *ctx = baton;
1855
1856   switch (leaving_state)
1857     {
1858       case UPDATE_REPORT:
1859         ctx->done = TRUE;
1860         break;
1861       case TARGET_REVISION:
1862         {
1863           const char *revstr = svn_hash_gets(attrs, "rev");
1864           apr_int64_t rev;
1865
1866           SVN_ERR(svn_cstring_atoi64(&rev, revstr));
1867
1868           SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
1869                                                    (svn_revnum_t)rev,
1870                                                    scratch_pool));
1871         }
1872         break;
1873
1874       case CHECKED_IN_HREF:
1875         if (ctx->cur_file)
1876           ctx->cur_file->url = apr_pstrdup(ctx->cur_file->pool, cdata->data);
1877         else
1878           ctx->cur_dir->url = apr_pstrdup(ctx->cur_dir->pool, cdata->data);
1879         break;
1880
1881       case SET_PROP:
1882       case REMOVE_PROP:
1883         {
1884           const char *name = svn_hash_gets(attrs, "name");
1885           const char *encoding;
1886           const svn_string_t *value;
1887
1888           if (leaving_state == REMOVE_PROP)
1889             value = NULL;
1890           else if ((encoding = svn_hash_gets(attrs, "encoding")))
1891             {
1892               if (strcmp(encoding, "base64") != 0)
1893                 return svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL,
1894                                          _("Got unrecognized encoding '%s'"),
1895                                          encoding);
1896
1897               value = svn_base64_decode_string(cdata, scratch_pool);
1898             }
1899           else
1900             value = cdata;
1901
1902           if (ctx->cur_file)
1903             {
1904               file_baton_t *file = ctx->cur_file;
1905
1906               if (value
1907                   || ctx->add_props_included
1908                   || SVN_IS_VALID_REVNUM(file->base_rev))
1909                 {
1910                   SVN_ERR(ensure_file_opened(file, scratch_pool));
1911
1912                   SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1913                                                         name,
1914                                                         value,
1915                                                         scratch_pool));
1916                 }
1917               else
1918                 {
1919                   if (!file->remove_props)
1920                     file->remove_props = apr_hash_make(file->pool);
1921
1922                   svn_hash_sets(file->remove_props,
1923                                 apr_pstrdup(file->pool, name),
1924                                 "");
1925                 }
1926             }
1927           else
1928             {
1929               dir_baton_t *dir = ctx->cur_dir;
1930
1931               if (value
1932                   || ctx->add_props_included
1933                   || SVN_IS_VALID_REVNUM(dir->base_rev))
1934                 {
1935                   SVN_ERR(ensure_dir_opened(dir, scratch_pool));
1936
1937                   SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
1938                                                        name,
1939                                                        value,
1940                                                        scratch_pool));
1941                 }
1942               else
1943                 {
1944                   if (!dir->remove_props)
1945                     dir->remove_props = apr_hash_make(dir->pool);
1946
1947                   svn_hash_sets(dir->remove_props,
1948                                 apr_pstrdup(dir->pool, name),
1949                                 "");
1950                 }
1951             }
1952         }
1953         break;
1954
1955       case OPEN_DIR:
1956       case ADD_DIR:
1957         {
1958           dir_baton_t *dir = ctx->cur_dir;
1959           ctx->cur_dir = ctx->cur_dir->parent_dir;
1960
1961           if (dir->fetch_props && ! dir->url)
1962             {
1963               return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1964                                       _("The REPORT response did not "
1965                                         "include the requested checked-in "
1966                                         "value"));
1967             }
1968
1969           if (!dir->fetch_props)
1970             {
1971               SVN_ERR(maybe_close_dir(dir));
1972               break; /* dir potentially no longer valid */
1973             }
1974           else
1975             {
1976               /* Otherwise, if the server is *not* in "send-all" mode, we
1977                  are at a point where we can queue up the PROPFIND request */
1978               SVN_ERR(fetch_for_dir(dir, scratch_pool));
1979             }
1980         }
1981         break;
1982
1983       case OPEN_FILE:
1984       case ADD_FILE:
1985         {
1986           file_baton_t *file = ctx->cur_file;
1987
1988           ctx->cur_file = NULL;
1989           /* go fetch info->name from DAV:checked-in */
1990
1991           if ((file->fetch_file || file->fetch_props) && ! file->url)
1992             {
1993               return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1994                                       _("The REPORT response did not "
1995                                         "include the requested checked-in "
1996                                         "value"));
1997             }
1998
1999           /* If the server is in "send-all" mode or didn't get further work,
2000              we can now close the file */
2001           if (! file->fetch_file && ! file->fetch_props)
2002             {
2003               SVN_ERR(close_file(file, scratch_pool));
2004               break; /* file is no longer valid */
2005             }
2006           else
2007             {
2008               /* Otherwise, if the server is *not* in "send-all" mode, we
2009                  should be at a point where we can queue up any auxiliary
2010                  content-fetching requests. */
2011               SVN_ERR(fetch_for_file(file, scratch_pool));
2012             }
2013         }
2014         break;
2015
2016       case MD5_CHECKSUM:
2017         SVN_ERR(svn_checksum_parse_hex(&ctx->cur_file->final_md5_checksum,
2018                                        svn_checksum_md5,
2019                                        cdata->data,
2020                                        ctx->cur_file->pool));
2021         break;
2022
2023       case FETCH_FILE:
2024         {
2025           file_baton_t *file = ctx->cur_file;
2026           const char *base_checksum = svn_hash_gets(attrs, "base-checksum");
2027           const char *sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
2028
2029           if (base_checksum)
2030             SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
2031                                            svn_checksum_md5, base_checksum,
2032                                            file->pool));
2033
2034           /* Property is duplicated between add-file and fetch-file */
2035           if (sha1_checksum && !file->final_sha1_checksum)
2036             SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
2037                                            svn_checksum_sha1,
2038                                            sha1_checksum,
2039                                            file->pool));
2040
2041           /* Some 0.3x mod_dav_svn wrote both txdelta and fetch-file
2042              elements in send-all mode. (See neon for history) */
2043           if (! ctx->send_all_mode)
2044             file->fetch_file = TRUE;
2045         }
2046         break;
2047
2048       case DELETE_ENTRY:
2049         {
2050           const char *name = svn_hash_gets(attrs, "name");
2051           const char *revstr;
2052           apr_int64_t delete_rev;
2053
2054           SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
2055
2056           revstr = svn_hash_gets(attrs, "rev");
2057
2058           if (revstr)
2059             SVN_ERR(svn_cstring_atoi64(&delete_rev, revstr));
2060           else
2061             delete_rev = SVN_INVALID_REVNUM;
2062
2063           SVN_ERR(ctx->editor->delete_entry(
2064                                     svn_relpath_join(ctx->cur_dir->relpath,
2065                                                      name,
2066                                                      scratch_pool),
2067                                     (svn_revnum_t)delete_rev,
2068                                     ctx->cur_dir->dir_baton,
2069                                     scratch_pool));
2070         }
2071         break;
2072
2073       case ABSENT_DIR:
2074         {
2075           const char *name = svn_hash_gets(attrs, "name");
2076
2077           SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
2078
2079           SVN_ERR(ctx->editor->absent_directory(
2080                                     svn_relpath_join(ctx->cur_dir->relpath,
2081                                                      name, scratch_pool),
2082                                     ctx->cur_dir->dir_baton,
2083                                     scratch_pool));
2084         }
2085         break;
2086      case ABSENT_FILE:
2087         {
2088           const char *name = svn_hash_gets(attrs, "name");
2089
2090           SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
2091
2092           SVN_ERR(ctx->editor->absent_file(
2093                                     svn_relpath_join(ctx->cur_dir->relpath,
2094                                                      name, scratch_pool),
2095                                     ctx->cur_dir->dir_baton,
2096                                     scratch_pool));
2097         }
2098         break;
2099
2100       case TXDELTA:
2101         {
2102           file_baton_t *file = ctx->cur_file;
2103
2104           if (file->txdelta_stream)
2105             {
2106               SVN_ERR(svn_stream_close(file->txdelta_stream));
2107               file->txdelta_stream = NULL;
2108             }
2109         }
2110         break;
2111
2112       case VERSION_NAME:
2113       case CREATIONDATE:
2114       case CREATOR_DISPLAYNAME:
2115         {
2116           /* Subversion <= 1.6 servers would return a fetch-props element on
2117              open-file and open-dir when non entry props were changed in
2118              !send-all mode. In turn we fetch the full set of properties and
2119              send those as *changes* to the editor. So these editors have to
2120              be aware that they receive non property changes.
2121              (In case of incomplete directories they have to be aware anyway)
2122
2123              In that case the last_* entry props are posted as 3 specific xml
2124              elements, which we handle here.
2125
2126              In r1063337 this behavior was changed in mod_dav_svn to always
2127              send property changes inline in these cases. (See issue #3657)
2128            */
2129
2130           const char *propname;
2131
2132           if (ctx->cur_file)
2133             SVN_ERR(ensure_file_opened(ctx->cur_file, scratch_pool));
2134           else if (ctx->cur_dir)
2135             SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
2136           else
2137             break;
2138
2139           switch (leaving_state)
2140             {
2141               case VERSION_NAME:
2142                 propname = SVN_PROP_ENTRY_COMMITTED_REV;
2143                 break;
2144               case CREATIONDATE:
2145                 propname = SVN_PROP_ENTRY_COMMITTED_DATE;
2146                 break;
2147               case CREATOR_DISPLAYNAME:
2148                 propname = SVN_PROP_ENTRY_LAST_AUTHOR;
2149                 break;
2150               default:
2151                 SVN_ERR_MALFUNCTION(); /* Impossible to reach */
2152             }
2153
2154           if (ctx->cur_file)
2155             SVN_ERR(ctx->editor->change_file_prop(ctx->cur_file->file_baton,
2156                                                   propname, cdata,
2157                                                   scratch_pool));
2158           else
2159             SVN_ERR(ctx->editor->change_dir_prop(ctx->cur_dir->dir_baton,
2160                                                   propname, cdata,
2161                                                   scratch_pool));
2162         }
2163         break;
2164     }
2165
2166   return SVN_NO_ERROR;
2167 }
2168
2169
2170 /* Conforms to svn_ra_serf__xml_cdata_t  */
2171 static svn_error_t *
2172 update_cdata(svn_ra_serf__xml_estate_t *xes,
2173              void *baton,
2174              int current_state,
2175              const char *data,
2176              apr_size_t len,
2177              apr_pool_t *scratch_pool)
2178 {
2179   report_context_t *ctx = baton;
2180
2181   if (current_state == TXDELTA && ctx->cur_file
2182       && ctx->cur_file->txdelta_stream)
2183     {
2184       SVN_ERR(svn_stream_write(ctx->cur_file->txdelta_stream, data, &len));
2185     }
2186
2187   return SVN_NO_ERROR;
2188 }
2189
2190 \f
2191 /** Editor callbacks given to callers to create request body */
2192
2193 /* Helper to create simple xml tag without attributes. */
2194 static void
2195 make_simple_xml_tag(svn_stringbuf_t **buf_p,
2196                     const char *tagname,
2197                     const char *cdata,
2198                     apr_pool_t *pool)
2199 {
2200   svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname,
2201                         SVN_VA_NULL);
2202   svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
2203   svn_xml_make_close_tag(buf_p, pool, tagname);
2204 }
2205
2206 static svn_error_t *
2207 set_path(void *report_baton,
2208          const char *path,
2209          svn_revnum_t revision,
2210          svn_depth_t depth,
2211          svn_boolean_t start_empty,
2212          const char *lock_token,
2213          apr_pool_t *pool)
2214 {
2215   report_context_t *report = report_baton;
2216   svn_stringbuf_t *buf = NULL;
2217
2218   svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2219                         "rev", apr_ltoa(pool, revision),
2220                         "lock-token", lock_token,
2221                         "depth", svn_depth_to_word(depth),
2222                         "start-empty", start_empty ? "true" : NULL,
2223                         SVN_VA_NULL);
2224   svn_xml_escape_cdata_cstring(&buf, path, pool);
2225   svn_xml_make_close_tag(&buf, pool, "S:entry");
2226
2227   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2228
2229   return SVN_NO_ERROR;
2230 }
2231
2232 static svn_error_t *
2233 delete_path(void *report_baton,
2234             const char *path,
2235             apr_pool_t *pool)
2236 {
2237   report_context_t *report = report_baton;
2238   svn_stringbuf_t *buf = NULL;
2239
2240   make_simple_xml_tag(&buf, "S:missing", path, pool);
2241
2242   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2243
2244   return SVN_NO_ERROR;
2245 }
2246
2247 static svn_error_t *
2248 link_path(void *report_baton,
2249           const char *path,
2250           const char *url,
2251           svn_revnum_t revision,
2252           svn_depth_t depth,
2253           svn_boolean_t start_empty,
2254           const char *lock_token,
2255           apr_pool_t *pool)
2256 {
2257   report_context_t *report = report_baton;
2258   const char *link, *report_target;
2259   apr_uri_t uri;
2260   apr_status_t status;
2261   svn_stringbuf_t *buf = NULL;
2262
2263   /* We need to pass in the baseline relative path.
2264    *
2265    * TODO Confirm that it's on the same server?
2266    */
2267   status = apr_uri_parse(pool, url, &uri);
2268   if (status)
2269     {
2270       return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2271                                _("Unable to parse URL '%s'"), url);
2272     }
2273
2274   SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, pool));
2275   SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, pool));
2276
2277   link = apr_pstrcat(pool, "/", link, SVN_VA_NULL);
2278
2279   svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2280                         "rev", apr_ltoa(pool, revision),
2281                         "lock-token", lock_token,
2282                         "depth", svn_depth_to_word(depth),
2283                         "linkpath", link,
2284                         "start-empty", start_empty ? "true" : NULL,
2285                         SVN_VA_NULL);
2286   svn_xml_escape_cdata_cstring(&buf, path, pool);
2287   svn_xml_make_close_tag(&buf, pool, "S:entry");
2288
2289   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2290
2291   /* Store the switch roots to allow generating repos_relpaths from just
2292      the working copy paths. (Needed for HTTPv2) */
2293   path = apr_pstrdup(report->pool, path);
2294   link = apr_pstrdup(report->pool, link + 1);
2295   svn_hash_sets(report->switched_paths, path, link);
2296
2297   if (!path[0] && report->update_target[0])
2298     {
2299       /* The update root is switched. Make sure we store it the way
2300          we expect it to find */
2301       svn_hash_sets(report->switched_paths, report->update_target, link);
2302     }
2303
2304   return APR_SUCCESS;
2305 }
2306
2307 /* Serf callback to create update request body bucket.
2308    Implements svn_ra_serf__request_body_delegate_t */
2309 static svn_error_t *
2310 create_update_report_body(serf_bucket_t **body_bkt,
2311                           void *baton,
2312                           serf_bucket_alloc_t *alloc,
2313                           apr_pool_t *pool /* request pool */,
2314                           apr_pool_t *scratch_pool)
2315 {
2316   report_context_t *report = baton;
2317   body_create_baton_t *body = report->body;
2318
2319   if (body->file)
2320     {
2321       apr_off_t offset;
2322
2323       offset = 0;
2324       SVN_ERR(svn_io_file_seek(body->file, APR_SET, &offset, pool));
2325
2326       *body_bkt = serf_bucket_file_create(report->body->file, alloc);
2327     }
2328   else
2329     {
2330       *body_bkt = serf_bucket_simple_create(body->all_data,
2331                                             body->total_bytes,
2332                                             NULL, NULL, alloc);
2333     }
2334
2335   return SVN_NO_ERROR;
2336 }
2337
2338 /* Serf callback to setup update request headers. */
2339 static svn_error_t *
2340 setup_update_report_headers(serf_bucket_t *headers,
2341                             void *baton,
2342                             apr_pool_t *pool /* request pool */,
2343                             apr_pool_t *scratch_pool)
2344 {
2345   report_context_t *report = baton;
2346
2347   if (report->sess->using_compression)
2348     {
2349       serf_bucket_headers_setn(headers, "Accept-Encoding",
2350                                "gzip,svndiff1;q=0.9,svndiff;q=0.8");
2351     }
2352   else
2353     {
2354       serf_bucket_headers_setn(headers, "Accept-Encoding",
2355                                "svndiff1;q=0.9,svndiff;q=0.8");
2356     }
2357
2358   return SVN_NO_ERROR;
2359 }
2360
2361 /* Baton for update_delay_handler */
2362 typedef struct update_delay_baton_t
2363 {
2364   report_context_t *report;
2365   svn_spillbuf_t *spillbuf;
2366   svn_ra_serf__response_handler_t inner_handler;
2367   void *inner_handler_baton;
2368 } update_delay_baton_t;
2369
2370 /* Helper for update_delay_handler() and process_pending() to
2371    call UDB->INNER_HANDLER with buffer pointed by DATA. */
2372 static svn_error_t *
2373 process_buffer(update_delay_baton_t *udb,
2374                serf_request_t *request,
2375                const void *data,
2376                apr_size_t len,
2377                svn_boolean_t at_eof,
2378                serf_bucket_alloc_t *alloc,
2379                apr_pool_t *pool)
2380 {
2381   serf_bucket_t *tmp_bucket;
2382   svn_error_t *err;
2383
2384   /* ### This code (and the eagain bucket code) can probably be
2385       ### simplified by using a bit of aggregate bucket magic.
2386       ### See mail from Ivan to dev@s.a.o. */
2387   if (at_eof)
2388   {
2389       tmp_bucket = serf_bucket_simple_create(data, len, NULL, NULL,
2390                                              alloc);
2391   }
2392   else
2393   {
2394       tmp_bucket = svn_ra_serf__create_bucket_with_eagain(data, len,
2395                                                           alloc);
2396   }
2397
2398   /* If not at EOF create a bucket that finishes with EAGAIN, otherwise
2399       use a standard bucket with default EOF handling */
2400   err = udb->inner_handler(request, tmp_bucket,
2401                            udb->inner_handler_baton, pool);
2402
2403   /* And free the bucket explicitly to avoid growing request allocator
2404      storage (in a loop) */
2405   serf_bucket_destroy(tmp_bucket);
2406
2407   return svn_error_trace(err);
2408 }
2409
2410
2411 /* Delaying wrapping reponse handler, to avoid creating too many
2412    requests to deliver efficiently */
2413 static svn_error_t *
2414 update_delay_handler(serf_request_t *request,
2415                      serf_bucket_t *response,
2416                      void *handler_baton,
2417                      apr_pool_t *scratch_pool)
2418 {
2419   update_delay_baton_t *udb = handler_baton;
2420   apr_status_t status;
2421   apr_pool_t *iterpool = NULL;
2422
2423   if (! udb->spillbuf)
2424     {
2425       if (udb->report->send_all_mode)
2426         {
2427           /* Easy out... We only have one request, so avoid everything and just
2428              call the inner handler.
2429
2430              We will always get in the loop (below) on the first chunk, as only
2431              the server can get us in true send-all mode */
2432
2433           return svn_error_trace(udb->inner_handler(request, response,
2434                                                     udb->inner_handler_baton,
2435                                                     scratch_pool));
2436         }
2437
2438       while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
2439                  < REQUEST_COUNT_TO_RESUME)
2440         {
2441           const char *data;
2442           apr_size_t len;
2443           svn_boolean_t at_eof = FALSE;
2444           svn_error_t *err;
2445
2446           status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
2447           if (SERF_BUCKET_READ_ERROR(status))
2448             return svn_ra_serf__wrap_err(status, NULL);
2449           else if (APR_STATUS_IS_EOF(status))
2450             udb->report->report_received = at_eof = TRUE;
2451
2452           if (!iterpool)
2453             iterpool = svn_pool_create(scratch_pool);
2454           else
2455             svn_pool_clear(iterpool);
2456
2457           if (len == 0 && !at_eof)
2458             return svn_ra_serf__wrap_err(status, NULL);
2459
2460           err = process_buffer(udb, request, data, len, at_eof,
2461                                serf_request_get_alloc(request),
2462                                iterpool);
2463
2464           if (err && SERF_BUCKET_READ_ERROR(err->apr_err))
2465             return svn_error_trace(err);
2466           else if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
2467             {
2468               svn_error_clear(err); /* Throttling is working ok */
2469             }
2470           else if (err && (APR_STATUS_IS_EOF(err->apr_err)))
2471             {
2472               svn_pool_destroy(iterpool);
2473               return svn_error_trace(err); /* No buffering was necessary */
2474             }
2475           else
2476             {
2477               /* SERF_ERROR_WAIT_CONN should be impossible? */
2478               return svn_error_trace(err);
2479             }
2480         }
2481
2482       /* Let's start using the spill infrastructure */
2483       udb->spillbuf = svn_spillbuf__create(SPILLBUF_BLOCKSIZE,
2484                                            SPILLBUF_MAXBUFFSIZE,
2485                                            udb->report->pool);
2486     }
2487
2488   /* Read everything we can to a spillbuffer */
2489   do
2490     {
2491       const char *data;
2492       apr_size_t len;
2493
2494       /* ### What blocksize should we pass? */
2495       status = serf_bucket_read(response, 8*PARSE_CHUNK_SIZE, &data, &len);
2496
2497       if (!SERF_BUCKET_READ_ERROR(status))
2498         SVN_ERR(svn_spillbuf__write(udb->spillbuf, data, len, scratch_pool));
2499     }
2500   while (status == APR_SUCCESS);
2501
2502   if (APR_STATUS_IS_EOF(status))
2503     udb->report->report_received = TRUE;
2504
2505   /* We handle feeding the data from the main context loop, which will be right
2506      after processing the pending data */
2507
2508   if (status)
2509     return svn_ra_serf__wrap_err(status, NULL);
2510   else
2511     return SVN_NO_ERROR;
2512 }
2513
2514 /* Process pending data from the update report, if any */
2515 static svn_error_t *
2516 process_pending(update_delay_baton_t *udb,
2517                 apr_pool_t *scratch_pool)
2518 {
2519   apr_pool_t *iterpool = NULL;
2520   serf_bucket_alloc_t *alloc = NULL;
2521
2522   while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
2523             < REQUEST_COUNT_TO_RESUME)
2524     {
2525       const char *data;
2526       apr_size_t len;
2527       svn_boolean_t at_eof;
2528       svn_error_t *err;
2529
2530       if (!iterpool)
2531         {
2532           iterpool = svn_pool_create(scratch_pool);
2533           alloc = serf_bucket_allocator_create(scratch_pool, NULL, NULL);
2534         }
2535       else
2536         svn_pool_clear(iterpool);
2537
2538       SVN_ERR(svn_spillbuf__read(&data, &len, udb->spillbuf, iterpool));
2539
2540       if (data == NULL && !udb->report->report_received)
2541         break;
2542       else if (data == NULL)
2543         at_eof = TRUE;
2544       else
2545         at_eof = FALSE;
2546
2547       err = process_buffer(udb, NULL /* allowed? */, data, len,
2548                            at_eof, alloc, iterpool);
2549
2550       if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
2551         {
2552           svn_error_clear(err); /* Throttling is working */
2553         }
2554       else if (err && APR_STATUS_IS_EOF(err->apr_err))
2555         {
2556           svn_error_clear(err);
2557
2558           svn_pool_destroy(iterpool);
2559           udb->spillbuf = NULL;
2560           return SVN_NO_ERROR;
2561         }
2562       else if (err)
2563         return svn_error_trace(err);
2564     }
2565
2566   if (iterpool)
2567     svn_pool_destroy(iterpool);
2568
2569   return SVN_NO_ERROR;
2570 }
2571
2572 /* Process the 'update' editor report */
2573 static svn_error_t *
2574 process_editor_report(report_context_t *ctx,
2575                       svn_ra_serf__handler_t *handler,
2576                       apr_pool_t *scratch_pool)
2577 {
2578   svn_ra_serf__session_t *sess = ctx->sess;
2579   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2580   apr_interval_time_t waittime_left = sess->timeout;
2581   update_delay_baton_t *ud;
2582
2583   /* Now wrap the response handler with delay support to avoid sending
2584      out too many requests at once */
2585   ud = apr_pcalloc(scratch_pool, sizeof(*ud));
2586   ud->report = ctx;
2587
2588   ud->inner_handler = handler->response_handler;
2589   ud->inner_handler_baton = handler->response_baton;
2590
2591   handler->response_handler = update_delay_handler;
2592   handler->response_baton = ud;
2593
2594   /* Open the first extra connection. */
2595   SVN_ERR(open_connection_if_needed(sess, 0));
2596
2597   sess->cur_conn = 1;
2598
2599   /* Note that we may have no active GET or PROPFIND requests, yet the
2600      processing has not been completed. This could be from a delay on the
2601      network or because we've spooled the entire response into our "pending"
2602      content of the XML parser. The DONE flag will get set when all the
2603      XML content has been received *and* parsed.  */
2604   while (!handler->done
2605          || ctx->num_active_fetches
2606          || ctx->num_active_propfinds
2607          || !ctx->done)
2608     {
2609       svn_error_t *err;
2610       int i;
2611
2612       svn_pool_clear(iterpool);
2613
2614       err = svn_ra_serf__context_run(sess, &waittime_left, iterpool);
2615
2616       if (handler->done && handler->server_error)
2617         {
2618           svn_error_clear(err);
2619           err = svn_ra_serf__server_error_create(handler, iterpool);
2620
2621           SVN_ERR_ASSERT(err != NULL);
2622         }
2623
2624       SVN_ERR(err);
2625
2626       /* If there is pending REPORT data, process it now. */
2627       if (ud->spillbuf)
2628         SVN_ERR(process_pending(ud, iterpool));
2629
2630       /* Debugging purposes only! */
2631       for (i = 0; i < sess->num_conns; i++)
2632         {
2633           serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
2634         }
2635     }
2636
2637   svn_pool_clear(iterpool);
2638
2639   /* If we got a complete report, close the edit.  Otherwise, abort it. */
2640   if (ctx->done)
2641     SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, iterpool));
2642   else
2643     return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2644                             _("Missing update-report close tag"));
2645
2646   svn_pool_destroy(iterpool);
2647   return SVN_NO_ERROR;
2648 }
2649
2650 static svn_error_t *
2651 finish_report(void *report_baton,
2652               apr_pool_t *pool)
2653 {
2654   report_context_t *report = report_baton;
2655   svn_ra_serf__session_t *sess = report->sess;
2656   svn_ra_serf__handler_t *handler;
2657   svn_ra_serf__xml_context_t *xmlctx;
2658   const char *report_target;
2659   svn_stringbuf_t *buf = NULL;
2660   apr_pool_t *scratch_pool = svn_pool_create(pool);
2661   svn_error_t *err;
2662
2663   svn_xml_make_close_tag(&buf, scratch_pool, "S:update-report");
2664   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2665   SVN_ERR(svn_stream_close(report->body_template));
2666
2667   SVN_ERR(svn_ra_serf__report_resource(&report_target, sess,  scratch_pool));
2668
2669   xmlctx = svn_ra_serf__xml_context_create(update_ttable,
2670                                            update_opened, update_closed,
2671                                            update_cdata,
2672                                            report,
2673                                            scratch_pool);
2674   handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL,
2675                                               scratch_pool);
2676
2677   handler->method = "REPORT";
2678   handler->path = report_target;
2679   handler->body_delegate = create_update_report_body;
2680   handler->body_delegate_baton = report;
2681   handler->body_type = "text/xml";
2682   handler->custom_accept_encoding = TRUE;
2683   handler->header_delegate = setup_update_report_headers;
2684   handler->header_delegate_baton = report;
2685
2686   svn_ra_serf__request_create(handler);
2687
2688   err = process_editor_report(report, handler, scratch_pool);
2689
2690   if (err)
2691     {
2692       err = svn_error_trace(err);
2693       err = svn_error_compose_create(
2694                 err,
2695                 svn_error_trace(
2696                     report->editor->abort_edit(report->editor_baton,
2697                                                scratch_pool)));
2698     }
2699
2700   svn_pool_destroy(scratch_pool);
2701
2702   return svn_error_trace(err);
2703 }
2704
2705
2706 static svn_error_t *
2707 abort_report(void *report_baton,
2708              apr_pool_t *pool)
2709 {
2710 #if 0
2711   report_context_t *report = report_baton;
2712 #endif
2713
2714   /* Should we perform some cleanup here? */
2715
2716   return SVN_NO_ERROR;
2717 }
2718
2719 static const svn_ra_reporter3_t ra_serf_reporter = {
2720   set_path,
2721   delete_path,
2722   link_path,
2723   finish_report,
2724   abort_report
2725 };
2726
2727 \f
2728 /** RA function implementations and body */
2729
2730 static svn_error_t *
2731 make_update_reporter(svn_ra_session_t *ra_session,
2732                      const svn_ra_reporter3_t **reporter,
2733                      void **report_baton,
2734                      svn_revnum_t revision,
2735                      const char *src_path,
2736                      const char *dest_path,
2737                      const char *update_target,
2738                      svn_depth_t depth,
2739                      svn_boolean_t ignore_ancestry,
2740                      svn_boolean_t text_deltas,
2741                      svn_boolean_t send_copyfrom_args,
2742                      const svn_delta_editor_t *update_editor,
2743                      void *update_baton,
2744                      apr_pool_t *result_pool,
2745                      apr_pool_t *scratch_pool)
2746 {
2747   report_context_t *report;
2748   const svn_delta_editor_t *filter_editor;
2749   void *filter_baton;
2750   svn_boolean_t has_target = *update_target != '\0';
2751   svn_boolean_t server_supports_depth;
2752   svn_ra_serf__session_t *sess = ra_session->priv;
2753   svn_stringbuf_t *buf = NULL;
2754   svn_boolean_t use_bulk_updates;
2755
2756   SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
2757                                       SVN_RA_CAPABILITY_DEPTH, scratch_pool));
2758   /* We can skip the depth filtering when the user requested
2759      depth_files or depth_infinity because the server will
2760      transmit the right stuff anyway. */
2761   if ((depth != svn_depth_files)
2762       && (depth != svn_depth_infinity)
2763       && ! server_supports_depth)
2764     {
2765       SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
2766                                             &filter_baton,
2767                                             update_editor,
2768                                             update_baton,
2769                                             depth, has_target,
2770                                             result_pool));
2771       update_editor = filter_editor;
2772       update_baton = filter_baton;
2773     }
2774
2775   report = apr_pcalloc(result_pool, sizeof(*report));
2776   report->pool = result_pool;
2777   report->sess = sess;
2778   report->target_rev = revision;
2779   report->ignore_ancestry = ignore_ancestry;
2780   report->send_copyfrom_args = send_copyfrom_args;
2781   report->text_deltas = text_deltas;
2782   report->switched_paths = apr_hash_make(report->pool);
2783
2784   report->source = src_path;
2785   report->destination = dest_path;
2786   report->update_target = update_target;
2787
2788   report->editor = update_editor;
2789   report->editor_baton = update_baton;
2790   report->done = FALSE;
2791
2792   *reporter = &ra_serf_reporter;
2793   *report_baton = report;
2794
2795   report->body = apr_pcalloc(report->pool, sizeof(*report->body));
2796   report->body->result_pool = report->pool;
2797   report->body_template = svn_stream_create(report->body, report->pool);
2798   svn_stream_set_write(report->body_template, body_write_fn);
2799   svn_stream_set_close(report->body_template, body_done_fn);
2800
2801   if (sess->bulk_updates == svn_tristate_true)
2802     {
2803       /* User would like to use bulk updates. */
2804       use_bulk_updates = TRUE;
2805     }
2806   else if (sess->bulk_updates == svn_tristate_false)
2807     {
2808       /* User doesn't want bulk updates. */
2809       use_bulk_updates = FALSE;
2810     }
2811   else
2812     {
2813       /* User doesn't have any preferences on bulk updates. Decide on server
2814          preferences and capabilities. */
2815       if (sess->server_allows_bulk)
2816         {
2817           if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
2818             {
2819               /* Server doesn't want bulk updates */
2820               use_bulk_updates = FALSE;
2821             }
2822           else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
2823             {
2824               /* Server prefers bulk updates, and we respect that */
2825               use_bulk_updates = TRUE;
2826             }
2827           else
2828             {
2829               /* Server allows bulk updates, but doesn't dictate its use. Do
2830                  whatever is the default. */
2831               use_bulk_updates = FALSE;
2832             }
2833         }
2834       else
2835         {
2836           /* Pre-1.8 server didn't send the bulk_updates header. Check if server
2837              supports inlining properties in update editor report. */
2838           if (sess->supports_inline_props)
2839             {
2840               /* NOTE: both inlined properties and server->allows_bulk_update
2841                  (flag SVN_DAV_ALLOW_BULK_UPDATES) were added in 1.8.0, so
2842                  this code is never reached with a released version of
2843                  mod_dav_svn.
2844
2845                  Basically by default a 1.8.0 client connecting to a 1.7.x or
2846                  older server will always use bulk updates. */
2847
2848               /* Inline props supported: do not use bulk updates. */
2849               use_bulk_updates = FALSE;
2850             }
2851           else
2852             {
2853               /* Inline props are not supported: use bulk updates to avoid
2854                * PROPFINDs for every added node. */
2855               use_bulk_updates = TRUE;
2856             }
2857         }
2858     }
2859
2860   if (use_bulk_updates)
2861     {
2862       svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
2863                             "S:update-report",
2864                             "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
2865                             SVN_VA_NULL);
2866     }
2867   else
2868     {
2869       svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
2870                             "S:update-report",
2871                             "xmlns:S", SVN_XML_NAMESPACE,
2872                             SVN_VA_NULL);
2873       /* Subversion 1.8+ servers can be told to send properties for newly
2874          added items inline even when doing a skelta response. */
2875       make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
2876     }
2877
2878   make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
2879
2880   if (SVN_IS_VALID_REVNUM(report->target_rev))
2881     {
2882       make_simple_xml_tag(&buf, "S:target-revision",
2883                           apr_ltoa(scratch_pool, report->target_rev),
2884                           scratch_pool);
2885     }
2886
2887   if (report->destination && *report->destination)
2888     {
2889       make_simple_xml_tag(&buf, "S:dst-path", report->destination,
2890                           scratch_pool);
2891     }
2892
2893   if (report->update_target && *report->update_target)
2894     {
2895       make_simple_xml_tag(&buf, "S:update-target", report->update_target,
2896                           scratch_pool);
2897     }
2898
2899   if (report->ignore_ancestry)
2900     {
2901       make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
2902     }
2903
2904   if (report->send_copyfrom_args)
2905     {
2906       make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
2907     }
2908
2909   /* Old servers know "recursive" but not "depth"; help them DTRT. */
2910   if (depth == svn_depth_files || depth == svn_depth_empty)
2911     {
2912       make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
2913     }
2914
2915   /* When in 'send-all' mode, mod_dav_svn will assume that it should
2916      calculate and transmit real text-deltas (instead of empty windows
2917      that merely indicate "text is changed") unless it finds this
2918      element.
2919
2920      NOTE: Do NOT count on servers actually obeying this, as some exist
2921      which obey send-all, but do not check for this directive at all!
2922
2923      NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
2924      override our request and send text-deltas. */
2925   if (! text_deltas)
2926     {
2927       make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
2928     }
2929
2930   make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
2931
2932   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2933
2934   return SVN_NO_ERROR;
2935 }
2936
2937 svn_error_t *
2938 svn_ra_serf__do_update(svn_ra_session_t *ra_session,
2939                        const svn_ra_reporter3_t **reporter,
2940                        void **report_baton,
2941                        svn_revnum_t revision_to_update_to,
2942                        const char *update_target,
2943                        svn_depth_t depth,
2944                        svn_boolean_t send_copyfrom_args,
2945                        svn_boolean_t ignore_ancestry,
2946                        const svn_delta_editor_t *update_editor,
2947                        void *update_baton,
2948                        apr_pool_t *result_pool,
2949                        apr_pool_t *scratch_pool)
2950 {
2951   svn_ra_serf__session_t *session = ra_session->priv;
2952
2953   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
2954                                revision_to_update_to,
2955                                session->session_url.path, NULL, update_target,
2956                                depth, ignore_ancestry, TRUE /* text_deltas */,
2957                                send_copyfrom_args,
2958                                update_editor, update_baton,
2959                                result_pool, scratch_pool));
2960   return SVN_NO_ERROR;
2961 }
2962
2963 svn_error_t *
2964 svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
2965                      const svn_ra_reporter3_t **reporter,
2966                      void **report_baton,
2967                      svn_revnum_t revision,
2968                      const char *diff_target,
2969                      svn_depth_t depth,
2970                      svn_boolean_t ignore_ancestry,
2971                      svn_boolean_t text_deltas,
2972                      const char *versus_url,
2973                      const svn_delta_editor_t *diff_editor,
2974                      void *diff_baton,
2975                      apr_pool_t *pool)
2976 {
2977   svn_ra_serf__session_t *session = ra_session->priv;
2978   apr_pool_t *scratch_pool = svn_pool_create(pool);
2979
2980   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
2981                                revision,
2982                                session->session_url.path, versus_url, diff_target,
2983                                depth, ignore_ancestry, text_deltas,
2984                                FALSE /* send_copyfrom */,
2985                                diff_editor, diff_baton,
2986                                pool, scratch_pool));
2987   svn_pool_destroy(scratch_pool);
2988   return SVN_NO_ERROR;
2989 }
2990
2991 svn_error_t *
2992 svn_ra_serf__do_status(svn_ra_session_t *ra_session,
2993                        const svn_ra_reporter3_t **reporter,
2994                        void **report_baton,
2995                        const char *status_target,
2996                        svn_revnum_t revision,
2997                        svn_depth_t depth,
2998                        const svn_delta_editor_t *status_editor,
2999                        void *status_baton,
3000                        apr_pool_t *pool)
3001 {
3002   svn_ra_serf__session_t *session = ra_session->priv;
3003   apr_pool_t *scratch_pool = svn_pool_create(pool);
3004
3005   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3006                                revision,
3007                                session->session_url.path, NULL, status_target,
3008                                depth, FALSE, FALSE, FALSE,
3009                                status_editor, status_baton,
3010                                pool, scratch_pool));
3011   svn_pool_destroy(scratch_pool);
3012   return SVN_NO_ERROR;
3013 }
3014
3015 svn_error_t *
3016 svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
3017                        const svn_ra_reporter3_t **reporter,
3018                        void **report_baton,
3019                        svn_revnum_t revision_to_switch_to,
3020                        const char *switch_target,
3021                        svn_depth_t depth,
3022                        const char *switch_url,
3023                        svn_boolean_t send_copyfrom_args,
3024                        svn_boolean_t ignore_ancestry,
3025                        const svn_delta_editor_t *switch_editor,
3026                        void *switch_baton,
3027                        apr_pool_t *result_pool,
3028                        apr_pool_t *scratch_pool)
3029 {
3030   svn_ra_serf__session_t *session = ra_session->priv;
3031
3032   return make_update_reporter(ra_session, reporter, report_baton,
3033                               revision_to_switch_to,
3034                               session->session_url.path,
3035                               switch_url, switch_target,
3036                               depth,
3037                               ignore_ancestry,
3038                               TRUE /* text_deltas */,
3039                               send_copyfrom_args,
3040                               switch_editor, switch_baton,
3041                               result_pool, scratch_pool);
3042 }