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_ra_serf__session_t *session;
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 /* Stream for collecting the request body. */
437 svn_stream_t *body_template;
439 /* Buffer holding request body for the REPORT (can spill to disk). */
440 svn_ra_serf__request_body_t *body;
442 /* number of pending GET requests */
443 unsigned int num_active_fetches;
445 /* number of pending PROPFIND requests */
446 unsigned int num_active_propfinds;
448 /* Are we done parsing the REPORT response? */
451 /* Did we receive all data from the network? */
452 svn_boolean_t report_received;
454 /* Did we close the root directory? */
455 svn_boolean_t closed_root;
459 create_dir_baton(dir_baton_t **new_dir,
460 report_context_t *ctx,
462 apr_pool_t *scratch_pool)
464 dir_baton_t *parent = ctx->cur_dir;
465 apr_pool_t *dir_pool;
469 dir_pool = svn_pool_create(parent->pool);
471 dir_pool = svn_pool_create(ctx->pool);
473 dir = apr_pcalloc(dir_pool, sizeof(*dir));
474 dir->pool = dir_pool;
479 dir->parent_dir = parent;
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);
487 dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->relpath);
488 if (!dir->repos_relpath)
491 dir->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
494 dir->repos_relpath = svn_uri_skip_ancestor(ctx->sess->repos_root_str,
495 ctx->sess->session_url_str,
499 dir->base_rev = SVN_INVALID_REVNUM;
500 dir->copyfrom_rev = SVN_INVALID_REVNUM;
511 create_file_baton(file_baton_t **new_file,
512 report_context_t *ctx,
514 apr_pool_t *scratch_pool)
516 dir_baton_t *parent = ctx->cur_dir;
517 apr_pool_t *file_pool;
520 file_pool = svn_pool_create(parent->pool);
522 file = apr_pcalloc(file_pool, sizeof(*file));
523 file->pool = file_pool;
525 file->parent_dir = parent;
528 file->relpath = svn_relpath_join(parent->relpath, name, file_pool);
529 file->base_name = svn_relpath_basename(file->relpath, NULL);
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,
537 file->base_rev = SVN_INVALID_REVNUM;
538 file->copyfrom_rev = SVN_INVALID_REVNUM;
542 ctx->cur_file = file;
547 /** Minimum nr. of outstanding requests needed before a new connection is
549 #define REQS_PER_CONN 8
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.
556 open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
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))
563 int cur = sess->num_conns;
566 sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
567 sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
569 sess->conns[cur]->last_status_code = -1;
570 sess->conns[cur]->session = sess;
571 status = serf_connection_create2(&sess->conns[cur]->conn,
574 svn_ra_serf__conn_setup,
576 svn_ra_serf__conn_closed,
580 return svn_ra_serf__wrap_err(status, NULL);
588 /* Returns best connection for fetching files/properties. */
589 static svn_ra_serf__connection_t *
590 get_best_connection(report_context_t *ctx)
592 svn_ra_serf__connection_t *conn;
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).
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.
606 ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
608 if (ctx->report_received && (ctx->sess->max_connections > 2))
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)
615 conn = ctx->sess->conns[first_conn];
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
626 The method used here selects the connection with the least amount of
627 pending requests, thereby giving more work to lightly loaded server
630 int i, best_conn = first_conn;
631 unsigned int min = INT_MAX;
632 for (i = first_conn; i < ctx->sess->num_conns; i++)
634 serf_connection_t *sc = ctx->sess->conns[i]->conn;
635 unsigned int pending = serf_connection_pending_requests(sc);
642 conn = ctx->sess->conns[best_conn];
644 /* We don't know how many requests are pending per connection, so just
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;
655 /** Helpers to open and close directories */
658 ensure_dir_opened(dir_baton_t *dir,
659 apr_pool_t *scratch_pool)
661 report_context_t *ctx = dir->ctx;
666 if (dir->base_name[0] == '\0')
669 && ctx->sess->wc_callbacks->invalidate_wc_props)
671 SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
672 ctx->sess->wc_callback_baton,
674 SVN_RA_SERF__WC_CHECKED_IN_URL, scratch_pool));
677 SVN_ERR(ctx->editor->open_root(ctx->editor_baton, dir->base_rev,
683 SVN_ERR(ensure_dir_opened(dir->parent_dir, scratch_pool));
685 if (SVN_IS_VALID_REVNUM(dir->base_rev))
687 SVN_ERR(ctx->editor->open_directory(dir->relpath,
688 dir->parent_dir->dir_baton,
695 SVN_ERR(ctx->editor->add_directory(dir->relpath,
696 dir->parent_dir->dir_baton,
704 dir->dir_opened = TRUE;
710 maybe_close_dir(dir_baton_t *dir)
712 apr_pool_t *scratch_pool = dir->pool;
713 dir_baton_t *parent = dir->parent_dir;
714 report_context_t *ctx = dir->ctx;
716 if (--dir->ref_count)
721 SVN_ERR(ensure_dir_opened(dir, dir->pool));
723 if (dir->remove_props)
725 apr_hash_index_t *hi;
727 for (hi = apr_hash_first(scratch_pool, dir->remove_props);
729 hi = apr_hash_next(hi))
731 SVN_ERR(ctx->editor->change_file_prop(dir->dir_baton,
732 apr_hash_this_key(hi),
738 SVN_ERR(dir->ctx->editor->close_directory(dir->dir_baton, scratch_pool));
740 svn_pool_destroy(dir->pool /* scratch_pool */);
743 return svn_error_trace(maybe_close_dir(parent));
749 ensure_file_opened(file_baton_t *file,
750 apr_pool_t *scratch_pool)
752 const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
754 if (file->file_opened)
757 /* Ensure our parent is open. */
758 SVN_ERR(ensure_dir_opened(file->parent_dir, scratch_pool));
760 /* Open (or add) the file. */
761 if (SVN_IS_VALID_REVNUM(file->base_rev))
763 SVN_ERR(editor->open_file(file->relpath,
764 file->parent_dir->dir_baton,
771 SVN_ERR(editor->add_file(file->relpath,
772 file->parent_dir->dir_baton,
779 file->file_opened = TRUE;
785 /** Routines called when we are fetching a file */
788 headers_fetch(serf_bucket_t *headers,
790 apr_pool_t *pool /* request pool */,
791 apr_pool_t *scratch_pool)
793 fetch_ctx_t *fetch_ctx = baton;
795 /* note that we have old VC URL */
796 if (fetch_ctx->delta_base)
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);
802 else if (fetch_ctx->session->using_compression != svn_tristate_false)
804 serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
811 cancel_fetch(serf_request_t *request,
812 serf_bucket_t *response,
816 fetch_ctx_t *fetch_ctx = baton;
818 /* Uh-oh. Our connection died on us.
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.
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.
829 if (fetch_ctx->read_headers)
831 if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
833 fetch_ctx->aborted_read = TRUE;
834 fetch_ctx->aborted_read_size = fetch_ctx->read_size;
836 fetch_ctx->read_size = 0;
842 /* We have no idea what went wrong. */
843 SVN_ERR_MALFUNCTION();
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).
851 Callers will probably want to also see the function that serves
852 the opposite purpose of this one, close_updated_file(). */
854 open_file_txdelta(file_baton_t *file,
855 apr_pool_t *scratch_pool)
857 const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
859 SVN_ERR_ASSERT(file->txdelta == NULL);
861 SVN_ERR(ensure_file_opened(file, scratch_pool));
863 /* Get (maybe) a textdelta window handler for transmitting file
865 SVN_ERR(editor->apply_textdelta(file->file_baton,
866 svn_checksum_to_cstring(
867 file->base_md5_checksum,
871 &file->txdelta_baton));
876 /* Close the file, handling loose ends and cleanup */
878 close_file(file_baton_t *file,
879 apr_pool_t *scratch_pool)
881 dir_baton_t *parent_dir = file->parent_dir;
882 report_context_t *ctx = parent_dir->ctx;
884 SVN_ERR(ensure_file_opened(file, scratch_pool));
886 /* Set all of the properties we received */
887 if (file->remove_props)
889 apr_hash_index_t *hi;
891 for (hi = apr_hash_first(scratch_pool, file->remove_props);
893 hi = apr_hash_next(hi))
895 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
896 apr_hash_this_key(hi),
902 /* Check for lock information. */
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.
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
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 */)
915 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
916 SVN_PROP_ENTRY_LOCK_TOKEN,
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,
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,
937 svn_pool_destroy(file->pool);
939 SVN_ERR(maybe_close_dir(parent_dir)); /* Remove reference */
944 /* Implements svn_ra_serf__response_handler_t */
946 handle_fetch(serf_request_t *request,
947 serf_bucket_t *response,
954 fetch_ctx_t *fetch_ctx = handler_baton;
955 file_baton_t *file = fetch_ctx->file;
957 /* ### new field. make sure we didn't miss some initialization. */
958 SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
960 if (!fetch_ctx->read_headers)
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.
968 if (fetch_ctx->handler->sline.code != 200)
970 fetch_ctx->handler->discard_body = TRUE;
971 return SVN_NO_ERROR; /* Will return an error in the DONE handler */
974 hdrs = serf_bucket_response_get_headers(response);
975 val = serf_bucket_headers_get(hdrs, "Content-Type");
977 if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
979 fetch_ctx->result_stream =
980 svn_txdelta_parse_svndiff(file->txdelta,
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)
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);
995 else if (val && (strcmp(val, fetch_ctx->delta_base) != 0))
997 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
998 _("GET request returned unexpected "
999 "delta base: %s"), val);
1004 fetch_ctx->result_stream = NULL;
1007 fetch_ctx->read_headers = TRUE;
1012 svn_txdelta_window_t delta_window = { 0 };
1013 svn_txdelta_op_t delta_op;
1014 svn_string_t window_data;
1016 status = serf_bucket_read(response, 8000, &data, &len);
1017 if (SERF_BUCKET_READ_ERROR(status))
1019 return svn_ra_serf__wrap_err(status, NULL);
1022 fetch_ctx->read_size += len;
1024 if (fetch_ctx->aborted_read)
1027 /* We haven't caught up to where we were before. */
1028 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1030 /* Eek. What did the file shrink or something? */
1031 if (APR_STATUS_IS_EOF(status))
1033 SVN_ERR_MALFUNCTION();
1036 /* Skip on to the next iteration of this loop. */
1037 if (status /* includes EAGAIN */)
1038 return svn_ra_serf__wrap_err(status, NULL);
1043 /* Woo-hoo. We're back. */
1044 fetch_ctx->aborted_read = FALSE;
1046 /* Update data and len to just provide the new data. */
1047 skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
1049 len -= (apr_size_t)skip;
1052 if (fetch_ctx->result_stream)
1053 SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &len));
1055 /* otherwise, manually construct the text delta window. */
1058 window_data.data = data;
1059 window_data.len = len;
1061 delta_op.action_code = svn_txdelta_new;
1062 delta_op.offset = 0;
1063 delta_op.length = len;
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;
1070 /* write to the file located in the info. */
1071 SVN_ERR(file->txdelta(&delta_window, file->txdelta_baton));
1074 if (APR_STATUS_IS_EOF(status))
1076 if (fetch_ctx->result_stream)
1077 SVN_ERR(svn_stream_close(fetch_ctx->result_stream));
1079 SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
1082 /* Report EOF, EEAGAIN and other special errors to serf */
1084 return svn_ra_serf__wrap_err(status, NULL);
1088 /* --------------------------------------------------------- */
1090 /** Wrappers around our various property walkers **/
1092 /* Implements svn_ra_serf__prop_func */
1093 static svn_error_t *
1094 set_file_props(void *baton,
1098 const svn_string_t *val,
1099 apr_pool_t *scratch_pool)
1101 file_baton_t *file = baton;
1102 report_context_t *ctx = file->parent_dir->ctx;
1103 const char *prop_name;
1105 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
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.
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
1117 /* assert(!ctx->add_props_included); // Or we wouldn't be here */
1118 if (file->lock_token
1119 && !file->found_lock_prop
1121 && strcmp(ns, "DAV:") == 0
1122 && strcmp(name, "lockdiscovery") == 0)
1125 new_lock = apr_pstrdup(scratch_pool, val->data);
1126 apr_collapse_spaces(new_lock, new_lock);
1128 if (new_lock[0] != '\0')
1129 file->found_lock_prop = TRUE;
1132 return SVN_NO_ERROR;
1135 SVN_ERR(ensure_file_opened(file, scratch_pool));
1137 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1141 return SVN_NO_ERROR;
1144 /* Implements svn_ra_serf__response_done_delegate_t */
1145 static svn_error_t *
1146 file_props_done(serf_request_t *request,
1148 apr_pool_t *scratch_pool)
1150 file_baton_t *file = baton;
1151 svn_ra_serf__handler_t *handler = file->propfind_handler;
1153 if (handler->server_error)
1154 return svn_error_trace(svn_ra_serf__server_error_create(handler,
1157 if (handler->sline.code != 207)
1158 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1160 file->parent_dir->ctx->num_active_propfinds--;
1162 file->fetch_props = FALSE;
1164 if (file->fetch_file)
1165 return SVN_NO_ERROR; /* Still processing file request */
1167 /* Closing the file will automatically deliver the propfind props.
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. */
1173 return svn_error_trace(close_file(file, scratch_pool));
1176 static svn_error_t *
1177 file_fetch_done(serf_request_t *request,
1179 apr_pool_t *scratch_pool)
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;
1185 if (handler->server_error)
1186 return svn_error_trace(svn_ra_serf__server_error_create(handler,
1189 if (handler->sline.code != 200)
1190 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1192 file->parent_dir->ctx->num_active_fetches--;
1194 file->fetch_file = FALSE;
1196 if (file->fetch_props)
1197 return SVN_NO_ERROR; /* Still processing PROPFIND request */
1199 /* Closing the file will automatically deliver the propfind props.
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));
1208 /* Initiates additional requests needed for a file when not in "send-all" mode.
1210 static svn_error_t *
1211 fetch_for_file(file_baton_t *file,
1212 apr_pool_t *scratch_pool)
1214 report_context_t *ctx = file->parent_dir->ctx;
1215 svn_ra_serf__connection_t *conn;
1216 svn_ra_serf__handler_t *handler;
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));
1223 /* What connection should we go on? */
1224 conn = get_best_connection(ctx);
1226 /* Note that we (still) use conn for both requests.. Should we send
1227 them out on different connections? */
1229 if (file->fetch_file)
1231 SVN_ERR(open_file_txdelta(file, scratch_pool));
1233 if (!ctx->text_deltas
1234 || file->txdelta == svn_delta_noop_window_handler)
1236 SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
1237 file->fetch_file = FALSE;
1240 if (file->fetch_file
1241 && file->final_sha1_checksum
1242 && ctx->sess->wc_callbacks->get_wc_contents)
1245 svn_stream_t *cached_contents = NULL;
1247 err = ctx->sess->wc_callbacks->get_wc_contents(
1248 ctx->sess->wc_callback_baton,
1250 file->final_sha1_checksum,
1253 if (err || !cached_contents)
1254 svn_error_clear(err); /* ### Can we return some/most errors? */
1257 /* ### For debugging purposes we could validate the md5 here,
1258 but our implementations in libsvn_client already do that
1260 SVN_ERR(svn_txdelta_send_stream(cached_contents,
1262 file->txdelta_baton,
1263 NULL, scratch_pool));
1264 SVN_ERR(svn_stream_close(cached_contents));
1265 file->fetch_file = FALSE;
1269 if (file->fetch_file)
1271 fetch_ctx_t *fetch_ctx;
1273 /* Let's fetch the file with a GET request... */
1274 SVN_ERR_ASSERT(file->url && file->repos_relpath);
1276 /* Otherwise, we use a GET request for the file's contents. */
1278 fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx));
1279 fetch_ctx->file = file;
1280 fetch_ctx->session = ctx->sess;
1282 /* Can we somehow get away with just obtaining a DIFF? */
1283 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
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.
1288 if (SVN_IS_VALID_REVNUM(file->base_rev))
1290 fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
1291 ctx->sess->rev_root_stub,
1293 svn_path_uri_encode(
1294 file->repos_relpath,
1297 else if (file->copyfrom_path)
1299 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(file->copyfrom_rev));
1301 fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
1302 ctx->sess->rev_root_stub,
1304 svn_path_uri_encode(
1305 file->copyfrom_path+1,
1309 else if (ctx->sess->wc_callbacks->get_wc_prop)
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
1315 const svn_string_t *value = NULL;
1316 SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
1317 ctx->sess->wc_callback_baton,
1319 SVN_RA_SERF__WC_CHECKED_IN_URL,
1320 &value, scratch_pool));
1322 fetch_ctx->delta_base = value
1323 ? apr_pstrdup(file->pool, value->data)
1327 handler = svn_ra_serf__create_handler(ctx->sess, file->pool);
1329 handler->method = "GET";
1330 handler->path = file->url;
1332 handler->conn = conn; /* Explicit scheduling */
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;
1339 handler->response_handler = handle_fetch;
1340 handler->response_baton = fetch_ctx;
1342 handler->response_error = cancel_fetch;
1343 handler->response_error_baton = fetch_ctx;
1345 handler->done_delegate = file_fetch_done;
1346 handler->done_delegate_baton = fetch_ctx;
1348 fetch_ctx->handler = handler;
1350 svn_ra_serf__request_create(handler);
1352 ctx->num_active_fetches++;
1356 /* If needed, create the PROPFIND to retrieve the file's properties. */
1357 if (file->fetch_props)
1359 SVN_ERR(svn_ra_serf__create_propfind_handler(&file->propfind_handler,
1360 ctx->sess, file->url,
1361 ctx->target_rev, "0",
1363 set_file_props, file,
1365 file->propfind_handler->conn = conn; /* Explicit scheduling */
1367 file->propfind_handler->done_delegate = file_props_done;
1368 file->propfind_handler->done_delegate_baton = file;
1370 /* Create a serf request for the PROPFIND. */
1371 svn_ra_serf__request_create(file->propfind_handler);
1373 ctx->num_active_propfinds++;
1376 if (file->fetch_props || file->fetch_file)
1377 return SVN_NO_ERROR;
1380 /* Somehow we are done; probably via the local cache.
1381 Close the file and release memory, etc. */
1383 return svn_error_trace(close_file(file, scratch_pool));
1386 /* Implements svn_ra_serf__prop_func */
1387 static svn_error_t *
1388 set_dir_prop(void *baton,
1392 const svn_string_t *val,
1393 apr_pool_t *scratch_pool)
1395 dir_baton_t *dir = baton;
1396 report_context_t *ctx = dir->ctx;
1397 const char *prop_name;
1399 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
1400 if (prop_name == NULL)
1401 return SVN_NO_ERROR;
1403 SVN_ERR(ensure_dir_opened(dir, scratch_pool));
1405 SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
1408 return SVN_NO_ERROR;
1411 /* Implements svn_ra_serf__response_done_delegate_t */
1412 static svn_error_t *
1413 dir_props_done(serf_request_t *request,
1415 apr_pool_t *scratch_pool)
1417 dir_baton_t *dir = baton;
1418 svn_ra_serf__handler_t *handler = dir->propfind_handler;
1420 if (handler->server_error)
1421 return svn_ra_serf__server_error_create(handler, scratch_pool);
1423 if (handler->sline.code != 207)
1424 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1426 dir->ctx->num_active_propfinds--;
1428 /* Closing the directory will automatically deliver the propfind props.
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. */
1434 return svn_error_trace(maybe_close_dir(dir));
1437 /* Initiates additional requests needed for a directory when not in "send-all"
1439 static svn_error_t *
1440 fetch_for_dir(dir_baton_t *dir,
1441 apr_pool_t *scratch)
1443 report_context_t *ctx = dir->ctx;
1444 svn_ra_serf__connection_t *conn;
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));
1451 /* What connection should we go on? */
1452 conn = get_best_connection(ctx);
1454 /* If needed, create the PROPFIND to retrieve the file's properties. */
1455 if (dir->fetch_props)
1457 SVN_ERR(svn_ra_serf__create_propfind_handler(&dir->propfind_handler,
1458 ctx->sess, dir->url,
1459 ctx->target_rev, "0",
1464 dir->propfind_handler->conn = conn;
1465 dir->propfind_handler->done_delegate = dir_props_done;
1466 dir->propfind_handler->done_delegate_baton = dir;
1468 /* Create a serf request for the PROPFIND. */
1469 svn_ra_serf__request_create(dir->propfind_handler);
1471 ctx->num_active_propfinds++;
1474 SVN_ERR_MALFUNCTION();
1476 return SVN_NO_ERROR;
1480 /** XML callbacks for our update-report response parsing */
1482 /* Conforms to svn_ra_serf__xml_opened_t */
1483 static svn_error_t *
1484 update_opened(svn_ra_serf__xml_estate_t *xes,
1487 const svn_ra_serf__dav_props_t *tag,
1488 apr_pool_t *scratch_pool)
1490 report_context_t *ctx = baton;
1493 switch (entered_state)
1499 attrs = svn_ra_serf__xml_gather_since(xes, UPDATE_REPORT);
1500 val = svn_hash_gets(attrs, "inline-props");
1502 if (val && (strcmp(val, "true") == 0))
1503 ctx->add_props_included = TRUE;
1505 val = svn_hash_gets(attrs, "send-all");
1507 if (val && (strcmp(val, "true") == 0))
1509 ctx->send_all_mode = TRUE;
1511 /* All properties are included in send-all mode. */
1512 ctx->add_props_included = TRUE;
1522 attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1524 name = svn_hash_gets(attrs, "name");
1528 SVN_ERR(create_dir_baton(&dir, ctx, name, scratch_pool));
1530 if (entered_state == OPEN_DIR)
1532 apr_int64_t base_rev;
1534 SVN_ERR(svn_cstring_atoi64(&base_rev,
1535 svn_hash_gets(attrs, "rev")));
1536 dir->base_rev = (svn_revnum_t)base_rev;
1540 dir->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
1542 if (dir->copyfrom_path)
1544 apr_int64_t copyfrom_rev;
1545 const char *copyfrom_rev_str;
1546 dir->copyfrom_path = svn_fspath__canonicalize(
1550 copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
1552 if (!copyfrom_rev_str)
1553 return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1555 _("Missing '%s' attribute"),
1558 SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str));
1560 dir->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
1563 if (! ctx->add_props_included)
1564 dir->fetch_props = TRUE;
1573 attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1575 SVN_ERR(create_file_baton(&file, ctx, svn_hash_gets(attrs, "name"),
1578 if (entered_state == OPEN_FILE)
1580 apr_int64_t base_rev;
1582 SVN_ERR(svn_cstring_atoi64(&base_rev,
1583 svn_hash_gets(attrs, "rev")));
1584 file->base_rev = (svn_revnum_t)base_rev;
1588 const char *sha1_checksum;
1589 file->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
1591 if (file->copyfrom_path)
1593 apr_int64_t copyfrom_rev;
1594 const char *copyfrom_rev_str;
1596 file->copyfrom_path = svn_fspath__canonicalize(
1597 file->copyfrom_path,
1600 copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
1602 if (!copyfrom_rev_str)
1603 return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1605 _("Missing '%s' attribute"),
1608 SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str));
1610 file->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
1613 sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
1616 SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
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;
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;
1637 file_baton_t *file = ctx->cur_file;
1638 const char *base_checksum;
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)
1647 file->fetch_file = FALSE;
1649 attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1650 base_checksum = svn_hash_gets(attrs, "base-checksum");
1653 SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
1654 svn_checksum_md5, base_checksum,
1657 SVN_ERR(open_file_txdelta(ctx->cur_file, scratch_pool));
1659 if (ctx->cur_file->txdelta != svn_delta_noop_window_handler)
1661 svn_stream_t *decoder;
1663 decoder = svn_txdelta_parse_svndiff(file->txdelta,
1664 file->txdelta_baton,
1665 TRUE /* error early close*/,
1668 file->txdelta_stream = svn_base64_decode(decoder, file->pool);
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)
1682 In r1063337 this behavior was changed in mod_dav_svn to always
1683 send property changes inline in these cases. (See issue #3657)
1685 Note that before that change the property changes to the last_*
1686 entry props were already inlined via specific xml elements. */
1688 ctx->cur_file->fetch_props = TRUE;
1689 else if (ctx->cur_dir)
1690 ctx->cur_dir->fetch_props = TRUE;
1695 return SVN_NO_ERROR;
1700 /* Conforms to svn_ra_serf__xml_closed_t */
1701 static svn_error_t *
1702 update_closed(svn_ra_serf__xml_estate_t *xes,
1705 const svn_string_t *cdata,
1707 apr_pool_t *scratch_pool)
1709 report_context_t *ctx = baton;
1711 switch (leaving_state)
1716 case TARGET_REVISION:
1718 const char *revstr = svn_hash_gets(attrs, "rev");
1721 SVN_ERR(svn_cstring_atoi64(&rev, revstr));
1723 SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
1729 case CHECKED_IN_HREF:
1731 ctx->cur_file->url = apr_pstrdup(ctx->cur_file->pool, cdata->data);
1733 ctx->cur_dir->url = apr_pstrdup(ctx->cur_dir->pool, cdata->data);
1739 const char *name = svn_hash_gets(attrs, "name");
1740 const char *encoding;
1741 const svn_string_t *value;
1743 if (leaving_state == REMOVE_PROP)
1745 else if ((encoding = svn_hash_gets(attrs, "encoding")))
1747 if (strcmp(encoding, "base64") != 0)
1748 return svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL,
1749 _("Got unrecognized encoding '%s'"),
1752 value = svn_base64_decode_string(cdata, scratch_pool);
1759 file_baton_t *file = ctx->cur_file;
1762 || ctx->add_props_included
1763 || SVN_IS_VALID_REVNUM(file->base_rev))
1765 SVN_ERR(ensure_file_opened(file, scratch_pool));
1767 SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1774 if (!file->remove_props)
1775 file->remove_props = apr_hash_make(file->pool);
1777 svn_hash_sets(file->remove_props,
1778 apr_pstrdup(file->pool, name),
1784 dir_baton_t *dir = ctx->cur_dir;
1787 || ctx->add_props_included
1788 || SVN_IS_VALID_REVNUM(dir->base_rev))
1790 SVN_ERR(ensure_dir_opened(dir, scratch_pool));
1792 SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
1799 if (!dir->remove_props)
1800 dir->remove_props = apr_hash_make(dir->pool);
1802 svn_hash_sets(dir->remove_props,
1803 apr_pstrdup(dir->pool, name),
1813 dir_baton_t *dir = ctx->cur_dir;
1814 ctx->cur_dir = ctx->cur_dir->parent_dir;
1816 if (dir->fetch_props && ! dir->url)
1818 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1819 _("The REPORT response did not "
1820 "include the requested checked-in "
1824 if (!dir->fetch_props)
1826 SVN_ERR(maybe_close_dir(dir));
1827 break; /* dir potentially no longer valid */
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));
1841 file_baton_t *file = ctx->cur_file;
1843 ctx->cur_file = NULL;
1844 /* go fetch info->name from DAV:checked-in */
1846 if ((file->fetch_file || file->fetch_props) && ! file->url)
1848 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1849 _("The REPORT response did not "
1850 "include the requested checked-in "
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)
1858 SVN_ERR(close_file(file, scratch_pool));
1859 break; /* file is no longer valid */
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));
1872 SVN_ERR(svn_checksum_parse_hex(&ctx->cur_file->final_md5_checksum,
1875 ctx->cur_file->pool));
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");
1885 SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
1886 svn_checksum_md5, base_checksum,
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,
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;
1905 const char *name = svn_hash_gets(attrs, "name");
1907 apr_int64_t delete_rev;
1909 SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
1911 revstr = svn_hash_gets(attrs, "rev");
1914 SVN_ERR(svn_cstring_atoi64(&delete_rev, revstr));
1916 delete_rev = SVN_INVALID_REVNUM;
1918 SVN_ERR(ctx->editor->delete_entry(
1919 svn_relpath_join(ctx->cur_dir->relpath,
1922 (svn_revnum_t)delete_rev,
1923 ctx->cur_dir->dir_baton,
1930 const char *name = svn_hash_gets(attrs, "name");
1932 SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
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,
1943 const char *name = svn_hash_gets(attrs, "name");
1945 SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
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,
1957 file_baton_t *file = ctx->cur_file;
1959 if (file->txdelta_stream)
1961 SVN_ERR(svn_stream_close(file->txdelta_stream));
1962 file->txdelta_stream = NULL;
1969 case CREATOR_DISPLAYNAME:
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)
1978 In that case the last_* entry props are posted as 3 specific xml
1979 elements, which we handle here.
1981 In r1063337 this behavior was changed in mod_dav_svn to always
1982 send property changes inline in these cases. (See issue #3657)
1985 const char *propname;
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));
1994 switch (leaving_state)
1997 propname = SVN_PROP_ENTRY_COMMITTED_REV;
2000 propname = SVN_PROP_ENTRY_COMMITTED_DATE;
2002 case CREATOR_DISPLAYNAME:
2003 propname = SVN_PROP_ENTRY_LAST_AUTHOR;
2006 SVN_ERR_MALFUNCTION(); /* Impossible to reach */
2010 SVN_ERR(ctx->editor->change_file_prop(ctx->cur_file->file_baton,
2014 SVN_ERR(ctx->editor->change_dir_prop(ctx->cur_dir->dir_baton,
2021 return SVN_NO_ERROR;
2025 /* Conforms to svn_ra_serf__xml_cdata_t */
2026 static svn_error_t *
2027 update_cdata(svn_ra_serf__xml_estate_t *xes,
2032 apr_pool_t *scratch_pool)
2034 report_context_t *ctx = baton;
2036 if (current_state == TXDELTA && ctx->cur_file
2037 && ctx->cur_file->txdelta_stream)
2039 SVN_ERR(svn_stream_write(ctx->cur_file->txdelta_stream, data, &len));
2042 return SVN_NO_ERROR;
2046 /** Editor callbacks given to callers to create request body */
2048 /* Helper to create simple xml tag without attributes. */
2050 make_simple_xml_tag(svn_stringbuf_t **buf_p,
2051 const char *tagname,
2055 svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname,
2057 svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
2058 svn_xml_make_close_tag(buf_p, pool, tagname);
2061 static svn_error_t *
2062 set_path(void *report_baton,
2064 svn_revnum_t revision,
2066 svn_boolean_t start_empty,
2067 const char *lock_token,
2070 report_context_t *report = report_baton;
2071 svn_stringbuf_t *buf = NULL;
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,
2079 svn_xml_escape_cdata_cstring(&buf, path, pool);
2080 svn_xml_make_close_tag(&buf, pool, "S:entry");
2082 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2084 return SVN_NO_ERROR;
2087 static svn_error_t *
2088 delete_path(void *report_baton,
2092 report_context_t *report = report_baton;
2093 svn_stringbuf_t *buf = NULL;
2095 make_simple_xml_tag(&buf, "S:missing", path, pool);
2097 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2099 return SVN_NO_ERROR;
2102 static svn_error_t *
2103 link_path(void *report_baton,
2106 svn_revnum_t revision,
2108 svn_boolean_t start_empty,
2109 const char *lock_token,
2112 report_context_t *report = report_baton;
2113 const char *link, *report_target;
2115 apr_status_t status;
2116 svn_stringbuf_t *buf = NULL;
2118 /* We need to pass in the baseline relative path.
2120 * TODO Confirm that it's on the same server?
2122 status = apr_uri_parse(pool, url, &uri);
2125 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2126 _("Unable to parse URL '%s'"), url);
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));
2132 link = apr_pstrcat(pool, "/", link, SVN_VA_NULL);
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),
2139 "start-empty", start_empty ? "true" : NULL,
2141 svn_xml_escape_cdata_cstring(&buf, path, pool);
2142 svn_xml_make_close_tag(&buf, pool, "S:entry");
2144 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
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);
2152 if (!path[0] && report->update_target[0])
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);
2162 /* Serf callback to setup update request headers. */
2163 static svn_error_t *
2164 setup_update_report_headers(serf_bucket_t *headers,
2166 apr_pool_t *pool /* request pool */,
2167 apr_pool_t *scratch_pool)
2169 report_context_t *report = baton;
2171 svn_ra_serf__setup_svndiff_accept_encoding(headers, report->sess);
2173 return SVN_NO_ERROR;
2176 /* Baton for update_delay_handler */
2177 typedef struct update_delay_baton_t
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;
2185 /* Helper for update_delay_handler() and process_pending() to
2186 call UDB->INNER_HANDLER with buffer pointed by DATA. */
2187 static svn_error_t *
2188 process_buffer(update_delay_baton_t *udb,
2189 serf_request_t *request,
2192 svn_boolean_t at_eof,
2193 serf_bucket_alloc_t *alloc,
2196 serf_bucket_t *tmp_bucket;
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. */
2204 tmp_bucket = serf_bucket_simple_create(data, len, NULL, NULL,
2209 tmp_bucket = svn_ra_serf__create_bucket_with_eagain(data, len,
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);
2218 /* And free the bucket explicitly to avoid growing request allocator
2219 storage (in a loop) */
2220 serf_bucket_destroy(tmp_bucket);
2222 return svn_error_trace(err);
2226 /* Delaying wrapping reponse handler, to avoid creating too many
2227 requests to deliver efficiently */
2228 static svn_error_t *
2229 update_delay_handler(serf_request_t *request,
2230 serf_bucket_t *response,
2231 void *handler_baton,
2232 apr_pool_t *scratch_pool)
2234 update_delay_baton_t *udb = handler_baton;
2235 apr_status_t status;
2236 apr_pool_t *iterpool = NULL;
2238 if (! udb->spillbuf)
2240 if (udb->report->send_all_mode)
2242 /* Easy out... We only have one request, so avoid everything and just
2243 call the inner handler.
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 */
2248 return svn_error_trace(udb->inner_handler(request, response,
2249 udb->inner_handler_baton,
2253 while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
2254 < REQUEST_COUNT_TO_RESUME)
2258 svn_boolean_t at_eof = FALSE;
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;
2268 iterpool = svn_pool_create(scratch_pool);
2270 svn_pool_clear(iterpool);
2272 if (len == 0 && !at_eof)
2273 return svn_ra_serf__wrap_err(status, NULL);
2275 err = process_buffer(udb, request, data, len, at_eof,
2276 serf_request_get_alloc(request),
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))
2283 svn_error_clear(err); /* Throttling is working ok */
2285 else if (err && (APR_STATUS_IS_EOF(err->apr_err)))
2287 svn_pool_destroy(iterpool);
2288 return svn_error_trace(err); /* No buffering was necessary */
2292 /* SERF_ERROR_WAIT_CONN should be impossible? */
2293 return svn_error_trace(err);
2297 /* Let's start using the spill infrastructure */
2298 udb->spillbuf = svn_spillbuf__create(SPILLBUF_BLOCKSIZE,
2299 SPILLBUF_MAXBUFFSIZE,
2303 /* Read everything we can to a spillbuffer */
2309 /* ### What blocksize should we pass? */
2310 status = serf_bucket_read(response, 8*PARSE_CHUNK_SIZE, &data, &len);
2312 if (!SERF_BUCKET_READ_ERROR(status))
2313 SVN_ERR(svn_spillbuf__write(udb->spillbuf, data, len, scratch_pool));
2315 while (status == APR_SUCCESS);
2317 if (APR_STATUS_IS_EOF(status))
2318 udb->report->report_received = TRUE;
2320 /* We handle feeding the data from the main context loop, which will be right
2321 after processing the pending data */
2324 return svn_ra_serf__wrap_err(status, NULL);
2326 return SVN_NO_ERROR;
2329 /* Process pending data from the update report, if any */
2330 static svn_error_t *
2331 process_pending(update_delay_baton_t *udb,
2332 apr_pool_t *scratch_pool)
2334 apr_pool_t *iterpool = NULL;
2335 serf_bucket_alloc_t *alloc = NULL;
2337 while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
2338 < REQUEST_COUNT_TO_RESUME)
2342 svn_boolean_t at_eof;
2347 iterpool = svn_pool_create(scratch_pool);
2348 alloc = serf_bucket_allocator_create(scratch_pool, NULL, NULL);
2351 svn_pool_clear(iterpool);
2353 SVN_ERR(svn_spillbuf__read(&data, &len, udb->spillbuf, iterpool));
2355 if (data == NULL && !udb->report->report_received)
2357 else if (data == NULL)
2362 err = process_buffer(udb, NULL /* allowed? */, data, len,
2363 at_eof, alloc, iterpool);
2365 if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
2367 svn_error_clear(err); /* Throttling is working */
2369 else if (err && APR_STATUS_IS_EOF(err->apr_err))
2371 svn_error_clear(err);
2373 svn_pool_destroy(iterpool);
2374 udb->spillbuf = NULL;
2375 return SVN_NO_ERROR;
2378 return svn_error_trace(err);
2382 svn_pool_destroy(iterpool);
2384 return SVN_NO_ERROR;
2387 /* Process the 'update' editor report */
2388 static svn_error_t *
2389 process_editor_report(report_context_t *ctx,
2390 svn_ra_serf__handler_t *handler,
2391 apr_pool_t *scratch_pool)
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;
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));
2403 ud->inner_handler = handler->response_handler;
2404 ud->inner_handler_baton = handler->response_baton;
2406 handler->response_handler = update_delay_handler;
2407 handler->response_baton = ud;
2409 /* Open the first extra connection. */
2410 SVN_ERR(open_connection_if_needed(sess, 0));
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
2427 svn_pool_clear(iterpool);
2429 err = svn_ra_serf__context_run(sess, &waittime_left, iterpool);
2431 if (handler->done && handler->server_error)
2433 svn_error_clear(err);
2434 err = svn_ra_serf__server_error_create(handler, iterpool);
2436 SVN_ERR_ASSERT(err != NULL);
2441 /* If there is pending REPORT data, process it now. */
2443 SVN_ERR(process_pending(ud, iterpool));
2445 /* Debugging purposes only! */
2446 for (i = 0; i < sess->num_conns; i++)
2448 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
2452 svn_pool_clear(iterpool);
2454 /* If we got a complete report, close the edit. Otherwise, abort it. */
2456 SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, iterpool));
2458 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2459 _("Missing update-report close tag"));
2461 svn_pool_destroy(iterpool);
2462 return SVN_NO_ERROR;
2465 static svn_error_t *
2466 finish_report(void *report_baton,
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);
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));
2482 SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, scratch_pool));
2484 xmlctx = svn_ra_serf__xml_context_create(update_ttable,
2485 update_opened, update_closed,
2489 handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL,
2492 svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
2493 &handler->body_delegate_baton,
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;
2502 svn_ra_serf__request_create(handler);
2504 err = process_editor_report(report, handler, scratch_pool);
2508 err = svn_error_trace(err);
2509 err = svn_error_compose_create(
2512 report->editor->abort_edit(report->editor_baton,
2516 svn_pool_destroy(scratch_pool);
2518 return svn_error_trace(err);
2522 static svn_error_t *
2523 abort_report(void *report_baton,
2527 report_context_t *report = report_baton;
2530 /* Should we perform some cleanup here? */
2532 return SVN_NO_ERROR;
2535 static const svn_ra_reporter3_t ra_serf_reporter = {
2544 /** RA function implementations and body */
2546 static svn_error_t *
2547 make_update_reporter(svn_ra_session_t *ra_session,
2548 const svn_ra_reporter3_t **reporter,
2549 void **report_baton,
2550 svn_revnum_t revision,
2551 const char *src_path,
2552 const char *dest_path,
2553 const char *update_target,
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,
2560 apr_pool_t *result_pool,
2561 apr_pool_t *scratch_pool)
2563 report_context_t *report;
2564 const svn_delta_editor_t *filter_editor;
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;
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)
2581 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
2587 update_editor = filter_editor;
2588 update_baton = filter_baton;
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);
2600 report->source = src_path;
2601 report->destination = dest_path;
2602 report->update_target = update_target;
2604 report->editor = update_editor;
2605 report->editor_baton = update_baton;
2606 report->done = FALSE;
2608 *reporter = &ra_serf_reporter;
2609 *report_baton = report;
2612 svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
2614 report->body_template = svn_ra_serf__request_body_get_stream(report->body);
2616 if (sess->bulk_updates == svn_tristate_true)
2618 /* User would like to use bulk updates. */
2619 use_bulk_updates = TRUE;
2621 else if (sess->bulk_updates == svn_tristate_false)
2623 /* User doesn't want bulk updates. */
2624 use_bulk_updates = FALSE;
2628 /* User doesn't have any preferences on bulk updates. Decide on server
2629 preferences and capabilities. */
2630 if (sess->server_allows_bulk)
2632 if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
2634 /* Server doesn't want bulk updates */
2635 use_bulk_updates = FALSE;
2637 else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
2639 /* Server prefers bulk updates, and we respect that */
2640 use_bulk_updates = TRUE;
2644 /* Server allows bulk updates, but doesn't dictate its use. Do
2645 whatever is the default. */
2646 use_bulk_updates = FALSE;
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)
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
2660 Basically by default a 1.8.0 client connecting to a 1.7.x or
2661 older server will always use bulk updates. */
2663 /* Inline props supported: do not use bulk updates. */
2664 use_bulk_updates = FALSE;
2668 /* Inline props are not supported: use bulk updates to avoid
2669 * PROPFINDs for every added node. */
2670 use_bulk_updates = TRUE;
2675 if (use_bulk_updates)
2677 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
2679 "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
2684 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
2686 "xmlns:S", SVN_XML_NAMESPACE,
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);
2693 make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
2695 if (SVN_IS_VALID_REVNUM(report->target_rev))
2697 make_simple_xml_tag(&buf, "S:target-revision",
2698 apr_ltoa(scratch_pool, report->target_rev),
2702 if (report->destination && *report->destination)
2704 make_simple_xml_tag(&buf, "S:dst-path", report->destination,
2708 if (report->update_target && *report->update_target)
2710 make_simple_xml_tag(&buf, "S:update-target", report->update_target,
2714 if (report->ignore_ancestry)
2716 make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
2719 if (report->send_copyfrom_args)
2721 make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
2724 /* Old servers know "recursive" but not "depth"; help them DTRT. */
2725 if (depth == svn_depth_files || depth == svn_depth_empty)
2727 make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
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
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!
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. */
2742 make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
2745 make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
2747 SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2749 return SVN_NO_ERROR;
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,
2759 svn_boolean_t send_copyfrom_args,
2760 svn_boolean_t ignore_ancestry,
2761 const svn_delta_editor_t *update_editor,
2763 apr_pool_t *result_pool,
2764 apr_pool_t *scratch_pool)
2766 svn_ra_serf__session_t *session = ra_session->priv;
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 */,
2773 update_editor, update_baton,
2774 result_pool, scratch_pool));
2775 return SVN_NO_ERROR;
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,
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,
2792 svn_ra_serf__session_t *session = ra_session->priv;
2793 apr_pool_t *scratch_pool = svn_pool_create(pool);
2795 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
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;
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,
2813 const svn_delta_editor_t *status_editor,
2817 svn_ra_serf__session_t *session = ra_session->priv;
2818 apr_pool_t *scratch_pool = svn_pool_create(pool);
2820 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
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;
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,
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,
2842 apr_pool_t *result_pool,
2843 apr_pool_t *scratch_pool)
2845 svn_ra_serf__session_t *session = ra_session->priv;
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,
2853 TRUE /* text_deltas */,
2855 switch_editor, switch_baton,
2856 result_pool, scratch_pool);