2 * update.c : entry point for update RA functions for ra_serf
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
26 #define APR_WANT_STRFUNC
27 #include <apr_version.h>
35 #include "svn_pools.h"
39 #include "svn_delta.h"
41 #include "svn_base64.h"
42 #include "svn_props.h"
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"
50 #include "../libsvn_ra/ra_loader.h"
55 * This enum represents the current state of our XML parsing for a REPORT.
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.
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.
66 typedef enum report_state_e {
67 INITIAL = XML_STATE_INITIAL /* = 0 */,
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 },
108 { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
109 FALSE, { "rev", NULL }, TRUE },
111 { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
112 FALSE, { "rev", NULL }, TRUE },
114 { OPEN_DIR, S_, "open-directory", OPEN_DIR,
115 FALSE, { "rev", "name", NULL }, TRUE },
117 { ADD_DIR, S_, "open-directory", OPEN_DIR,
118 FALSE, { "rev", "name", NULL }, TRUE },
120 { OPEN_DIR, S_, "add-directory", ADD_DIR,
121 FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/
124 { ADD_DIR, S_, "add-directory", ADD_DIR,
125 FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/
128 { OPEN_DIR, S_, "open-file", OPEN_FILE,
129 FALSE, { "rev", "name", NULL }, TRUE },
131 { ADD_DIR, S_, "open-file", OPEN_FILE,
132 FALSE, { "rev", "name", NULL }, TRUE },
134 { OPEN_DIR, S_, "add-file", ADD_FILE,
135 FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
136 "?sha1-checksum", NULL }, TRUE },
138 { ADD_DIR, S_, "add-file", ADD_FILE,
139 FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
140 "?sha1-checksum", NULL }, TRUE },
142 { OPEN_DIR, S_, "delete-entry", DELETE_ENTRY,
143 FALSE, { "?rev", "name", NULL }, TRUE },
145 { ADD_DIR, S_, "delete-entry", DELETE_ENTRY,
146 FALSE, { "?rev", "name", NULL }, TRUE },
148 { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
149 FALSE, { "name", NULL }, TRUE },
151 { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
152 FALSE, { "name", NULL }, TRUE },
154 { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
155 FALSE, { "name", NULL }, TRUE },
157 { ADD_DIR, S_, "absent-file", ABSENT_FILE,
158 FALSE, { "name", NULL }, TRUE },
161 { OPEN_DIR, D_, "checked-in", CHECKED_IN,
162 FALSE, { NULL }, FALSE },
164 { ADD_DIR, D_, "checked-in", CHECKED_IN,
165 FALSE, { NULL }, FALSE },
167 { OPEN_FILE, D_, "checked-in", CHECKED_IN,
168 FALSE, { NULL }, FALSE },
170 { ADD_FILE, D_, "checked-in", CHECKED_IN,
171 FALSE, { NULL }, FALSE },
174 { OPEN_DIR, S_, "set-prop", SET_PROP,
175 TRUE, { "name", "?encoding", NULL }, TRUE },
177 { ADD_DIR, S_, "set-prop", SET_PROP,
178 TRUE, { "name", "?encoding", NULL }, TRUE },
180 { OPEN_FILE, S_, "set-prop", SET_PROP,
181 TRUE, { "name", "?encoding", NULL }, TRUE },
183 { ADD_FILE, S_, "set-prop", SET_PROP,
184 TRUE, { "name", "?encoding", NULL }, TRUE },
187 { OPEN_DIR, S_, "remove-prop", REMOVE_PROP,
188 TRUE, { "name", NULL }, TRUE },
190 { ADD_DIR, S_, "remove-prop", REMOVE_PROP,
191 TRUE, { "name", NULL }, TRUE },
193 { OPEN_FILE, S_, "remove-prop", REMOVE_PROP,
194 TRUE, { "name", NULL }, TRUE },
196 { ADD_FILE, S_, "remove-prop", REMOVE_PROP,
197 TRUE, { "name", NULL }, TRUE },
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 },
208 { OPEN_FILE, S_, "txdelta", TXDELTA,
209 FALSE, { "?base-checksum" }, TRUE },
211 { ADD_FILE, S_, "txdelta", TXDELTA,
212 FALSE, { "?base-checksum" }, TRUE },
214 { OPEN_FILE, S_, "fetch-file", FETCH_FILE,
215 FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE},
217 { ADD_FILE, S_, "fetch-file", FETCH_FILE,
218 FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE },
220 { CHECKED_IN, D_, "href", CHECKED_IN_HREF,
221 TRUE, { NULL }, TRUE },
223 { PROP, V_, "md5-checksum", MD5_CHECKSUM,
224 TRUE, { NULL }, TRUE },
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 },
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 },
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.
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.
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
258 #define SPILLBUF_BLOCKSIZE 4096
259 #define SPILLBUF_MAXBUFFSIZE 131072
261 #define PARSE_CHUNK_SIZE 8000 /* Copied from xml.c ### Needs tuning */
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;
267 * This structure represents the information for a directory.
269 typedef struct dir_baton_t
271 struct dir_baton_t *parent_dir; /* NULL when root */
273 apr_pool_t *pool; /* Subpool for this directory */
275 /* Pointer back to our original report context. */
276 report_context_t *ctx;
278 const char *relpath; /* session relative path */
279 const char *base_name; /* Name of item "" for root */
281 /* the canonical url for this directory after updating. (received) */
284 /* The original repos_relpath of this url (via the reporter)
285 directly, or via an ancestor. */
286 const char *repos_relpath;
288 svn_revnum_t base_rev; /* base revision or NULL for Add */
290 const char *copyfrom_path; /* NULL for open */
291 svn_revnum_t copyfrom_rev; /* SVN_INVALID_REVNUM for open */
293 /* controlling dir baton - this is only created in ensure_dir_opened() */
294 svn_boolean_t dir_opened;
297 /* How many references to this directory do we still have open? */
298 apr_size_t ref_count;
300 svn_boolean_t fetch_props; /* Use PROPFIND request? */
301 svn_ra_serf__handler_t *propfind_handler;
302 apr_hash_t *remove_props;
307 * This structure represents the information for a file.
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.
313 typedef struct file_baton_t
315 dir_baton_t *parent_dir; /* The parent */
316 apr_pool_t *pool; /* Subpool for this file*/
318 const char *relpath; /* session relative path */
319 const char *base_name;
321 /* the canonical url for this directory after updating. (received) */
324 /* The original repos_relpath of this url as reported. */
325 const char *repos_relpath;
327 /* lock token, if we had one to start off with. */
328 const char *lock_token;
330 svn_revnum_t base_rev; /* SVN_INVALID_REVNUM for Add */
332 const char *copyfrom_path; /* NULL for open */
333 svn_revnum_t copyfrom_rev; /* SVN_INVALID_REVNUM for open */
335 /* controlling dir baton - this is only created in ensure_file_opened() */
336 svn_boolean_t file_opened;
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;
344 /* Has the server told us to go fetch - only valid if we had it already */
345 svn_boolean_t fetch_file;
347 /* controlling file_baton and textdelta handler */
348 svn_txdelta_window_handler_t txdelta;
351 svn_checksum_t *base_md5_checksum;
352 svn_checksum_t *final_md5_checksum;
353 svn_checksum_t *final_sha1_checksum;
355 svn_stream_t *txdelta_stream; /* Stream that feeds windows when
356 written to within txdelta*/
360 * This structure represents a single request to GET (fetch) a file with
361 * its associated Serf session/connection.
363 typedef struct fetch_ctx_t {
365 /* The handler representing this particular fetch. */
366 svn_ra_serf__handler_t *handler;
368 svn_boolean_t using_compression;
370 /* Stores the information for the file we want to fetch. */
373 /* Have we read our response headers yet? */
374 svn_boolean_t read_headers;
376 /* This flag is set when our response is aborted before we reach the
377 * end and we decide to requeue this request.
379 svn_boolean_t aborted_read;
380 apr_off_t aborted_read_size;
382 /* This is the amount of data that we have read so far. */
385 /* If we're writing this file to a stream, this will be non-NULL. */
386 svn_stream_t *result_stream;
388 /* The base-rev header */
389 const char *delta_base;
394 * The master structure for a REPORT request and response.
396 struct report_context_t {
399 svn_ra_serf__session_t *sess;
401 /* Source path and destination path */
403 const char *destination;
405 /* Our update target. */
406 const char *update_target;
408 /* What is the target revision that we want for this REPORT? */
409 svn_revnum_t target_rev;
411 /* Where are we (used while parsing) */
412 dir_baton_t *cur_dir;
413 file_baton_t *cur_file;
415 /* Have we been asked to ignore ancestry or textdeltas? */
416 svn_boolean_t ignore_ancestry;
417 svn_boolean_t text_deltas;
419 /* Do we want the server to send copyfrom args or not? */
420 svn_boolean_t send_copyfrom_args;
422 /* Is the server sending everything in one response? */
423 svn_boolean_t send_all_mode;
425 /* Is the server including properties inline for newly added
427 svn_boolean_t add_props_included;
429 /* Path -> const char *repos_relpath mapping */
430 apr_hash_t *switched_paths;
432 /* Our master update editor and baton. */
433 const svn_delta_editor_t *editor;
436 /* The file holding request body for the REPORT.
438 * ### todo: It will be better for performance to store small
439 * request bodies (like 4k) in memory and bigger bodies on disk.
441 svn_stream_t *body_template;
442 body_create_baton_t *body;
444 /* number of pending GET requests */
445 unsigned int num_active_fetches;
447 /* number of pending PROPFIND requests */
448 unsigned int num_active_propfinds;
450 /* Are we done parsing the REPORT response? */
453 /* Did we receive all data from the network? */
454 svn_boolean_t report_received;
456 /* Did we close the root directory? */
457 svn_boolean_t closed_root;
460 /* Baton for collecting REPORT body. Depending on the size this
461 work is backed by a memory buffer (via serf buckets) or by
463 struct body_create_baton_t
465 apr_pool_t *result_pool;
466 apr_size_t total_bytes;
468 apr_pool_t *scratch_pool;
470 serf_bucket_alloc_t *alloc;
471 serf_bucket_t *collect_bucket;
473 const void *all_data;
478 #define MAX_BODY_IN_RAM (256*1024)
480 /* Fold all previously collected data in a single buffer allocated in
481 RESULT_POOL and clear all intermediate state */
483 body_allocate_all(body_create_baton_t *body,
484 apr_pool_t *result_pool)
486 char *buffer = apr_pcalloc(result_pool, body->total_bytes);
490 apr_size_t remaining = body->total_bytes;
493 while (!(s = serf_bucket_read(body->collect_bucket, remaining, &data, &sz)))
495 memcpy(next, data, sz);
503 if (!SERF_BUCKET_READ_ERROR(s))
505 memcpy(next, data, sz);
508 serf_bucket_destroy(body->collect_bucket);
509 body->collect_bucket = NULL;
511 return (s != APR_EOF) ? NULL : buffer;
514 /* Noop function. Make serf take care of freeing in error situations */
515 static void serf_free_no_error(void *unfreed_baton, void *block) {}
517 /* Stream write function for body creation */
519 body_write_fn(void *baton,
523 body_create_baton_t *bcb = baton;
525 if (!bcb->scratch_pool)
526 bcb->scratch_pool = svn_pool_create(bcb->result_pool);
530 SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL,
532 svn_pool_clear(bcb->scratch_pool);
534 bcb->total_bytes += *len;
536 else if (*len + bcb->total_bytes > MAX_BODY_IN_RAM)
538 SVN_ERR(svn_io_open_unique_file3(&bcb->file, NULL, NULL,
539 svn_io_file_del_on_pool_cleanup,
540 bcb->result_pool, bcb->scratch_pool));
542 if (bcb->total_bytes)
544 const char *all = body_allocate_all(bcb, bcb->scratch_pool);
546 SVN_ERR(svn_io_file_write_full(bcb->file, all, bcb->total_bytes,
547 NULL, bcb->scratch_pool));
550 SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL,
552 bcb->total_bytes += *len;
557 bcb->alloc = serf_bucket_allocator_create(bcb->scratch_pool,
558 serf_free_no_error, NULL);
560 if (!bcb->collect_bucket)
561 bcb->collect_bucket = serf_bucket_aggregate_create(bcb->alloc);
563 serf_bucket_aggregate_append(bcb->collect_bucket,
564 serf_bucket_simple_copy_create(data, *len,
567 bcb->total_bytes += *len;
573 /* Stream close function for collecting body */
575 body_done_fn(void *baton)
577 body_create_baton_t *bcb = baton;
580 /* We need to flush the file, make it unbuffered (so that it can be
581 * zero-copied via mmap), and reset the position before attempting
582 * to deliver the file.
584 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use
585 * mmap and zero-copy the PUT body. However, on older APR versions,
586 * we can't check the buffer status; but serf will fall through and
587 * create a file bucket for us on the buffered handle.
590 SVN_ERR(svn_io_file_flush(bcb->file, bcb->scratch_pool));
591 apr_file_buffer_set(bcb->file, NULL, 0);
593 else if (bcb->collect_bucket)
594 bcb->all_data = body_allocate_all(bcb, bcb->result_pool);
596 if (bcb->scratch_pool)
597 svn_pool_destroy(bcb->scratch_pool);
603 create_dir_baton(dir_baton_t **new_dir,
604 report_context_t *ctx,
606 apr_pool_t *scratch_pool)
608 dir_baton_t *parent = ctx->cur_dir;
609 apr_pool_t *dir_pool;
613 dir_pool = svn_pool_create(parent->pool);
615 dir_pool = svn_pool_create(ctx->pool);
617 dir = apr_pcalloc(dir_pool, sizeof(*dir));
618 dir->pool = dir_pool;
623 dir->parent_dir = parent;
627 dir->relpath = parent ? svn_relpath_join(parent->relpath, name, dir_pool)
628 : apr_pstrdup(dir_pool, name);
629 dir->base_name = svn_relpath_basename(dir->relpath, NULL);
631 dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->relpath);
632 if (!dir->repos_relpath)
635 dir->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
638 dir->repos_relpath = svn_uri_skip_ancestor(ctx->sess->repos_root_str,
639 ctx->sess->session_url_str,
643 dir->base_rev = SVN_INVALID_REVNUM;
644 dir->copyfrom_rev = SVN_INVALID_REVNUM;
655 create_file_baton(file_baton_t **new_file,
656 report_context_t *ctx,
658 apr_pool_t *scratch_pool)
660 dir_baton_t *parent = ctx->cur_dir;
661 apr_pool_t *file_pool;
664 file_pool = svn_pool_create(parent->pool);
666 file = apr_pcalloc(file_pool, sizeof(*file));
667 file->pool = file_pool;
669 file->parent_dir = parent;
672 file->relpath = svn_relpath_join(parent->relpath, name, file_pool);
673 file->base_name = svn_relpath_basename(file->relpath, NULL);
675 file->repos_relpath = svn_hash_gets(ctx->switched_paths, file->relpath);
676 if (!file->repos_relpath)
677 file->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
681 file->base_rev = SVN_INVALID_REVNUM;
682 file->copyfrom_rev = SVN_INVALID_REVNUM;
686 ctx->cur_file = file;
691 /** Minimum nr. of outstanding requests needed before a new connection is
693 #define REQS_PER_CONN 8
695 /** This function creates a new connection for this serf session, but only
696 * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
697 * only one main connection open.
700 open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
702 /* For each REQS_PER_CONN outstanding requests open a new connection, with
703 * a minimum of 1 extra connection. */
704 if (sess->num_conns == 1 ||
705 ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
707 int cur = sess->num_conns;
710 sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
711 sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
713 sess->conns[cur]->last_status_code = -1;
714 sess->conns[cur]->session = sess;
715 status = serf_connection_create2(&sess->conns[cur]->conn,
718 svn_ra_serf__conn_setup,
720 svn_ra_serf__conn_closed,
724 return svn_ra_serf__wrap_err(status, NULL);
732 /* Returns best connection for fetching files/properties. */
733 static svn_ra_serf__connection_t *
734 get_best_connection(report_context_t *ctx)
736 svn_ra_serf__connection_t *conn;
739 /* Skip the first connection if the REPORT response hasn't been completely
740 received yet or if we're being told to limit our connections to
741 2 (because this could be an attempt to ensure that we do all our
742 auxiliary GETs/PROPFINDs on a single connection).
744 ### FIXME: This latter requirement (max_connections > 2) is
745 ### really just a hack to work around the fact that some update
746 ### editor implementations (such as svnrdump's dump editor)
747 ### simply can't handle the way ra_serf violates the editor v1
748 ### drive ordering requirements.
750 ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
752 if (ctx->report_received && (ctx->sess->max_connections > 2))
755 /* If there's only one available auxiliary connection to use, don't bother
756 doing all the cur_conn math -- just return that one connection. */
757 if (ctx->sess->num_conns - first_conn == 1)
759 conn = ctx->sess->conns[first_conn];
763 #if SERF_VERSION_AT_LEAST(1, 4, 0)
764 /* Often one connection is slower than others, e.g. because the server
765 process/thread has to do more work for the particular set of requests.
766 In the worst case, when REQUEST_COUNT_TO_RESUME requests are queued
767 on such a slow connection, ra_serf will completely stop sending
770 The method used here selects the connection with the least amount of
771 pending requests, thereby giving more work to lightly loaded server
774 int i, best_conn = first_conn;
775 unsigned int min = INT_MAX;
776 for (i = first_conn; i < ctx->sess->num_conns; i++)
778 serf_connection_t *sc = ctx->sess->conns[i]->conn;
779 unsigned int pending = serf_connection_pending_requests(sc);
786 conn = ctx->sess->conns[best_conn];
788 /* We don't know how many requests are pending per connection, so just
790 conn = ctx->sess->conns[ctx->sess->cur_conn];
791 ctx->sess->cur_conn++;
792 if (ctx->sess->cur_conn >= ctx->sess->num_conns)
793 ctx->sess->cur_conn = first_conn;
799 /** Helpers to open and close directories */
802 ensure_dir_opened(dir_baton_t *dir,
803 apr_pool_t *scratch_pool)
805 report_context_t *ctx = dir->ctx;
810 if (dir->base_name[0] == '\0')
813 && ctx->sess->wc_callbacks->invalidate_wc_props)
815 SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
816 ctx->sess->wc_callback_baton,
818 SVN_RA_SERF__WC_CHECKED_IN_URL, scratch_pool));
821 SVN_ERR(ctx->editor->open_root(ctx->editor_baton, dir->base_rev,
827 SVN_ERR(ensure_dir_opened(dir->parent_dir, scratch_pool));
829 if (SVN_IS_VALID_REVNUM(dir->base_rev))
831 SVN_ERR(ctx->editor->open_directory(dir->relpath,
832 dir->parent_dir->dir_baton,
839 SVN_ERR(ctx->editor->add_directory(dir->relpath,
840 dir->parent_dir->dir_baton,
848 dir->dir_opened = TRUE;
854 maybe_close_dir(dir_baton_t *dir)
856 apr_pool_t *scratch_pool = dir->pool;
857 dir_baton_t *parent = dir->parent_dir;
858 report_context_t *ctx = dir->ctx;
860 if (--dir->ref_count)
865 SVN_ERR(ensure_dir_opened(dir, dir->pool));
867 if (dir->remove_props)
869 apr_hash_index_t *hi;
871 for (hi = apr_hash_first(scratch_pool, dir->remove_props);
873 hi = apr_hash_next(hi))
875 SVN_ERR(ctx->editor->change_file_prop(dir->dir_baton,
876 apr_hash_this_key(hi),
882 SVN_ERR(dir->ctx->editor->close_directory(dir->dir_baton, scratch_pool));
884 svn_pool_destroy(dir->pool /* scratch_pool */);
887 return svn_error_trace(maybe_close_dir(parent));
893 ensure_file_opened(file_baton_t *file,
894 apr_pool_t *scratch_pool)
896 const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
898 if (file->file_opened)
901 /* Ensure our parent is open. */
902 SVN_ERR(ensure_dir_opened(file->parent_dir, scratch_pool));
904 /* Open (or add) the file. */
905 if (SVN_IS_VALID_REVNUM(file->base_rev))
907 SVN_ERR(editor->open_file(file->relpath,
908 file->parent_dir->dir_baton,
915 SVN_ERR(editor->add_file(file->relpath,
916 file->parent_dir->dir_baton,
923 file->file_opened = TRUE;
929 /** Routines called when we are fetching a file */
932 headers_fetch(serf_bucket_t *headers,
934 apr_pool_t *pool /* request pool */,
935 apr_pool_t *scratch_pool)
937 fetch_ctx_t *fetch_ctx = baton;
939 /* note that we have old VC URL */
940 if (fetch_ctx->delta_base)
942 serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
943 fetch_ctx->delta_base);
944 serf_bucket_headers_setn(headers, "Accept-Encoding",
945 "svndiff1;q=0.9,svndiff;q=0.8");
947 else if (fetch_ctx->using_compression)
949 serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
956 cancel_fetch(serf_request_t *request,
957 serf_bucket_t *response,
961 fetch_ctx_t *fetch_ctx = baton;
963 /* Uh-oh. Our connection died on us.
965 * The core ra_serf layer will requeue our request - we just need to note
966 * that we got cut off in the middle of our song.
970 /* If we already started the fetch and opened the file handle, we need
971 * to hold subsequent read() ops until we get back to where we were
972 * before the close and we can then resume the textdelta() calls.
974 if (fetch_ctx->read_headers)
976 if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
978 fetch_ctx->aborted_read = TRUE;
979 fetch_ctx->aborted_read_size = fetch_ctx->read_size;
981 fetch_ctx->read_size = 0;
987 /* We have no idea what went wrong. */
988 SVN_ERR_MALFUNCTION();
991 /* Wield the editor referenced by INFO to open (or add) the file
992 file also associated with INFO, setting properties on the file and
993 calling the editor's apply_textdelta() function on it if necessary
994 (or if FORCE_APPLY_TEXTDELTA is set).
996 Callers will probably want to also see the function that serves
997 the opposite purpose of this one, close_updated_file(). */
999 open_file_txdelta(file_baton_t *file,
1000 apr_pool_t *scratch_pool)
1002 const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
1004 SVN_ERR_ASSERT(file->txdelta == NULL);
1006 SVN_ERR(ensure_file_opened(file, scratch_pool));
1008 /* Get (maybe) a textdelta window handler for transmitting file
1010 SVN_ERR(editor->apply_textdelta(file->file_baton,
1011 svn_checksum_to_cstring(
1012 file->base_md5_checksum,
1016 &file->txdelta_baton));
1018 return SVN_NO_ERROR;
1021 /* Close the file, handling loose ends and cleanup */
1022 static svn_error_t *
1023 close_file(file_baton_t *file,
1024 apr_pool_t *scratch_pool)
1026 dir_baton_t *parent_dir = file->parent_dir;
1027 report_context_t *ctx = parent_dir->ctx;
1029 SVN_ERR(ensure_file_opened(file, scratch_pool));
1031 /* Set all of the properties we received */
1032 if (file->remove_props)
1034 apr_hash_index_t *hi;
1036 for (hi = apr_hash_first(scratch_pool, file->remove_props);
1038 hi = apr_hash_next(hi))
1040 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1041 apr_hash_this_key(hi),
1047 /* Check for lock information. */
1049 /* This works around a bug in some older versions of mod_dav_svn in that it
1050 * will not send remove-prop in the update report when a lock property
1051 * disappears when send-all is false.
1053 ### Given that we only fetch props on additions, is this really necessary?
1054 Or is it covering up old local copy bugs where we copied locks to other
1056 if (!ctx->add_props_included
1057 && file->lock_token && !file->found_lock_prop
1058 && SVN_IS_VALID_REVNUM(file->base_rev) /* file_is_added */)
1060 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1061 SVN_PROP_ENTRY_LOCK_TOKEN,
1068 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1069 SVN_RA_SERF__WC_CHECKED_IN_URL,
1070 svn_string_create(file->url,
1075 /* Close the file via the editor. */
1076 SVN_ERR(ctx->editor->close_file(file->file_baton,
1077 svn_checksum_to_cstring(
1078 file->final_md5_checksum,
1082 svn_pool_destroy(file->pool);
1084 SVN_ERR(maybe_close_dir(parent_dir)); /* Remove reference */
1086 return SVN_NO_ERROR;
1089 /* Implements svn_ra_serf__response_handler_t */
1090 static svn_error_t *
1091 handle_fetch(serf_request_t *request,
1092 serf_bucket_t *response,
1093 void *handler_baton,
1098 apr_status_t status;
1099 fetch_ctx_t *fetch_ctx = handler_baton;
1100 file_baton_t *file = fetch_ctx->file;
1102 /* ### new field. make sure we didn't miss some initialization. */
1103 SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1105 if (!fetch_ctx->read_headers)
1107 serf_bucket_t *hdrs;
1110 /* If the error code wasn't 200, something went wrong. Don't use the
1111 * returned data as its probably an error message. Just bail out instead.
1113 if (fetch_ctx->handler->sline.code != 200)
1115 fetch_ctx->handler->discard_body = TRUE;
1116 return SVN_NO_ERROR; /* Will return an error in the DONE handler */
1119 hdrs = serf_bucket_response_get_headers(response);
1120 val = serf_bucket_headers_get(hdrs, "Content-Type");
1122 if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
1124 fetch_ctx->result_stream =
1125 svn_txdelta_parse_svndiff(file->txdelta,
1126 file->txdelta_baton,
1129 /* Validate the delta base claimed by the server matches
1130 what we asked for! */
1131 val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
1132 if (val && fetch_ctx->delta_base == NULL)
1134 /* We recieved response with delta base header while we didn't
1135 requested it -- report it as error. */
1136 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1137 _("GET request returned unexpected "
1138 "delta base: %s"), val);
1140 else if (val && (strcmp(val, fetch_ctx->delta_base) != 0))
1142 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1143 _("GET request returned unexpected "
1144 "delta base: %s"), val);
1149 fetch_ctx->result_stream = NULL;
1152 fetch_ctx->read_headers = TRUE;
1157 svn_txdelta_window_t delta_window = { 0 };
1158 svn_txdelta_op_t delta_op;
1159 svn_string_t window_data;
1161 status = serf_bucket_read(response, 8000, &data, &len);
1162 if (SERF_BUCKET_READ_ERROR(status))
1164 return svn_ra_serf__wrap_err(status, NULL);
1167 fetch_ctx->read_size += len;
1169 if (fetch_ctx->aborted_read)
1172 /* We haven't caught up to where we were before. */
1173 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1175 /* Eek. What did the file shrink or something? */
1176 if (APR_STATUS_IS_EOF(status))
1178 SVN_ERR_MALFUNCTION();
1181 /* Skip on to the next iteration of this loop. */
1182 if (status /* includes EAGAIN */)
1183 return svn_ra_serf__wrap_err(status, NULL);
1188 /* Woo-hoo. We're back. */
1189 fetch_ctx->aborted_read = FALSE;
1191 /* Update data and len to just provide the new data. */
1192 skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
1194 len -= (apr_size_t)skip;
1197 if (fetch_ctx->result_stream)
1198 SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &len));
1200 /* otherwise, manually construct the text delta window. */
1203 window_data.data = data;
1204 window_data.len = len;
1206 delta_op.action_code = svn_txdelta_new;
1207 delta_op.offset = 0;
1208 delta_op.length = len;
1210 delta_window.tview_len = len;
1211 delta_window.num_ops = 1;
1212 delta_window.ops = &delta_op;
1213 delta_window.new_data = &window_data;
1215 /* write to the file located in the info. */
1216 SVN_ERR(file->txdelta(&delta_window, file->txdelta_baton));
1219 if (APR_STATUS_IS_EOF(status))
1221 if (fetch_ctx->result_stream)
1222 SVN_ERR(svn_stream_close(fetch_ctx->result_stream));
1224 SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
1227 /* Report EOF, EEAGAIN and other special errors to serf */
1229 return svn_ra_serf__wrap_err(status, NULL);
1233 /* --------------------------------------------------------- */
1235 /** Wrappers around our various property walkers **/
1237 /* Implements svn_ra_serf__prop_func */
1238 static svn_error_t *
1239 set_file_props(void *baton,
1243 const svn_string_t *val,
1244 apr_pool_t *scratch_pool)
1246 file_baton_t *file = baton;
1247 report_context_t *ctx = file->parent_dir->ctx;
1248 const char *prop_name;
1250 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
1254 /* This works around a bug in some older versions of
1255 * mod_dav_svn in that it will not send remove-prop in the update
1256 * report when a lock property disappears when send-all is false.
1258 * Therefore, we'll try to look at our properties and see if there's
1259 * an active lock. If not, then we'll assume there isn't a lock
1262 /* assert(!ctx->add_props_included); // Or we wouldn't be here */
1263 if (file->lock_token
1264 && !file->found_lock_prop
1266 && strcmp(ns, "DAV:") == 0
1267 && strcmp(name, "lockdiscovery") == 0)
1270 new_lock = apr_pstrdup(scratch_pool, val->data);
1271 apr_collapse_spaces(new_lock, new_lock);
1273 if (new_lock[0] != '\0')
1274 file->found_lock_prop = TRUE;
1277 return SVN_NO_ERROR;
1280 SVN_ERR(ensure_file_opened(file, scratch_pool));
1282 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1286 return SVN_NO_ERROR;
1289 /* Implements svn_ra_serf__response_done_delegate_t */
1290 static svn_error_t *
1291 file_props_done(serf_request_t *request,
1293 apr_pool_t *scratch_pool)
1295 file_baton_t *file = baton;
1296 svn_ra_serf__handler_t *handler = file->propfind_handler;
1298 if (handler->server_error)
1299 return svn_error_trace(svn_ra_serf__server_error_create(handler,
1302 if (handler->sline.code != 207)
1303 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1305 file->parent_dir->ctx->num_active_propfinds--;
1307 file->fetch_props = FALSE;
1309 if (file->fetch_file)
1310 return SVN_NO_ERROR; /* Still processing file request */
1312 /* Closing the file will automatically deliver the propfind props.
1314 * Note that closing the directory may dispose the pool containing the
1315 * handler, which is only a valid operation in this callback, as only
1316 * after this callback our serf plumbing assumes the request is done. */
1318 return svn_error_trace(close_file(file, scratch_pool));
1321 static svn_error_t *
1322 file_fetch_done(serf_request_t *request,
1324 apr_pool_t *scratch_pool)
1326 fetch_ctx_t *fetch_ctx = baton;
1327 file_baton_t *file = fetch_ctx->file;
1328 svn_ra_serf__handler_t *handler = fetch_ctx->handler;
1330 if (handler->server_error)
1331 return svn_error_trace(svn_ra_serf__server_error_create(handler,
1334 if (handler->sline.code != 200)
1335 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1337 file->parent_dir->ctx->num_active_fetches--;
1339 file->fetch_file = FALSE;
1341 if (file->fetch_props)
1342 return SVN_NO_ERROR; /* Still processing PROPFIND request */
1344 /* Closing the file will automatically deliver the propfind props.
1346 * Note that closing the directory may dispose the pool containing the
1347 * handler, fetch_ctx, etc. which is only a valid operation in this
1348 * callback, as only after this callback our serf plumbing assumes the
1349 * request is done. */
1350 return svn_error_trace(close_file(file, scratch_pool));
1353 /* Initiates additional requests needed for a file when not in "send-all" mode.
1355 static svn_error_t *
1356 fetch_for_file(file_baton_t *file,
1357 apr_pool_t *scratch_pool)
1359 report_context_t *ctx = file->parent_dir->ctx;
1360 svn_ra_serf__connection_t *conn;
1361 svn_ra_serf__handler_t *handler;
1363 /* Open extra connections if we have enough requests to send. */
1364 if (ctx->sess->num_conns < ctx->sess->max_connections)
1365 SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
1366 ctx->num_active_propfinds));
1368 /* What connection should we go on? */
1369 conn = get_best_connection(ctx);
1371 /* Note that we (still) use conn for both requests.. Should we send
1372 them out on different connections? */
1374 if (file->fetch_file)
1376 SVN_ERR(open_file_txdelta(file, scratch_pool));
1378 if (!ctx->text_deltas
1379 || file->txdelta == svn_delta_noop_window_handler)
1381 SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
1382 file->fetch_file = FALSE;
1385 if (file->fetch_file
1386 && file->final_sha1_checksum
1387 && ctx->sess->wc_callbacks->get_wc_contents)
1390 svn_stream_t *cached_contents = NULL;
1392 err = ctx->sess->wc_callbacks->get_wc_contents(
1393 ctx->sess->wc_callback_baton,
1395 file->final_sha1_checksum,
1398 if (err || !cached_contents)
1399 svn_error_clear(err); /* ### Can we return some/most errors? */
1402 /* ### For debugging purposes we could validate the md5 here,
1403 but our implementations in libsvn_client already do that
1405 SVN_ERR(svn_txdelta_send_stream(cached_contents,
1407 file->txdelta_baton,
1408 NULL, scratch_pool));
1409 SVN_ERR(svn_stream_close(cached_contents));
1410 file->fetch_file = FALSE;
1414 if (file->fetch_file)
1416 fetch_ctx_t *fetch_ctx;
1418 /* Let's fetch the file with a GET request... */
1419 SVN_ERR_ASSERT(file->url && file->repos_relpath);
1421 /* Otherwise, we use a GET request for the file's contents. */
1423 fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx));
1424 fetch_ctx->file = file;
1425 fetch_ctx->using_compression = ctx->sess->using_compression;
1427 /* Can we somehow get away with just obtaining a DIFF? */
1428 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
1430 /* If this file is switched vs the editor root we should provide
1431 its real url instead of the one calculated from the session root.
1433 if (SVN_IS_VALID_REVNUM(file->base_rev))
1435 fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
1436 ctx->sess->rev_root_stub,
1438 svn_path_uri_encode(
1439 file->repos_relpath,
1442 else if (file->copyfrom_path)
1444 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(file->copyfrom_rev));
1446 fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
1447 ctx->sess->rev_root_stub,
1449 svn_path_uri_encode(
1450 file->copyfrom_path+1,
1454 else if (ctx->sess->wc_callbacks->get_wc_prop)
1456 /* If we have a WC, we might be able to dive all the way into the WC
1457 * to get the previous URL so we can do a differential GET with the
1460 const svn_string_t *value = NULL;
1461 SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
1462 ctx->sess->wc_callback_baton,
1464 SVN_RA_SERF__WC_CHECKED_IN_URL,
1465 &value, scratch_pool));
1467 fetch_ctx->delta_base = value
1468 ? apr_pstrdup(file->pool, value->data)
1472 handler = svn_ra_serf__create_handler(ctx->sess, file->pool);
1474 handler->method = "GET";
1475 handler->path = file->url;
1477 handler->conn = conn; /* Explicit scheduling */
1479 handler->custom_accept_encoding = TRUE;
1480 handler->no_dav_headers = TRUE;
1481 handler->header_delegate = headers_fetch;
1482 handler->header_delegate_baton = fetch_ctx;
1484 handler->response_handler = handle_fetch;
1485 handler->response_baton = fetch_ctx;
1487 handler->response_error = cancel_fetch;
1488 handler->response_error_baton = fetch_ctx;
1490 handler->done_delegate = file_fetch_done;
1491 handler->done_delegate_baton = fetch_ctx;
1493 fetch_ctx->handler = handler;
1495 svn_ra_serf__request_create(handler);
1497 ctx->num_active_fetches++;
1501 /* If needed, create the PROPFIND to retrieve the file's properties. */
1502 if (file->fetch_props)
1504 SVN_ERR(svn_ra_serf__create_propfind_handler(&file->propfind_handler,
1505 ctx->sess, file->url,
1506 ctx->target_rev, "0",
1508 set_file_props, file,
1510 file->propfind_handler->conn = conn; /* Explicit scheduling */
1512 file->propfind_handler->done_delegate = file_props_done;
1513 file->propfind_handler->done_delegate_baton = file;
1515 /* Create a serf request for the PROPFIND. */
1516 svn_ra_serf__request_create(file->propfind_handler);
1518 ctx->num_active_propfinds++;
1521 if (file->fetch_props || file->fetch_file)
1522 return SVN_NO_ERROR;
1525 /* Somehow we are done; probably via the local cache.
1526 Close the file and release memory, etc. */
1528 return svn_error_trace(close_file(file, scratch_pool));
1531 /* Implements svn_ra_serf__prop_func */
1532 static svn_error_t *
1533 set_dir_prop(void *baton,
1537 const svn_string_t *val,
1538 apr_pool_t *scratch_pool)
1540 dir_baton_t *dir = baton;
1541 report_context_t *ctx = dir->ctx;
1542 const char *prop_name;
1544 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
1545 if (prop_name == NULL)
1546 return SVN_NO_ERROR;
1548 SVN_ERR(ensure_dir_opened(dir, scratch_pool));
1550 SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
1553 return SVN_NO_ERROR;
1556 /* Implements svn_ra_serf__response_done_delegate_t */
1557 static svn_error_t *
1558 dir_props_done(serf_request_t *request,
1560 apr_pool_t *scratch_pool)
1562 dir_baton_t *dir = baton;
1563 svn_ra_serf__handler_t *handler = dir->propfind_handler;
1565 if (handler->server_error)
1566 return svn_ra_serf__server_error_create(handler, scratch_pool);
1568 if (handler->sline.code != 207)
1569 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1571 dir->ctx->num_active_propfinds--;
1573 /* Closing the directory will automatically deliver the propfind props.
1575 * Note that closing the directory may dispose the pool containing the
1576 * handler, which is only a valid operation in this callback, as after
1577 * this callback serf assumes the request is done. */
1579 return svn_error_trace(maybe_close_dir(dir));
1582 /* Initiates additional requests needed for a directory when not in "send-all"
1584 static svn_error_t *
1585 fetch_for_dir(dir_baton_t *dir,
1586 apr_pool_t *scratch)
1588 report_context_t *ctx = dir->ctx;
1589 svn_ra_serf__connection_t *conn;
1591 /* Open extra connections if we have enough requests to send. */
1592 if (ctx->sess->num_conns < ctx->sess->max_connections)
1593 SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
1594 ctx->num_active_propfinds));
1596 /* What connection should we go on? */
1597 conn = get_best_connection(ctx);
1599 /* If needed, create the PROPFIND to retrieve the file's properties. */
1600 if (dir->fetch_props)
1602 SVN_ERR(svn_ra_serf__create_propfind_handler(&dir->propfind_handler,
1603 ctx->sess, dir->url,
1604 ctx->target_rev, "0",
1609 dir->propfind_handler->conn = conn;
1610 dir->propfind_handler->done_delegate = dir_props_done;
1611 dir->propfind_handler->done_delegate_baton = dir;
1613 /* Create a serf request for the PROPFIND. */
1614 svn_ra_serf__request_create(dir->propfind_handler);
1616 ctx->num_active_propfinds++;
1619 SVN_ERR_MALFUNCTION();
1621 return SVN_NO_ERROR;
1625 /** XML callbacks for our update-report response parsing */
1627 /* Conforms to svn_ra_serf__xml_opened_t */
1628 static svn_error_t *
1629 update_opened(svn_ra_serf__xml_estate_t *xes,
1632 const svn_ra_serf__dav_props_t *tag,
1633 apr_pool_t *scratch_pool)
1635 report_context_t *ctx = baton;
1638 switch (entered_state)
1644 attrs = svn_ra_serf__xml_gather_since(xes, UPDATE_REPORT);
1645 val = svn_hash_gets(attrs, "inline-props");
1647 if (val && (strcmp(val, "true") == 0))
1648 ctx->add_props_included = TRUE;
1650 val = svn_hash_gets(attrs, "send-all");
1652 if (val && (strcmp(val, "true") == 0))
1654 ctx->send_all_mode = TRUE;
1656 /* All properties are included in send-all mode. */
1657 ctx->add_props_included = TRUE;
1667 attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1669 name = svn_hash_gets(attrs, "name");
1673 SVN_ERR(create_dir_baton(&dir, ctx, name, scratch_pool));
1675 if (entered_state == OPEN_DIR)
1677 apr_int64_t base_rev;
1679 SVN_ERR(svn_cstring_atoi64(&base_rev,
1680 svn_hash_gets(attrs, "rev")));
1681 dir->base_rev = (svn_revnum_t)base_rev;
1685 dir->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
1687 if (dir->copyfrom_path)
1689 apr_int64_t copyfrom_rev;
1690 const char *copyfrom_rev_str;
1691 dir->copyfrom_path = svn_fspath__canonicalize(
1695 copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
1697 if (!copyfrom_rev_str)
1698 return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1700 _("Missing '%s' attribute"),
1703 SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str));
1705 dir->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
1708 if (! ctx->add_props_included)
1709 dir->fetch_props = TRUE;
1718 attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1720 SVN_ERR(create_file_baton(&file, ctx, svn_hash_gets(attrs, "name"),
1723 if (entered_state == OPEN_FILE)
1725 apr_int64_t base_rev;
1727 SVN_ERR(svn_cstring_atoi64(&base_rev,
1728 svn_hash_gets(attrs, "rev")));
1729 file->base_rev = (svn_revnum_t)base_rev;
1733 const char *sha1_checksum;
1734 file->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
1736 if (file->copyfrom_path)
1738 apr_int64_t copyfrom_rev;
1739 const char *copyfrom_rev_str;
1741 file->copyfrom_path = svn_fspath__canonicalize(
1742 file->copyfrom_path,
1745 copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
1747 if (!copyfrom_rev_str)
1748 return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1750 _("Missing '%s' attribute"),
1753 SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str));
1755 file->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
1758 sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
1761 SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
1767 /* If the server isn't in "send-all" mode, we should expect to
1768 fetch contents for added files. */
1769 if (! ctx->send_all_mode)
1770 file->fetch_file = TRUE;
1772 /* If the server isn't included properties for added items,
1773 we'll need to fetch them ourselves. */
1774 if (! ctx->add_props_included)
1775 file->fetch_props = TRUE;
1782 file_baton_t *file = ctx->cur_file;
1783 const char *base_checksum;
1785 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
1786 addition to <fetch-file>s and such) when *not* in
1787 "send-all" mode. As a client, we're smart enough to know
1788 that's wrong, so we'll just ignore these tags. */
1789 if (! ctx->send_all_mode)
1792 file->fetch_file = FALSE;
1794 attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1795 base_checksum = svn_hash_gets(attrs, "base-checksum");
1798 SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
1799 svn_checksum_md5, base_checksum,
1802 SVN_ERR(open_file_txdelta(ctx->cur_file, scratch_pool));
1804 if (ctx->cur_file->txdelta != svn_delta_noop_window_handler)
1806 svn_stream_t *decoder;
1808 decoder = svn_txdelta_parse_svndiff(file->txdelta,
1809 file->txdelta_baton,
1810 TRUE /* error early close*/,
1813 file->txdelta_stream = svn_base64_decode(decoder, file->pool);
1820 /* Subversion <= 1.6 servers will return a fetch-props element on
1821 open-file and open-dir when non entry props were changed in
1822 !send-all mode. In turn we fetch the full set of properties
1823 and send all of those as *changes* to the editor. So these
1824 editors have to be aware that they receive-non property changes.
1825 (In case of incomplete directories they have to be aware anyway)
1827 In r1063337 this behavior was changed in mod_dav_svn to always
1828 send property changes inline in these cases. (See issue #3657)
1830 Note that before that change the property changes to the last_*
1831 entry props were already inlined via specific xml elements. */
1833 ctx->cur_file->fetch_props = TRUE;
1834 else if (ctx->cur_dir)
1835 ctx->cur_dir->fetch_props = TRUE;
1840 return SVN_NO_ERROR;
1845 /* Conforms to svn_ra_serf__xml_closed_t */
1846 static svn_error_t *
1847 update_closed(svn_ra_serf__xml_estate_t *xes,
1850 const svn_string_t *cdata,
1852 apr_pool_t *scratch_pool)
1854 report_context_t *ctx = baton;
1856 switch (leaving_state)
1861 case TARGET_REVISION:
1863 const char *revstr = svn_hash_gets(attrs, "rev");
1866 SVN_ERR(svn_cstring_atoi64(&rev, revstr));
1868 SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
1874 case CHECKED_IN_HREF:
1876 ctx->cur_file->url = apr_pstrdup(ctx->cur_file->pool, cdata->data);
1878 ctx->cur_dir->url = apr_pstrdup(ctx->cur_dir->pool, cdata->data);
1884 const char *name = svn_hash_gets(attrs, "name");
1885 const char *encoding;
1886 const svn_string_t *value;
1888 if (leaving_state == REMOVE_PROP)
1890 else if ((encoding = svn_hash_gets(attrs, "encoding")))
1892 if (strcmp(encoding, "base64") != 0)
1893 return svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL,
1894 _("Got unrecognized encoding '%s'"),
1897 value = svn_base64_decode_string(cdata, scratch_pool);
1904 file_baton_t *file = ctx->cur_file;
1907 || ctx->add_props_included
1908 || SVN_IS_VALID_REVNUM(file->base_rev))
1910 SVN_ERR(ensure_file_opened(file, scratch_pool));
1912 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1919 if (!file->remove_props)
1920 file->remove_props = apr_hash_make(file->pool);
1922 svn_hash_sets(file->remove_props,
1923 apr_pstrdup(file->pool, name),
1929 dir_baton_t *dir = ctx->cur_dir;
1932 || ctx->add_props_included
1933 || SVN_IS_VALID_REVNUM(dir->base_rev))
1935 SVN_ERR(ensure_dir_opened(dir, scratch_pool));
1937 SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
1944 if (!dir->remove_props)
1945 dir->remove_props = apr_hash_make(dir->pool);
1947 svn_hash_sets(dir->remove_props,
1948 apr_pstrdup(dir->pool, name),
1958 dir_baton_t *dir = ctx->cur_dir;
1959 ctx->cur_dir = ctx->cur_dir->parent_dir;
1961 if (dir->fetch_props && ! dir->url)
1963 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1964 _("The REPORT response did not "
1965 "include the requested checked-in "
1969 if (!dir->fetch_props)
1971 SVN_ERR(maybe_close_dir(dir));
1972 break; /* dir potentially no longer valid */
1976 /* Otherwise, if the server is *not* in "send-all" mode, we
1977 are at a point where we can queue up the PROPFIND request */
1978 SVN_ERR(fetch_for_dir(dir, scratch_pool));
1986 file_baton_t *file = ctx->cur_file;
1988 ctx->cur_file = NULL;
1989 /* go fetch info->name from DAV:checked-in */
1991 if ((file->fetch_file || file->fetch_props) && ! file->url)
1993 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1994 _("The REPORT response did not "
1995 "include the requested checked-in "
1999 /* If the server is in "send-all" mode or didn't get further work,
2000 we can now close the file */
2001 if (! file->fetch_file && ! file->fetch_props)
2003 SVN_ERR(close_file(file, scratch_pool));
2004 break; /* file is no longer valid */
2008 /* Otherwise, if the server is *not* in "send-all" mode, we
2009 should be at a point where we can queue up any auxiliary
2010 content-fetching requests. */
2011 SVN_ERR(fetch_for_file(file, scratch_pool));
2017 SVN_ERR(svn_checksum_parse_hex(&ctx->cur_file->final_md5_checksum,
2020 ctx->cur_file->pool));
2025 file_baton_t *file = ctx->cur_file;
2026 const char *base_checksum = svn_hash_gets(attrs, "base-checksum");
2027 const char *sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
2030 SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
2031 svn_checksum_md5, base_checksum,
2034 /* Property is duplicated between add-file and fetch-file */
2035 if (sha1_checksum && !file->final_sha1_checksum)
2036 SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
2041 /* Some 0.3x mod_dav_svn wrote both txdelta and fetch-file
2042 elements in send-all mode. (See neon for history) */
2043 if (! ctx->send_all_mode)
2044 file->fetch_file = TRUE;
2050 const char *name = svn_hash_gets(attrs, "name");
2052 apr_int64_t delete_rev;
2054 SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
2056 revstr = svn_hash_gets(attrs, "rev");
2059 SVN_ERR(svn_cstring_atoi64(&delete_rev, revstr));
2061 delete_rev = SVN_INVALID_REVNUM;
2063 SVN_ERR(ctx->editor->delete_entry(
2064 svn_relpath_join(ctx->cur_dir->relpath,
2067 (svn_revnum_t)delete_rev,
2068 ctx->cur_dir->dir_baton,
2075 const char *name = svn_hash_gets(attrs, "name");
2077 SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
2079 SVN_ERR(ctx->editor->absent_directory(
2080 svn_relpath_join(ctx->cur_dir->relpath,
2081 name, scratch_pool),
2082 ctx->cur_dir->dir_baton,
2088 const char *name = svn_hash_gets(attrs, "name");
2090 SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
2092 SVN_ERR(ctx->editor->absent_file(
2093 svn_relpath_join(ctx->cur_dir->relpath,
2094 name, scratch_pool),
2095 ctx->cur_dir->dir_baton,
2102 file_baton_t *file = ctx->cur_file;
2104 if (file->txdelta_stream)
2106 SVN_ERR(svn_stream_close(file->txdelta_stream));
2107 file->txdelta_stream = NULL;
2114 case CREATOR_DISPLAYNAME:
2116 /* Subversion <= 1.6 servers would return a fetch-props element on
2117 open-file and open-dir when non entry props were changed in
2118 !send-all mode. In turn we fetch the full set of properties and
2119 send those as *changes* to the editor. So these editors have to
2120 be aware that they receive non property changes.
2121 (In case of incomplete directories they have to be aware anyway)
2123 In that case the last_* entry props are posted as 3 specific xml
2124 elements, which we handle here.
2126 In r1063337 this behavior was changed in mod_dav_svn to always
2127 send property changes inline in these cases. (See issue #3657)
2130 const char *propname;
2133 SVN_ERR(ensure_file_opened(ctx->cur_file, scratch_pool));
2134 else if (ctx->cur_dir)
2135 SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
2139 switch (leaving_state)
2142 propname = SVN_PROP_ENTRY_COMMITTED_REV;
2145 propname = SVN_PROP_ENTRY_COMMITTED_DATE;
2147 case CREATOR_DISPLAYNAME:
2148 propname = SVN_PROP_ENTRY_LAST_AUTHOR;
2151 SVN_ERR_MALFUNCTION(); /* Impossible to reach */
2155 SVN_ERR(ctx->editor->change_file_prop(ctx->cur_file->file_baton,
2159 SVN_ERR(ctx->editor->change_dir_prop(ctx->cur_dir->dir_baton,
2166 return SVN_NO_ERROR;
2170 /* Conforms to svn_ra_serf__xml_cdata_t */
2171 static svn_error_t *
2172 update_cdata(svn_ra_serf__xml_estate_t *xes,
2177 apr_pool_t *scratch_pool)
2179 report_context_t *ctx = baton;
2181 if (current_state == TXDELTA && ctx->cur_file
2182 && ctx->cur_file->txdelta_stream)
2184 SVN_ERR(svn_stream_write(ctx->cur_file->txdelta_stream, data, &len));
2187 return SVN_NO_ERROR;
2191 /** Editor callbacks given to callers to create request body */
2193 /* Helper to create simple xml tag without attributes. */
2195 make_simple_xml_tag(svn_stringbuf_t **buf_p,
2196 const char *tagname,
2200 svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname,
2202 svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
2203 svn_xml_make_close_tag(buf_p, pool, tagname);
2206 static svn_error_t *
2207 set_path(void *report_baton,
2209 svn_revnum_t revision,
2211 svn_boolean_t start_empty,
2212 const char *lock_token,
2215 report_context_t *report = report_baton;
2216 svn_stringbuf_t *buf = NULL;
2218 svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2219 "rev", apr_ltoa(pool, revision),
2220 "lock-token", lock_token,
2221 "depth", svn_depth_to_word(depth),
2222 "start-empty", start_empty ? "true" : NULL,
2224 svn_xml_escape_cdata_cstring(&buf, path, pool);
2225 svn_xml_make_close_tag(&buf, pool, "S:entry");
2227 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2229 return SVN_NO_ERROR;
2232 static svn_error_t *
2233 delete_path(void *report_baton,
2237 report_context_t *report = report_baton;
2238 svn_stringbuf_t *buf = NULL;
2240 make_simple_xml_tag(&buf, "S:missing", path, pool);
2242 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2244 return SVN_NO_ERROR;
2247 static svn_error_t *
2248 link_path(void *report_baton,
2251 svn_revnum_t revision,
2253 svn_boolean_t start_empty,
2254 const char *lock_token,
2257 report_context_t *report = report_baton;
2258 const char *link, *report_target;
2260 apr_status_t status;
2261 svn_stringbuf_t *buf = NULL;
2263 /* We need to pass in the baseline relative path.
2265 * TODO Confirm that it's on the same server?
2267 status = apr_uri_parse(pool, url, &uri);
2270 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2271 _("Unable to parse URL '%s'"), url);
2274 SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, pool));
2275 SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, pool));
2277 link = apr_pstrcat(pool, "/", link, SVN_VA_NULL);
2279 svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2280 "rev", apr_ltoa(pool, revision),
2281 "lock-token", lock_token,
2282 "depth", svn_depth_to_word(depth),
2284 "start-empty", start_empty ? "true" : NULL,
2286 svn_xml_escape_cdata_cstring(&buf, path, pool);
2287 svn_xml_make_close_tag(&buf, pool, "S:entry");
2289 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2291 /* Store the switch roots to allow generating repos_relpaths from just
2292 the working copy paths. (Needed for HTTPv2) */
2293 path = apr_pstrdup(report->pool, path);
2294 link = apr_pstrdup(report->pool, link + 1);
2295 svn_hash_sets(report->switched_paths, path, link);
2297 if (!path[0] && report->update_target[0])
2299 /* The update root is switched. Make sure we store it the way
2300 we expect it to find */
2301 svn_hash_sets(report->switched_paths, report->update_target, link);
2307 /* Serf callback to create update request body bucket.
2308 Implements svn_ra_serf__request_body_delegate_t */
2309 static svn_error_t *
2310 create_update_report_body(serf_bucket_t **body_bkt,
2312 serf_bucket_alloc_t *alloc,
2313 apr_pool_t *pool /* request pool */,
2314 apr_pool_t *scratch_pool)
2316 report_context_t *report = baton;
2317 body_create_baton_t *body = report->body;
2324 SVN_ERR(svn_io_file_seek(body->file, APR_SET, &offset, pool));
2326 *body_bkt = serf_bucket_file_create(report->body->file, alloc);
2330 *body_bkt = serf_bucket_simple_create(body->all_data,
2335 return SVN_NO_ERROR;
2338 /* Serf callback to setup update request headers. */
2339 static svn_error_t *
2340 setup_update_report_headers(serf_bucket_t *headers,
2342 apr_pool_t *pool /* request pool */,
2343 apr_pool_t *scratch_pool)
2345 report_context_t *report = baton;
2347 if (report->sess->using_compression)
2349 serf_bucket_headers_setn(headers, "Accept-Encoding",
2350 "gzip,svndiff1;q=0.9,svndiff;q=0.8");
2354 serf_bucket_headers_setn(headers, "Accept-Encoding",
2355 "svndiff1;q=0.9,svndiff;q=0.8");
2358 return SVN_NO_ERROR;
2361 /* Baton for update_delay_handler */
2362 typedef struct update_delay_baton_t
2364 report_context_t *report;
2365 svn_spillbuf_t *spillbuf;
2366 svn_ra_serf__response_handler_t inner_handler;
2367 void *inner_handler_baton;
2368 } update_delay_baton_t;
2370 /* Helper for update_delay_handler() and process_pending() to
2371 call UDB->INNER_HANDLER with buffer pointed by DATA. */
2372 static svn_error_t *
2373 process_buffer(update_delay_baton_t *udb,
2374 serf_request_t *request,
2377 svn_boolean_t at_eof,
2378 serf_bucket_alloc_t *alloc,
2381 serf_bucket_t *tmp_bucket;
2384 /* ### This code (and the eagain bucket code) can probably be
2385 ### simplified by using a bit of aggregate bucket magic.
2386 ### See mail from Ivan to dev@s.a.o. */
2389 tmp_bucket = serf_bucket_simple_create(data, len, NULL, NULL,
2394 tmp_bucket = svn_ra_serf__create_bucket_with_eagain(data, len,
2398 /* If not at EOF create a bucket that finishes with EAGAIN, otherwise
2399 use a standard bucket with default EOF handling */
2400 err = udb->inner_handler(request, tmp_bucket,
2401 udb->inner_handler_baton, pool);
2403 /* And free the bucket explicitly to avoid growing request allocator
2404 storage (in a loop) */
2405 serf_bucket_destroy(tmp_bucket);
2407 return svn_error_trace(err);
2411 /* Delaying wrapping reponse handler, to avoid creating too many
2412 requests to deliver efficiently */
2413 static svn_error_t *
2414 update_delay_handler(serf_request_t *request,
2415 serf_bucket_t *response,
2416 void *handler_baton,
2417 apr_pool_t *scratch_pool)
2419 update_delay_baton_t *udb = handler_baton;
2420 apr_status_t status;
2421 apr_pool_t *iterpool = NULL;
2423 if (! udb->spillbuf)
2425 if (udb->report->send_all_mode)
2427 /* Easy out... We only have one request, so avoid everything and just
2428 call the inner handler.
2430 We will always get in the loop (below) on the first chunk, as only
2431 the server can get us in true send-all mode */
2433 return svn_error_trace(udb->inner_handler(request, response,
2434 udb->inner_handler_baton,
2438 while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
2439 < REQUEST_COUNT_TO_RESUME)
2443 svn_boolean_t at_eof = FALSE;
2446 status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
2447 if (SERF_BUCKET_READ_ERROR(status))
2448 return svn_ra_serf__wrap_err(status, NULL);
2449 else if (APR_STATUS_IS_EOF(status))
2450 udb->report->report_received = at_eof = TRUE;
2453 iterpool = svn_pool_create(scratch_pool);
2455 svn_pool_clear(iterpool);
2457 if (len == 0 && !at_eof)
2458 return svn_ra_serf__wrap_err(status, NULL);
2460 err = process_buffer(udb, request, data, len, at_eof,
2461 serf_request_get_alloc(request),
2464 if (err && SERF_BUCKET_READ_ERROR(err->apr_err))
2465 return svn_error_trace(err);
2466 else if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
2468 svn_error_clear(err); /* Throttling is working ok */
2470 else if (err && (APR_STATUS_IS_EOF(err->apr_err)))
2472 svn_pool_destroy(iterpool);
2473 return svn_error_trace(err); /* No buffering was necessary */
2477 /* SERF_ERROR_WAIT_CONN should be impossible? */
2478 return svn_error_trace(err);
2482 /* Let's start using the spill infrastructure */
2483 udb->spillbuf = svn_spillbuf__create(SPILLBUF_BLOCKSIZE,
2484 SPILLBUF_MAXBUFFSIZE,
2488 /* Read everything we can to a spillbuffer */
2494 /* ### What blocksize should we pass? */
2495 status = serf_bucket_read(response, 8*PARSE_CHUNK_SIZE, &data, &len);
2497 if (!SERF_BUCKET_READ_ERROR(status))
2498 SVN_ERR(svn_spillbuf__write(udb->spillbuf, data, len, scratch_pool));
2500 while (status == APR_SUCCESS);
2502 if (APR_STATUS_IS_EOF(status))
2503 udb->report->report_received = TRUE;
2505 /* We handle feeding the data from the main context loop, which will be right
2506 after processing the pending data */
2509 return svn_ra_serf__wrap_err(status, NULL);
2511 return SVN_NO_ERROR;
2514 /* Process pending data from the update report, if any */
2515 static svn_error_t *
2516 process_pending(update_delay_baton_t *udb,
2517 apr_pool_t *scratch_pool)
2519 apr_pool_t *iterpool = NULL;
2520 serf_bucket_alloc_t *alloc = NULL;
2522 while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
2523 < REQUEST_COUNT_TO_RESUME)
2527 svn_boolean_t at_eof;
2532 iterpool = svn_pool_create(scratch_pool);
2533 alloc = serf_bucket_allocator_create(scratch_pool, NULL, NULL);
2536 svn_pool_clear(iterpool);
2538 SVN_ERR(svn_spillbuf__read(&data, &len, udb->spillbuf, iterpool));
2540 if (data == NULL && !udb->report->report_received)
2542 else if (data == NULL)
2547 err = process_buffer(udb, NULL /* allowed? */, data, len,
2548 at_eof, alloc, iterpool);
2550 if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
2552 svn_error_clear(err); /* Throttling is working */
2554 else if (err && APR_STATUS_IS_EOF(err->apr_err))
2556 svn_error_clear(err);
2558 svn_pool_destroy(iterpool);
2559 udb->spillbuf = NULL;
2560 return SVN_NO_ERROR;
2563 return svn_error_trace(err);
2567 svn_pool_destroy(iterpool);
2569 return SVN_NO_ERROR;
2572 /* Process the 'update' editor report */
2573 static svn_error_t *
2574 process_editor_report(report_context_t *ctx,
2575 svn_ra_serf__handler_t *handler,
2576 apr_pool_t *scratch_pool)
2578 svn_ra_serf__session_t *sess = ctx->sess;
2579 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2580 apr_interval_time_t waittime_left = sess->timeout;
2581 update_delay_baton_t *ud;
2583 /* Now wrap the response handler with delay support to avoid sending
2584 out too many requests at once */
2585 ud = apr_pcalloc(scratch_pool, sizeof(*ud));
2588 ud->inner_handler = handler->response_handler;
2589 ud->inner_handler_baton = handler->response_baton;
2591 handler->response_handler = update_delay_handler;
2592 handler->response_baton = ud;
2594 /* Open the first extra connection. */
2595 SVN_ERR(open_connection_if_needed(sess, 0));
2599 /* Note that we may have no active GET or PROPFIND requests, yet the
2600 processing has not been completed. This could be from a delay on the
2601 network or because we've spooled the entire response into our "pending"
2602 content of the XML parser. The DONE flag will get set when all the
2603 XML content has been received *and* parsed. */
2604 while (!handler->done
2605 || ctx->num_active_fetches
2606 || ctx->num_active_propfinds
2612 svn_pool_clear(iterpool);
2614 err = svn_ra_serf__context_run(sess, &waittime_left, iterpool);
2616 if (handler->done && handler->server_error)
2618 svn_error_clear(err);
2619 err = svn_ra_serf__server_error_create(handler, iterpool);
2621 SVN_ERR_ASSERT(err != NULL);
2626 /* If there is pending REPORT data, process it now. */
2628 SVN_ERR(process_pending(ud, iterpool));
2630 /* Debugging purposes only! */
2631 for (i = 0; i < sess->num_conns; i++)
2633 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
2637 svn_pool_clear(iterpool);
2639 /* If we got a complete report, close the edit. Otherwise, abort it. */
2641 SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, iterpool));
2643 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2644 _("Missing update-report close tag"));
2646 svn_pool_destroy(iterpool);
2647 return SVN_NO_ERROR;
2650 static svn_error_t *
2651 finish_report(void *report_baton,
2654 report_context_t *report = report_baton;
2655 svn_ra_serf__session_t *sess = report->sess;
2656 svn_ra_serf__handler_t *handler;
2657 svn_ra_serf__xml_context_t *xmlctx;
2658 const char *report_target;
2659 svn_stringbuf_t *buf = NULL;
2660 apr_pool_t *scratch_pool = svn_pool_create(pool);
2663 svn_xml_make_close_tag(&buf, scratch_pool, "S:update-report");
2664 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2665 SVN_ERR(svn_stream_close(report->body_template));
2667 SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, scratch_pool));
2669 xmlctx = svn_ra_serf__xml_context_create(update_ttable,
2670 update_opened, update_closed,
2674 handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL,
2677 handler->method = "REPORT";
2678 handler->path = report_target;
2679 handler->body_delegate = create_update_report_body;
2680 handler->body_delegate_baton = report;
2681 handler->body_type = "text/xml";
2682 handler->custom_accept_encoding = TRUE;
2683 handler->header_delegate = setup_update_report_headers;
2684 handler->header_delegate_baton = report;
2686 svn_ra_serf__request_create(handler);
2688 err = process_editor_report(report, handler, scratch_pool);
2692 err = svn_error_trace(err);
2693 err = svn_error_compose_create(
2696 report->editor->abort_edit(report->editor_baton,
2700 svn_pool_destroy(scratch_pool);
2702 return svn_error_trace(err);
2706 static svn_error_t *
2707 abort_report(void *report_baton,
2711 report_context_t *report = report_baton;
2714 /* Should we perform some cleanup here? */
2716 return SVN_NO_ERROR;
2719 static const svn_ra_reporter3_t ra_serf_reporter = {
2728 /** RA function implementations and body */
2730 static svn_error_t *
2731 make_update_reporter(svn_ra_session_t *ra_session,
2732 const svn_ra_reporter3_t **reporter,
2733 void **report_baton,
2734 svn_revnum_t revision,
2735 const char *src_path,
2736 const char *dest_path,
2737 const char *update_target,
2739 svn_boolean_t ignore_ancestry,
2740 svn_boolean_t text_deltas,
2741 svn_boolean_t send_copyfrom_args,
2742 const svn_delta_editor_t *update_editor,
2744 apr_pool_t *result_pool,
2745 apr_pool_t *scratch_pool)
2747 report_context_t *report;
2748 const svn_delta_editor_t *filter_editor;
2750 svn_boolean_t has_target = *update_target != '\0';
2751 svn_boolean_t server_supports_depth;
2752 svn_ra_serf__session_t *sess = ra_session->priv;
2753 svn_stringbuf_t *buf = NULL;
2754 svn_boolean_t use_bulk_updates;
2756 SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
2757 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
2758 /* We can skip the depth filtering when the user requested
2759 depth_files or depth_infinity because the server will
2760 transmit the right stuff anyway. */
2761 if ((depth != svn_depth_files)
2762 && (depth != svn_depth_infinity)
2763 && ! server_supports_depth)
2765 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
2771 update_editor = filter_editor;
2772 update_baton = filter_baton;
2775 report = apr_pcalloc(result_pool, sizeof(*report));
2776 report->pool = result_pool;
2777 report->sess = sess;
2778 report->target_rev = revision;
2779 report->ignore_ancestry = ignore_ancestry;
2780 report->send_copyfrom_args = send_copyfrom_args;
2781 report->text_deltas = text_deltas;
2782 report->switched_paths = apr_hash_make(report->pool);
2784 report->source = src_path;
2785 report->destination = dest_path;
2786 report->update_target = update_target;
2788 report->editor = update_editor;
2789 report->editor_baton = update_baton;
2790 report->done = FALSE;
2792 *reporter = &ra_serf_reporter;
2793 *report_baton = report;
2795 report->body = apr_pcalloc(report->pool, sizeof(*report->body));
2796 report->body->result_pool = report->pool;
2797 report->body_template = svn_stream_create(report->body, report->pool);
2798 svn_stream_set_write(report->body_template, body_write_fn);
2799 svn_stream_set_close(report->body_template, body_done_fn);
2801 if (sess->bulk_updates == svn_tristate_true)
2803 /* User would like to use bulk updates. */
2804 use_bulk_updates = TRUE;
2806 else if (sess->bulk_updates == svn_tristate_false)
2808 /* User doesn't want bulk updates. */
2809 use_bulk_updates = FALSE;
2813 /* User doesn't have any preferences on bulk updates. Decide on server
2814 preferences and capabilities. */
2815 if (sess->server_allows_bulk)
2817 if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
2819 /* Server doesn't want bulk updates */
2820 use_bulk_updates = FALSE;
2822 else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
2824 /* Server prefers bulk updates, and we respect that */
2825 use_bulk_updates = TRUE;
2829 /* Server allows bulk updates, but doesn't dictate its use. Do
2830 whatever is the default. */
2831 use_bulk_updates = FALSE;
2836 /* Pre-1.8 server didn't send the bulk_updates header. Check if server
2837 supports inlining properties in update editor report. */
2838 if (sess->supports_inline_props)
2840 /* NOTE: both inlined properties and server->allows_bulk_update
2841 (flag SVN_DAV_ALLOW_BULK_UPDATES) were added in 1.8.0, so
2842 this code is never reached with a released version of
2845 Basically by default a 1.8.0 client connecting to a 1.7.x or
2846 older server will always use bulk updates. */
2848 /* Inline props supported: do not use bulk updates. */
2849 use_bulk_updates = FALSE;
2853 /* Inline props are not supported: use bulk updates to avoid
2854 * PROPFINDs for every added node. */
2855 use_bulk_updates = TRUE;
2860 if (use_bulk_updates)
2862 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
2864 "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
2869 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
2871 "xmlns:S", SVN_XML_NAMESPACE,
2873 /* Subversion 1.8+ servers can be told to send properties for newly
2874 added items inline even when doing a skelta response. */
2875 make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
2878 make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
2880 if (SVN_IS_VALID_REVNUM(report->target_rev))
2882 make_simple_xml_tag(&buf, "S:target-revision",
2883 apr_ltoa(scratch_pool, report->target_rev),
2887 if (report->destination && *report->destination)
2889 make_simple_xml_tag(&buf, "S:dst-path", report->destination,
2893 if (report->update_target && *report->update_target)
2895 make_simple_xml_tag(&buf, "S:update-target", report->update_target,
2899 if (report->ignore_ancestry)
2901 make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
2904 if (report->send_copyfrom_args)
2906 make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
2909 /* Old servers know "recursive" but not "depth"; help them DTRT. */
2910 if (depth == svn_depth_files || depth == svn_depth_empty)
2912 make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
2915 /* When in 'send-all' mode, mod_dav_svn will assume that it should
2916 calculate and transmit real text-deltas (instead of empty windows
2917 that merely indicate "text is changed") unless it finds this
2920 NOTE: Do NOT count on servers actually obeying this, as some exist
2921 which obey send-all, but do not check for this directive at all!
2923 NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
2924 override our request and send text-deltas. */
2927 make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
2930 make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
2932 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2934 return SVN_NO_ERROR;
2938 svn_ra_serf__do_update(svn_ra_session_t *ra_session,
2939 const svn_ra_reporter3_t **reporter,
2940 void **report_baton,
2941 svn_revnum_t revision_to_update_to,
2942 const char *update_target,
2944 svn_boolean_t send_copyfrom_args,
2945 svn_boolean_t ignore_ancestry,
2946 const svn_delta_editor_t *update_editor,
2948 apr_pool_t *result_pool,
2949 apr_pool_t *scratch_pool)
2951 svn_ra_serf__session_t *session = ra_session->priv;
2953 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
2954 revision_to_update_to,
2955 session->session_url.path, NULL, update_target,
2956 depth, ignore_ancestry, TRUE /* text_deltas */,
2958 update_editor, update_baton,
2959 result_pool, scratch_pool));
2960 return SVN_NO_ERROR;
2964 svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
2965 const svn_ra_reporter3_t **reporter,
2966 void **report_baton,
2967 svn_revnum_t revision,
2968 const char *diff_target,
2970 svn_boolean_t ignore_ancestry,
2971 svn_boolean_t text_deltas,
2972 const char *versus_url,
2973 const svn_delta_editor_t *diff_editor,
2977 svn_ra_serf__session_t *session = ra_session->priv;
2978 apr_pool_t *scratch_pool = svn_pool_create(pool);
2980 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
2982 session->session_url.path, versus_url, diff_target,
2983 depth, ignore_ancestry, text_deltas,
2984 FALSE /* send_copyfrom */,
2985 diff_editor, diff_baton,
2986 pool, scratch_pool));
2987 svn_pool_destroy(scratch_pool);
2988 return SVN_NO_ERROR;
2992 svn_ra_serf__do_status(svn_ra_session_t *ra_session,
2993 const svn_ra_reporter3_t **reporter,
2994 void **report_baton,
2995 const char *status_target,
2996 svn_revnum_t revision,
2998 const svn_delta_editor_t *status_editor,
3002 svn_ra_serf__session_t *session = ra_session->priv;
3003 apr_pool_t *scratch_pool = svn_pool_create(pool);
3005 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3007 session->session_url.path, NULL, status_target,
3008 depth, FALSE, FALSE, FALSE,
3009 status_editor, status_baton,
3010 pool, scratch_pool));
3011 svn_pool_destroy(scratch_pool);
3012 return SVN_NO_ERROR;
3016 svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
3017 const svn_ra_reporter3_t **reporter,
3018 void **report_baton,
3019 svn_revnum_t revision_to_switch_to,
3020 const char *switch_target,
3022 const char *switch_url,
3023 svn_boolean_t send_copyfrom_args,
3024 svn_boolean_t ignore_ancestry,
3025 const svn_delta_editor_t *switch_editor,
3027 apr_pool_t *result_pool,
3028 apr_pool_t *scratch_pool)
3030 svn_ra_serf__session_t *session = ra_session->priv;
3032 return make_update_reporter(ra_session, reporter, report_baton,
3033 revision_to_switch_to,
3034 session->session_url.path,
3035 switch_url, switch_target,
3038 TRUE /* text_deltas */,
3040 switch_editor, switch_baton,
3041 result_pool, scratch_pool);