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
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
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 *
create_dir_baton(dir_baton_t ** new_dir,report_context_t * ctx,const char * name,apr_pool_t * scratch_pool)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 *
create_file_baton(file_baton_t ** new_file,report_context_t * ctx,const char * name,apr_pool_t * scratch_pool)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 *
open_connection_if_needed(svn_ra_serf__session_t * sess,int num_active_reqs)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 *
get_best_connection(report_context_t * ctx)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
655 /** Helpers to open and close directories */
656
657 static svn_error_t*
ensure_dir_opened(dir_baton_t * dir,apr_pool_t * scratch_pool)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 *
maybe_close_dir(dir_baton_t * dir)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 *
ensure_file_opened(file_baton_t * file,apr_pool_t * scratch_pool)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
785 /** Routines called when we are fetching a file */
786
787 static svn_error_t *
headers_fetch(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)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 *
cancel_fetch(serf_request_t * request,serf_bucket_t * response,int status_code,void * baton)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 *
open_file_txdelta(file_baton_t * file,apr_pool_t * scratch_pool)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 *
close_file(file_baton_t * file,apr_pool_t * scratch_pool)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 *
handle_fetch(serf_request_t * request,serf_bucket_t * response,void * handler_baton,apr_pool_t * pool)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 *
set_file_props(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * scratch_pool)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 *
file_props_done(serf_request_t * request,void * baton,apr_pool_t * scratch_pool)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 *
file_fetch_done(serf_request_t * request,void * baton,apr_pool_t * scratch_pool)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 *
fetch_for_file(file_baton_t * file,apr_pool_t * scratch_pool)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 *
set_dir_prop(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * scratch_pool)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 *
dir_props_done(serf_request_t * request,void * baton,apr_pool_t * scratch_pool)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 *
fetch_for_dir(dir_baton_t * dir,apr_pool_t * scratch)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
1480 /** XML callbacks for our update-report response parsing */
1481
1482 /* Conforms to svn_ra_serf__xml_opened_t */
1483 static svn_error_t *
update_opened(svn_ra_serf__xml_estate_t * xes,void * baton,int entered_state,const svn_ra_serf__dav_props_t * tag,apr_pool_t * scratch_pool)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(©from_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(©from_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 *
update_closed(svn_ra_serf__xml_estate_t * xes,void * baton,int leaving_state,const svn_string_t * cdata,apr_hash_t * attrs,apr_pool_t * scratch_pool)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 *
update_cdata(svn_ra_serf__xml_estate_t * xes,void * baton,int current_state,const char * data,apr_size_t len,apr_pool_t * scratch_pool)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
2046 /** Editor callbacks given to callers to create request body */
2047
2048 /* Helper to create simple xml tag without attributes. */
2049 static void
make_simple_xml_tag(svn_stringbuf_t ** buf_p,const char * tagname,const char * cdata,apr_pool_t * pool)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 *
set_path(void * report_baton,const char * path,svn_revnum_t revision,svn_depth_t depth,svn_boolean_t start_empty,const char * lock_token,apr_pool_t * pool)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 *
delete_path(void * report_baton,const char * path,apr_pool_t * pool)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 *
link_path(void * report_baton,const char * path,const char * url,svn_revnum_t revision,svn_depth_t depth,svn_boolean_t start_empty,const char * lock_token,apr_pool_t * pool)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 *
setup_update_report_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)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 *
process_buffer(update_delay_baton_t * udb,serf_request_t * request,const void * data,apr_size_t len,svn_boolean_t at_eof,serf_bucket_alloc_t * alloc,apr_pool_t * pool)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 *
update_delay_handler(serf_request_t * request,serf_bucket_t * response,void * handler_baton,apr_pool_t * scratch_pool)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 *
process_pending(update_delay_baton_t * udb,apr_pool_t * scratch_pool)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 *
process_editor_report(report_context_t * ctx,svn_ra_serf__handler_t * handler,apr_pool_t * scratch_pool)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 *
finish_report(void * report_baton,apr_pool_t * pool)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 *
abort_report(void * report_baton,apr_pool_t * pool)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
2544 /** RA function implementations and body */
2545
2546 static svn_error_t *
make_update_reporter(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,svn_revnum_t revision,const char * src_path,const char * dest_path,const char * update_target,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t text_deltas,svn_boolean_t send_copyfrom_args,const svn_delta_editor_t * update_editor,void * update_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_ra_serf__do_update(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,svn_revnum_t revision_to_update_to,const char * update_target,svn_depth_t depth,svn_boolean_t send_copyfrom_args,svn_boolean_t ignore_ancestry,const svn_delta_editor_t * update_editor,void * update_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_ra_serf__do_diff(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,svn_revnum_t revision,const char * diff_target,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t text_deltas,const char * versus_url,const svn_delta_editor_t * diff_editor,void * diff_baton,apr_pool_t * pool)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 *
svn_ra_serf__do_status(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,const char * status_target,svn_revnum_t revision,svn_depth_t depth,const svn_delta_editor_t * status_editor,void * status_baton,apr_pool_t * pool)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 *
svn_ra_serf__do_switch(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,svn_revnum_t revision_to_switch_to,const char * switch_target,svn_depth_t depth,const char * switch_url,svn_boolean_t send_copyfrom_args,svn_boolean_t ignore_ancestry,const svn_delta_editor_t * switch_editor,void * switch_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 }
2858