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