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"
54 * This enum represents the current state of our XML parsing for a REPORT.
56 * A little explanation of how the parsing works. Every time we see
57 * an open-directory tag, we enter the OPEN_DIR state. Likewise, for
58 * add-directory, open-file, etc. When we see the closing variant of the
59 * open-directory tag, we'll 'pop' out of that state.
61 * Each state has a pool associated with it that can have temporary
62 * allocations that will live as long as the tag is opened. Once
63 * the tag is 'closed', the pool will be reused.
65 typedef enum report_state_e {
83 /* While we process the REPORT response, we will queue up GET and PROPFIND
84 requests. For a very large checkout, it is very easy to queue requests
85 faster than they are resolved. Thus, we need to pause the XML processing
86 (which queues more requests) to avoid queueing too many, with their
87 attendant memory costs. When the queue count drops low enough, we will
88 resume XML processing.
90 Note that we don't want the count to drop to zero. We have multiple
91 connections that we want to keep busy. These are also heuristic numbers
92 since network and parsing behavior (ie. it doesn't pause immediately)
93 can make the measurements quite imprecise.
95 We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and
96 NUM_ACTIVE_PROPFINDS in the report_context_t structure. */
97 #define REQUEST_COUNT_TO_PAUSE 50
98 #define REQUEST_COUNT_TO_RESUME 40
101 /* Forward-declare our report context. */
102 typedef struct report_context_t report_context_t;
105 * This structure represents the information for a directory.
107 typedef struct report_dir_t
109 /* Our parent directory.
111 * This value is NULL when we are the root.
113 struct report_dir_t *parent_dir;
117 /* Pointer back to our original report context. */
118 report_context_t *report_context;
120 /* Our name sans any parents. */
121 const char *base_name;
123 /* the expanded directory name (including all parent names) */
126 /* the canonical url for this directory after updating. (received) */
129 /* The original repos_relpath of this url (from the working copy)
130 or NULL if the repos_relpath can be calculated from the edit root. */
131 const char *repos_relpath;
133 /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */
134 svn_revnum_t base_rev;
136 /* controlling dir baton - this is only created in ensure_dir_opened() */
138 apr_pool_t *dir_baton_pool;
140 /* How many references to this directory do we still have open? */
141 apr_size_t ref_count;
143 /* Namespace list allocated out of this ->pool. */
144 svn_ra_serf__ns_t *ns_list;
146 /* hashtable for all of the properties (shared within a dir) */
149 /* hashtable for all to-be-removed properties (shared within a dir) */
150 apr_hash_t *removed_props;
152 /* The propfind request for our current directory */
153 svn_ra_serf__handler_t *propfind_handler;
155 /* Has the server told us to fetch the dir props? */
156 svn_boolean_t fetch_props;
158 /* Have we closed the directory tag (meaning no more additions)? */
159 svn_boolean_t tag_closed;
161 /* The children of this directory */
162 struct report_dir_t *children;
164 /* The next sibling of this directory */
165 struct report_dir_t *sibling;
169 * This structure represents the information for a file.
171 * A directory may have a report_info_t associated with it as well.
173 * This structure is created as we parse the REPORT response and
174 * once the element is completed, we create a report_fetch_t structure
175 * to give to serf to retrieve this file.
177 typedef struct report_info_t
181 /* The enclosing directory.
183 * If this structure refers to a directory, the dir it points to will be
188 /* Our name sans any directory info. */
189 const char *base_name;
191 /* the expanded file name (including all parent directory names) */
194 /* the canonical url for this file. */
197 /* lock token, if we had one to start off with. */
198 const char *lock_token;
200 /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */
201 svn_revnum_t base_rev;
203 /* our delta base, if present (NULL if we're adding the file) */
204 const char *delta_base;
206 /* Path of original item if add with history */
207 const char *copyfrom_path;
209 /* Revision of original item if add with history */
210 svn_revnum_t copyfrom_rev;
212 /* The propfind request for our current file (if present) */
213 svn_ra_serf__handler_t *propfind_handler;
215 /* Has the server told us to fetch the file props? */
216 svn_boolean_t fetch_props;
218 /* Has the server told us to go fetch - only valid if we had it already */
219 svn_boolean_t fetch_file;
221 /* The properties for this file */
224 /* pool passed to update->add_file, etc. */
225 apr_pool_t *editor_pool;
227 /* controlling file_baton and textdelta handler */
229 const char *base_checksum;
230 const char *final_sha1_checksum;
231 svn_txdelta_window_handler_t textdelta;
232 void *textdelta_baton;
233 svn_stream_t *svndiff_decoder;
234 svn_stream_t *base64_decoder;
236 /* Checksum for close_file */
237 const char *final_checksum;
239 /* Stream containing file contents already cached in the working
240 copy (which may be used to avoid a GET request for the same). */
241 svn_stream_t *cached_contents;
243 /* temporary property for this file which is currently being parsed
244 * It will eventually be stored in our parent directory's property hash.
247 const char *prop_name;
248 svn_stringbuf_t *prop_value;
249 const char *prop_encoding;
253 * This structure represents a single request to GET (fetch) a file with
254 * its associated Serf session/connection.
256 typedef struct report_fetch_t {
258 /* The handler representing this particular fetch. */
259 svn_ra_serf__handler_t *handler;
261 /* The session we should use to fetch the file. */
262 svn_ra_serf__session_t *sess;
264 /* The connection we should use to fetch file. */
265 svn_ra_serf__connection_t *conn;
267 /* Stores the information for the file we want to fetch. */
270 /* Have we read our response headers yet? */
271 svn_boolean_t read_headers;
273 /* This flag is set when our response is aborted before we reach the
274 * end and we decide to requeue this request.
276 svn_boolean_t aborted_read;
277 apr_off_t aborted_read_size;
279 /* This is the amount of data that we have read so far. */
282 /* If we're receiving an svndiff, this will be non-NULL. */
283 svn_stream_t *delta_stream;
285 /* If we're writing this file to a stream, this will be non-NULL. */
286 svn_stream_t *target_stream;
288 /* Are we done fetching this file? */
291 /* Discard the rest of the content? */
292 svn_boolean_t discard;
294 svn_ra_serf__list_t **done_list;
295 svn_ra_serf__list_t done_item;
300 * The master structure for a REPORT request and response.
302 struct report_context_t {
305 svn_ra_serf__session_t *sess;
306 svn_ra_serf__connection_t *conn;
308 /* Source path and destination path */
310 const char *destination;
312 /* Our update target. */
313 const char *update_target;
315 /* What is the target revision that we want for this REPORT? */
316 svn_revnum_t target_rev;
318 /* Have we been asked to ignore ancestry or textdeltas? */
319 svn_boolean_t ignore_ancestry;
320 svn_boolean_t text_deltas;
322 /* Do we want the server to send copyfrom args or not? */
323 svn_boolean_t send_copyfrom_args;
325 /* Is the server sending everything in one response? */
326 svn_boolean_t send_all_mode;
328 /* Is the server including properties inline for newly added
330 svn_boolean_t add_props_included;
332 /* Path -> const char *repos_relpath mapping */
333 apr_hash_t *switched_paths;
335 /* Boolean indicating whether "" is switched.
336 (This indicates that the we are updating a single file) */
337 svn_boolean_t root_is_switched;
339 /* Our master update editor and baton. */
340 const svn_delta_editor_t *update_editor;
343 /* The file holding request body for the REPORT.
345 * ### todo: It will be better for performance to store small
346 * request bodies (like 4k) in memory and bigger bodies on disk.
348 apr_file_t *body_file;
350 /* root directory object */
351 report_dir_t *root_dir;
353 /* number of pending GET requests */
354 unsigned int num_active_fetches;
356 /* completed fetches (contains report_fetch_t) */
357 svn_ra_serf__list_t *done_fetches;
359 /* number of pending PROPFIND requests */
360 unsigned int num_active_propfinds;
362 /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */
363 svn_ra_serf__list_t *done_propfinds;
364 svn_ra_serf__list_t *done_dir_propfinds;
366 /* list of outstanding prop changes (contains report_dir_t) */
367 svn_ra_serf__list_t *active_dir_propfinds;
369 /* list of files that only have prop changes (contains report_info_t) */
370 svn_ra_serf__list_t *file_propchanges_only;
372 /* The path to the REPORT request */
375 /* Are we done parsing the REPORT response? */
378 /* Did we receive all data from the network? */
379 svn_boolean_t report_received;
381 /* Did we get a complete (non-truncated) report? */
382 svn_boolean_t report_completed;
384 /* The XML parser context for the REPORT response. */
385 svn_ra_serf__xml_parser_t *parser_ctx;
387 /* Did we close the root directory? */
388 svn_boolean_t closed_root;
395 #define S_ SVN_XML_NAMESPACE
396 static const svn_ra_serf__xml_transition_t update_ttable[] = {
397 { INITIAL, S_, "update-report", UPDATE_REPORT,
398 FALSE, { NULL }, FALSE },
400 { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
401 FALSE, { "rev", NULL }, TRUE },
403 { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
404 FALSE, { "rev", NULL }, TRUE },
406 { OPEN_DIR, S_, "open-directory", OPEN_DIR,
407 FALSE, { "rev", "name", NULL }, TRUE },
409 { OPEN_DIR, S_, "add-directory", ADD_DIR,
410 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
412 { ADD_DIR, S_, "add-directory", ADD_DIR,
413 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
415 { OPEN_DIR, S_, "open-file", OPEN_FILE,
416 FALSE, { "rev", "name", NULL }, TRUE },
418 { OPEN_DIR, S_, "add-file", ADD_FILE,
419 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
421 { ADD_DIR, S_, "add-file", ADD_FILE,
422 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
424 { OPEN_DIR, S_, "delete-entry", OPEN_FILE,
425 FALSE, { "?rev", "name", NULL }, TRUE },
427 { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
428 FALSE, { "name", NULL }, TRUE },
430 { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
431 FALSE, { "name", NULL }, TRUE },
433 { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
434 FALSE, { "name", NULL }, TRUE },
436 { ADD_DIR, S_, "absent-file", ABSENT_FILE,
437 FALSE, { "name", NULL }, TRUE },
444 /* Conforms to svn_ra_serf__xml_opened_t */
446 update_opened(svn_ra_serf__xml_estate_t *xes,
449 const svn_ra_serf__dav_props_t *tag,
450 apr_pool_t *scratch_pool)
452 report_context_t *ctx = baton;
459 /* Conforms to svn_ra_serf__xml_closed_t */
461 update_closed(svn_ra_serf__xml_estate_t *xes,
464 const svn_string_t *cdata,
466 apr_pool_t *scratch_pool)
468 report_context_t *ctx = baton;
470 if (leaving_state == TARGET_REVISION)
472 const char *rev = svn_hash_gets(attrs, "rev");
474 SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
483 /* Conforms to svn_ra_serf__xml_cdata_t */
485 update_cdata(svn_ra_serf__xml_estate_t *xes,
490 apr_pool_t *scratch_pool)
492 report_context_t *ctx = baton;
497 #endif /* NOT_USED_YET */
500 /* Returns best connection for fetching files/properties. */
501 static svn_ra_serf__connection_t *
502 get_best_connection(report_context_t *ctx)
504 svn_ra_serf__connection_t *conn;
507 /* Skip the first connection if the REPORT response hasn't been completely
508 received yet or if we're being told to limit our connections to
509 2 (because this could be an attempt to ensure that we do all our
510 auxiliary GETs/PROPFINDs on a single connection).
512 ### FIXME: This latter requirement (max_connections > 2) is
513 ### really just a hack to work around the fact that some update
514 ### editor implementations (such as svnrdump's dump editor)
515 ### simply can't handle the way ra_serf violates the editor v1
516 ### drive ordering requirements.
518 ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
520 if (ctx->report_received && (ctx->sess->max_connections > 2))
523 /* Currently, we just cycle connections. In the future we could
524 store the number of pending requests on each connection, or
525 perform other heuristics, to achieve better connection usage.
526 (As an optimization, if there's only one available auxiliary
527 connection to use, don't bother doing all the cur_conn math --
528 just return that one connection.) */
529 if (ctx->sess->num_conns - first_conn == 1)
531 conn = ctx->sess->conns[first_conn];
535 conn = ctx->sess->conns[ctx->sess->cur_conn];
536 ctx->sess->cur_conn++;
537 if (ctx->sess->cur_conn >= ctx->sess->num_conns)
538 ctx->sess->cur_conn = first_conn;
544 /** Report state management helper **/
546 static report_info_t *
547 push_state(svn_ra_serf__xml_parser_t *parser,
548 report_context_t *ctx,
549 report_state_e state)
552 apr_pool_t *info_parent_pool;
554 svn_ra_serf__xml_push_state(parser, state);
556 info = parser->state->private;
558 /* Our private pool needs to be disjoint from the state pool. */
561 info_parent_pool = ctx->pool;
565 info_parent_pool = info->pool;
568 if (state == OPEN_DIR || state == ADD_DIR)
570 report_info_t *new_info;
572 new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
573 new_info->pool = svn_pool_create(info_parent_pool);
574 new_info->lock_token = NULL;
575 new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
577 new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
578 new_info->dir->pool = new_info->pool;
580 /* Create the root property tree. */
581 new_info->dir->props = apr_hash_make(new_info->pool);
582 new_info->props = new_info->dir->props;
583 new_info->dir->removed_props = apr_hash_make(new_info->pool);
585 new_info->dir->report_context = ctx;
589 info->dir->ref_count++;
591 new_info->dir->parent_dir = info->dir;
593 /* Point our ns_list at our parents to try to reuse it. */
594 new_info->dir->ns_list = info->dir->ns_list;
596 /* Add ourselves to our parent's list */
597 new_info->dir->sibling = info->dir->children;
598 info->dir->children = new_info->dir;
602 /* Allow us to be found later. */
603 ctx->root_dir = new_info->dir;
606 parser->state->private = new_info;
608 else if (state == OPEN_FILE || state == ADD_FILE)
610 report_info_t *new_info;
612 new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
613 new_info->pool = svn_pool_create(info_parent_pool);
614 new_info->file_baton = NULL;
615 new_info->lock_token = NULL;
616 new_info->fetch_file = FALSE;
617 new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
619 /* Point at our parent's directory state. */
620 new_info->dir = info->dir;
621 info->dir->ref_count++;
623 new_info->props = apr_hash_make(new_info->pool);
625 parser->state->private = new_info;
628 return parser->state->private;
632 /** Wrappers around our various property walkers **/
635 set_file_props(void *baton,
638 const svn_string_t *val,
639 apr_pool_t *scratch_pool)
641 report_info_t *info = baton;
642 const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
643 const char *prop_name;
645 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
646 if (prop_name != NULL)
647 return svn_error_trace(editor->change_file_prop(info->file_baton,
656 set_dir_props(void *baton,
659 const svn_string_t *val,
660 apr_pool_t *scratch_pool)
662 report_dir_t *dir = baton;
663 const svn_delta_editor_t *editor = dir->report_context->update_editor;
664 const char *prop_name;
666 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
667 if (prop_name != NULL)
668 return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
677 remove_file_props(void *baton,
680 const svn_string_t *val,
681 apr_pool_t *scratch_pool)
683 report_info_t *info = baton;
684 const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
685 const char *prop_name;
687 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
688 if (prop_name != NULL)
689 return svn_error_trace(editor->change_file_prop(info->file_baton,
698 remove_dir_props(void *baton,
701 const svn_string_t *val,
702 apr_pool_t *scratch_pool)
704 report_dir_t *dir = baton;
705 const svn_delta_editor_t *editor = dir->report_context->update_editor;
706 const char *prop_name;
708 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
709 if (prop_name != NULL)
710 return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
718 /** Helpers to open and close directories */
721 ensure_dir_opened(report_dir_t *dir)
723 report_context_t *ctx = dir->report_context;
725 /* if we're already open, return now */
731 if (dir->base_name[0] == '\0')
733 dir->dir_baton_pool = svn_pool_create(dir->pool);
736 && ctx->sess->wc_callbacks->invalidate_wc_props)
738 SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
739 ctx->sess->wc_callback_baton,
741 SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
744 SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev,
750 SVN_ERR(ensure_dir_opened(dir->parent_dir));
752 dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool);
754 if (SVN_IS_VALID_REVNUM(dir->base_rev))
756 SVN_ERR(ctx->update_editor->open_directory(dir->name,
757 dir->parent_dir->dir_baton,
764 SVN_ERR(ctx->update_editor->add_directory(dir->name,
765 dir->parent_dir->dir_baton,
766 NULL, SVN_INVALID_REVNUM,
776 close_dir(report_dir_t *dir)
779 report_dir_t *sibling;
781 /* ### is there a better pool... this is tossed at end-of-func */
782 apr_pool_t *scratch_pool = dir->dir_baton_pool;
784 SVN_ERR_ASSERT(! dir->ref_count);
786 SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name,
791 SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name,
792 dir->base_rev, remove_dir_props, dir,
795 if (dir->fetch_props)
797 SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url,
798 dir->report_context->target_rev,
803 SVN_ERR(dir->report_context->update_editor->close_directory(
804 dir->dir_baton, scratch_pool));
806 /* remove us from our parent's children list */
810 sibling = dir->parent_dir->children;
812 while (sibling != dir)
815 sibling = sibling->sibling;
817 SVN_ERR_MALFUNCTION();
822 dir->parent_dir->children = dir->sibling;
826 prev->sibling = dir->sibling;
830 svn_pool_destroy(dir->dir_baton_pool);
831 svn_pool_destroy(dir->pool);
836 static svn_error_t *close_all_dirs(report_dir_t *dir)
838 while (dir->children)
840 SVN_ERR(close_all_dirs(dir->children));
844 SVN_ERR_ASSERT(! dir->ref_count);
846 SVN_ERR(ensure_dir_opened(dir));
848 return close_dir(dir);
852 /** Routines called when we are fetching a file */
854 /* This function works around a bug in some older versions of
855 * mod_dav_svn in that it will not send remove-prop in the update
856 * report when a lock property disappears when send-all is false.
858 * Therefore, we'll try to look at our properties and see if there's
859 * an active lock. If not, then we'll assume there isn't a lock
863 check_lock(report_info_t *info)
865 const char *lock_val;
867 lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
868 info->dir->report_context->target_rev,
869 "DAV:", "lockdiscovery");
874 new_lock = apr_pstrdup(info->editor_pool, lock_val);
875 apr_collapse_spaces(new_lock, new_lock);
879 if (!lock_val || lock_val[0] == '\0')
883 str = svn_string_ncreate("", 1, info->editor_pool);
885 svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name,
886 info->base_rev, "DAV:", "lock-token",
887 str, info->dir->pool);
892 headers_fetch(serf_bucket_t *headers,
896 report_fetch_t *fetch_ctx = baton;
898 /* note that we have old VC URL */
899 if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) &&
900 fetch_ctx->info->delta_base)
902 serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
903 fetch_ctx->info->delta_base);
904 serf_bucket_headers_setn(headers, "Accept-Encoding",
905 "svndiff1;q=0.9,svndiff;q=0.8");
907 else if (fetch_ctx->sess->using_compression)
909 serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
916 cancel_fetch(serf_request_t *request,
917 serf_bucket_t *response,
921 report_fetch_t *fetch_ctx = baton;
923 /* Uh-oh. Our connection died on us.
925 * The core ra_serf layer will requeue our request - we just need to note
926 * that we got cut off in the middle of our song.
930 /* If we already started the fetch and opened the file handle, we need
931 * to hold subsequent read() ops until we get back to where we were
932 * before the close and we can then resume the textdelta() calls.
934 if (fetch_ctx->read_headers)
936 if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
938 fetch_ctx->aborted_read = TRUE;
939 fetch_ctx->aborted_read_size = fetch_ctx->read_size;
941 fetch_ctx->read_size = 0;
947 /* We have no idea what went wrong. */
948 SVN_ERR_MALFUNCTION();
952 error_fetch(serf_request_t *request,
953 report_fetch_t *fetch_ctx,
956 fetch_ctx->done = TRUE;
958 fetch_ctx->done_item.data = fetch_ctx;
959 fetch_ctx->done_item.next = *fetch_ctx->done_list;
960 *fetch_ctx->done_list = &fetch_ctx->done_item;
962 /* Discard the rest of this request
963 (This makes sure it doesn't error when the request is aborted later) */
964 serf_request_set_handler(request,
965 svn_ra_serf__response_discard_handler, NULL);
967 /* Some errors would be handled by serf; make sure they really make
968 the update fail by wrapping it in a different error. */
969 if (!SERF_BUCKET_READ_ERROR(err->apr_err))
970 return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
975 /* Wield the editor referenced by INFO to open (or add) the file
976 file also associated with INFO, setting properties on the file and
977 calling the editor's apply_textdelta() function on it if necessary
978 (or if FORCE_APPLY_TEXTDELTA is set).
980 Callers will probably want to also see the function that serves
981 the opposite purpose of this one, close_updated_file(). */
983 open_updated_file(report_info_t *info,
984 svn_boolean_t force_apply_textdelta,
985 apr_pool_t *scratch_pool)
987 report_context_t *ctx = info->dir->report_context;
988 const svn_delta_editor_t *update_editor = ctx->update_editor;
990 /* Ensure our parent is open. */
991 SVN_ERR(ensure_dir_opened(info->dir));
992 info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
994 /* Expand our full name now if we haven't done so yet. */
997 info->name = svn_relpath_join(info->dir->name, info->base_name,
1001 /* Open (or add) the file. */
1002 if (SVN_IS_VALID_REVNUM(info->base_rev))
1004 SVN_ERR(update_editor->open_file(info->name,
1005 info->dir->dir_baton,
1008 &info->file_baton));
1012 SVN_ERR(update_editor->add_file(info->name,
1013 info->dir->dir_baton,
1014 info->copyfrom_path,
1017 &info->file_baton));
1020 /* Check for lock information. */
1021 if (info->lock_token)
1024 /* Get (maybe) a textdelta window handler for transmitting file
1026 if (info->fetch_file || force_apply_textdelta)
1028 SVN_ERR(update_editor->apply_textdelta(info->file_baton,
1029 info->base_checksum,
1032 &info->textdelta_baton));
1035 return SVN_NO_ERROR;
1038 /* Close the file associated with INFO->file_baton, and cleanup other
1039 bits of that structure managed by open_updated_file(). */
1040 static svn_error_t *
1041 close_updated_file(report_info_t *info,
1042 apr_pool_t *scratch_pool)
1044 report_context_t *ctx = info->dir->report_context;
1046 /* Set all of the properties we received */
1047 SVN_ERR(svn_ra_serf__walk_all_props(info->props,
1050 set_file_props, info,
1052 SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
1055 remove_file_props, info,
1057 if (info->fetch_props)
1059 SVN_ERR(svn_ra_serf__walk_all_props(info->props,
1062 set_file_props, info,
1066 /* Close the file via the editor. */
1067 SVN_ERR(info->dir->report_context->update_editor->close_file(
1068 info->file_baton, info->final_checksum, scratch_pool));
1070 /* We're done with our editor pool. */
1071 svn_pool_destroy(info->editor_pool);
1073 return SVN_NO_ERROR;
1076 /* Implements svn_ra_serf__response_handler_t */
1077 static svn_error_t *
1078 handle_fetch(serf_request_t *request,
1079 serf_bucket_t *response,
1080 void *handler_baton,
1085 apr_status_t status;
1086 report_fetch_t *fetch_ctx = handler_baton;
1089 /* ### new field. make sure we didn't miss some initialization. */
1090 SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1092 if (!fetch_ctx->read_headers)
1094 serf_bucket_t *hdrs;
1096 report_info_t *info;
1098 hdrs = serf_bucket_response_get_headers(response);
1099 val = serf_bucket_headers_get(hdrs, "Content-Type");
1100 info = fetch_ctx->info;
1102 if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
1104 fetch_ctx->delta_stream =
1105 svn_txdelta_parse_svndiff(info->textdelta,
1106 info->textdelta_baton,
1107 TRUE, info->editor_pool);
1109 /* Validate the delta base claimed by the server matches
1110 what we asked for! */
1111 val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
1112 if (val && (strcmp(val, info->delta_base) != 0))
1114 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1115 _("GET request returned unexpected "
1116 "delta base: %s"), val);
1117 return error_fetch(request, fetch_ctx, err);
1122 fetch_ctx->delta_stream = NULL;
1125 fetch_ctx->read_headers = TRUE;
1128 /* If the error code wasn't 200, something went wrong. Don't use the returned
1129 data as its probably an error message. Just bail out instead. */
1130 if (fetch_ctx->handler->sline.code != 200)
1132 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1133 _("GET request failed: %d %s"),
1134 fetch_ctx->handler->sline.code,
1135 fetch_ctx->handler->sline.reason);
1136 return error_fetch(request, fetch_ctx, err);
1141 svn_txdelta_window_t delta_window = { 0 };
1142 svn_txdelta_op_t delta_op;
1143 svn_string_t window_data;
1145 status = serf_bucket_read(response, 8000, &data, &len);
1146 if (SERF_BUCKET_READ_ERROR(status))
1148 return svn_ra_serf__wrap_err(status, NULL);
1151 fetch_ctx->read_size += len;
1153 if (fetch_ctx->aborted_read)
1156 /* We haven't caught up to where we were before. */
1157 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1159 /* Eek. What did the file shrink or something? */
1160 if (APR_STATUS_IS_EOF(status))
1162 SVN_ERR_MALFUNCTION();
1165 /* Skip on to the next iteration of this loop. */
1166 if (APR_STATUS_IS_EAGAIN(status))
1168 return svn_ra_serf__wrap_err(status, NULL);
1173 /* Woo-hoo. We're back. */
1174 fetch_ctx->aborted_read = FALSE;
1176 /* Update data and len to just provide the new data. */
1177 skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
1182 if (fetch_ctx->delta_stream)
1184 err = svn_stream_write(fetch_ctx->delta_stream, data, &len);
1187 return error_fetch(request, fetch_ctx, err);
1190 /* otherwise, manually construct the text delta window. */
1193 window_data.data = data;
1194 window_data.len = len;
1196 delta_op.action_code = svn_txdelta_new;
1197 delta_op.offset = 0;
1198 delta_op.length = len;
1200 delta_window.tview_len = len;
1201 delta_window.num_ops = 1;
1202 delta_window.ops = &delta_op;
1203 delta_window.new_data = &window_data;
1205 /* write to the file located in the info. */
1206 err = fetch_ctx->info->textdelta(&delta_window,
1207 fetch_ctx->info->textdelta_baton);
1210 return error_fetch(request, fetch_ctx, err);
1214 if (APR_STATUS_IS_EOF(status))
1216 report_info_t *info = fetch_ctx->info;
1218 if (fetch_ctx->delta_stream)
1219 err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream));
1221 err = svn_error_trace(info->textdelta(NULL,
1222 info->textdelta_baton));
1225 return error_fetch(request, fetch_ctx, err);
1228 err = close_updated_file(info, info->pool);
1231 return svn_error_trace(error_fetch(request, fetch_ctx, err));
1234 fetch_ctx->done = TRUE;
1236 fetch_ctx->done_item.data = fetch_ctx;
1237 fetch_ctx->done_item.next = *fetch_ctx->done_list;
1238 *fetch_ctx->done_list = &fetch_ctx->done_item;
1240 /* We're done with our pool. */
1241 svn_pool_destroy(info->pool);
1244 return svn_ra_serf__wrap_err(status, NULL);
1246 if (APR_STATUS_IS_EAGAIN(status))
1248 return svn_ra_serf__wrap_err(status, NULL);
1254 /* Implements svn_ra_serf__response_handler_t */
1255 static svn_error_t *
1256 handle_stream(serf_request_t *request,
1257 serf_bucket_t *response,
1258 void *handler_baton,
1261 report_fetch_t *fetch_ctx = handler_baton;
1263 apr_status_t status;
1265 /* ### new field. make sure we didn't miss some initialization. */
1266 SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1268 err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline,
1269 fetch_ctx->info->name,
1270 fetch_ctx->handler->location);
1273 fetch_ctx->handler->done = TRUE;
1275 err = svn_error_compose_create(
1277 svn_ra_serf__handle_discard_body(request, response, NULL, pool));
1279 return svn_error_trace(err);
1287 status = serf_bucket_read(response, 8000, &data, &len);
1288 if (SERF_BUCKET_READ_ERROR(status))
1290 return svn_ra_serf__wrap_err(status, NULL);
1293 fetch_ctx->read_size += len;
1295 if (fetch_ctx->aborted_read)
1297 /* We haven't caught up to where we were before. */
1298 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1300 /* Eek. What did the file shrink or something? */
1301 if (APR_STATUS_IS_EOF(status))
1303 SVN_ERR_MALFUNCTION();
1306 /* Skip on to the next iteration of this loop. */
1307 if (APR_STATUS_IS_EAGAIN(status))
1309 return svn_ra_serf__wrap_err(status, NULL);
1314 /* Woo-hoo. We're back. */
1315 fetch_ctx->aborted_read = FALSE;
1317 /* Increment data and len by the difference. */
1318 data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1319 len += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1324 apr_size_t written_len;
1328 SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data,
1332 if (APR_STATUS_IS_EOF(status))
1334 fetch_ctx->done = TRUE;
1339 return svn_ra_serf__wrap_err(status, NULL);
1345 /* Close the directory represented by DIR -- and any suitable parents
1346 thereof -- if we are able to do so. This is the case whenever:
1348 - there are no remaining open items within the directory, and
1349 - the directory's XML close tag has been processed (so we know
1350 there are no more children to worry about in the future), and
1352 - we aren't fetching properties for this directory, or
1353 - we've already finished fetching those properties.
1355 static svn_error_t *
1356 maybe_close_dir_chain(report_dir_t *dir)
1358 report_dir_t *cur_dir = dir;
1360 SVN_ERR(ensure_dir_opened(cur_dir));
1363 && !cur_dir->ref_count
1364 && cur_dir->tag_closed
1365 && (!cur_dir->fetch_props || cur_dir->propfind_handler->done))
1367 report_dir_t *parent = cur_dir->parent_dir;
1368 report_context_t *report_context = cur_dir->report_context;
1369 svn_boolean_t propfind_in_done_list = FALSE;
1370 svn_ra_serf__list_t *done_list;
1372 /* Make sure there are no references to this dir in the
1373 active_dir_propfinds list. If there are, don't close the
1374 directory -- which would delete the pool from which the
1375 relevant active_dir_propfinds list item is allocated -- and
1376 of course don't crawl upward to check the parents for
1377 a closure opportunity, either. */
1378 done_list = report_context->active_dir_propfinds;
1381 if (done_list->data == cur_dir)
1383 propfind_in_done_list = TRUE;
1386 done_list = done_list->next;
1388 if (propfind_in_done_list)
1391 SVN_ERR(close_dir(cur_dir));
1394 parent->ref_count--;
1398 report_context->closed_root = TRUE;
1403 return SVN_NO_ERROR;
1406 /* Open the file associated with INFO for editing, pass along any
1407 propchanges we've recorded for it, and then close the file. */
1408 static svn_error_t *
1409 handle_propchange_only(report_info_t *info,
1410 apr_pool_t *scratch_pool)
1412 SVN_ERR(open_updated_file(info, FALSE, scratch_pool));
1413 SVN_ERR(close_updated_file(info, scratch_pool));
1415 /* We're done with our pool. */
1416 svn_pool_destroy(info->pool);
1418 info->dir->ref_count--;
1420 /* See if the parent directory of this file (and perhaps even
1421 parents of that) can be closed now. */
1422 SVN_ERR(maybe_close_dir_chain(info->dir));
1424 return SVN_NO_ERROR;
1427 /* "Fetch" a file whose contents were made available via the
1428 get_wc_contents() callback (as opposed to requiring a GET to the
1429 server), and feed the information through the associated update
1430 editor. In editor-speak, this will add/open the file, transmit any
1431 property changes, handle the contents, and then close the file. */
1432 static svn_error_t *
1433 handle_local_content(report_info_t *info,
1434 apr_pool_t *scratch_pool)
1436 SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta,
1437 info->textdelta_baton, NULL, scratch_pool));
1438 SVN_ERR(svn_stream_close(info->cached_contents));
1439 info->cached_contents = NULL;
1440 SVN_ERR(close_updated_file(info, scratch_pool));
1442 /* We're done with our pool. */
1443 svn_pool_destroy(info->pool);
1445 info->dir->ref_count--;
1447 /* See if the parent directory of this fetched item (and
1448 perhaps even parents of that) can be closed now. */
1449 SVN_ERR(maybe_close_dir_chain(info->dir));
1451 return SVN_NO_ERROR;
1454 /* --------------------------------------------------------- */
1456 static svn_error_t *
1457 fetch_file(report_context_t *ctx, report_info_t *info)
1459 svn_ra_serf__connection_t *conn;
1460 svn_ra_serf__handler_t *handler;
1462 /* What connection should we go on? */
1463 conn = get_best_connection(ctx);
1465 /* If needed, create the PROPFIND to retrieve the file's properties. */
1466 info->propfind_handler = NULL;
1467 if (info->fetch_props)
1469 SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props,
1470 ctx->sess, conn, info->url,
1471 ctx->target_rev, "0", all_props,
1472 &ctx->done_propfinds,
1474 SVN_ERR_ASSERT(info->propfind_handler);
1476 /* Create a serf request for the PROPFIND. */
1477 svn_ra_serf__request_create(info->propfind_handler);
1479 ctx->num_active_propfinds++;
1482 /* If we've been asked to fetch the file or it's an add, do so.
1483 * Otherwise, handle the case where only the properties changed.
1485 if (info->fetch_file && ctx->text_deltas)
1487 svn_stream_t *contents = NULL;
1489 /* Open the file for editing. */
1490 SVN_ERR(open_updated_file(info, FALSE, info->pool));
1492 if (info->textdelta == svn_delta_noop_window_handler)
1494 /* There is nobody looking for an actual stream.
1496 Just report an empty stream instead of fetching
1497 to be ingored data */
1498 info->cached_contents = svn_stream_empty(info->pool);
1500 else if (ctx->sess->wc_callbacks->get_wc_contents
1501 && info->final_sha1_checksum)
1503 svn_error_t *err = NULL;
1504 svn_checksum_t *checksum = NULL;
1506 /* Parse the optional SHA1 checksum (1.7+) */
1507 err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
1508 info->final_sha1_checksum,
1511 /* Okay so far? Let's try to get a stream on some readily
1512 available matching content. */
1513 if (!err && checksum)
1515 err = ctx->sess->wc_callbacks->get_wc_contents(
1516 ctx->sess->wc_callback_baton, &contents,
1517 checksum, info->pool);
1520 info->cached_contents = contents;
1525 /* Meh. Maybe we'll care one day why we're in an
1526 errorful state, but this codepath is optional. */
1527 svn_error_clear(err);
1531 /* If the working copy can provide cached contents for this
1532 file, we don't have to fetch them from the server. */
1533 if (info->cached_contents)
1535 /* If we'll be doing a PROPFIND for this file... */
1536 if (info->propfind_handler)
1538 /* ... then we'll just leave ourselves a little "todo"
1539 about that fact (and we'll deal with the file content
1540 stuff later, after we've handled that PROPFIND
1542 svn_ra_serf__list_t *list_item;
1544 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1545 list_item->data = info;
1546 list_item->next = ctx->file_propchanges_only;
1547 ctx->file_propchanges_only = list_item;
1551 /* Otherwise, if we've no PROPFIND to do, we might as
1552 well take care of those locally accessible file
1554 SVN_ERR(handle_local_content(info, info->pool));
1559 /* Otherwise, we use a GET request for the file's contents. */
1560 report_fetch_t *fetch_ctx;
1562 fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
1563 fetch_ctx->info = info;
1564 fetch_ctx->done_list = &ctx->done_fetches;
1565 fetch_ctx->sess = ctx->sess;
1566 fetch_ctx->conn = conn;
1568 handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
1570 handler->handler_pool = info->dir->pool;
1571 handler->method = "GET";
1572 handler->path = fetch_ctx->info->url;
1574 handler->conn = conn;
1575 handler->session = ctx->sess;
1577 handler->custom_accept_encoding = TRUE;
1578 handler->header_delegate = headers_fetch;
1579 handler->header_delegate_baton = fetch_ctx;
1581 handler->response_handler = handle_fetch;
1582 handler->response_baton = fetch_ctx;
1584 handler->response_error = cancel_fetch;
1585 handler->response_error_baton = fetch_ctx;
1587 fetch_ctx->handler = handler;
1589 svn_ra_serf__request_create(handler);
1591 ctx->num_active_fetches++;
1594 else if (info->propfind_handler)
1596 svn_ra_serf__list_t *list_item;
1598 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1599 list_item->data = info;
1600 list_item->next = ctx->file_propchanges_only;
1601 ctx->file_propchanges_only = list_item;
1605 /* No propfind or GET request. Just handle the prop changes now. */
1606 SVN_ERR(handle_propchange_only(info, info->pool));
1609 if (ctx->num_active_fetches + ctx->num_active_propfinds
1610 > REQUEST_COUNT_TO_PAUSE)
1611 ctx->parser_ctx->paused = TRUE;
1613 return SVN_NO_ERROR;
1617 /** XML callbacks for our update-report response parsing */
1619 static svn_error_t *
1620 start_report(svn_ra_serf__xml_parser_t *parser,
1621 svn_ra_serf__dav_props_t name,
1623 apr_pool_t *scratch_pool)
1625 report_context_t *ctx = parser->user_data;
1626 report_state_e state;
1628 state = parser->state->current_state;
1630 if (state == NONE && strcmp(name.name, "update-report") == 0)
1634 val = svn_xml_get_attr_value("inline-props", attrs);
1635 if (val && (strcmp(val, "true") == 0))
1636 ctx->add_props_included = TRUE;
1638 val = svn_xml_get_attr_value("send-all", attrs);
1639 if (val && (strcmp(val, "true") == 0))
1641 ctx->send_all_mode = TRUE;
1643 /* All properties are included in send-all mode. */
1644 ctx->add_props_included = TRUE;
1647 else if (state == NONE && strcmp(name.name, "target-revision") == 0)
1651 rev = svn_xml_get_attr_value("rev", attrs);
1655 return svn_error_create(
1656 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1657 _("Missing revision attr in target-revision element"));
1660 SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
1661 SVN_STR_TO_REV(rev),
1664 else if (state == NONE && strcmp(name.name, "open-directory") == 0)
1667 report_info_t *info;
1669 rev = svn_xml_get_attr_value("rev", attrs);
1673 return svn_error_create(
1674 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1675 _("Missing revision attr in open-directory element"));
1678 info = push_state(parser, ctx, OPEN_DIR);
1680 info->base_rev = SVN_STR_TO_REV(rev);
1681 info->dir->base_rev = info->base_rev;
1682 info->fetch_props = TRUE;
1684 info->dir->base_name = "";
1685 info->dir->name = "";
1687 info->base_name = info->dir->base_name;
1688 info->name = info->dir->name;
1690 info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, "");
1692 if (!info->dir->repos_relpath)
1693 SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath,
1694 ctx->sess->session_url.path,
1695 ctx->sess, ctx->conn,
1698 else if (state == NONE)
1700 /* do nothing as we haven't seen our valid start tag yet. */
1702 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1703 strcmp(name.name, "open-directory") == 0)
1705 const char *rev, *dirname;
1707 report_info_t *info;
1709 rev = svn_xml_get_attr_value("rev", attrs);
1713 return svn_error_create(
1714 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1715 _("Missing revision attr in open-directory element"));
1718 dirname = svn_xml_get_attr_value("name", attrs);
1722 return svn_error_create(
1723 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1724 _("Missing name attr in open-directory element"));
1727 info = push_state(parser, ctx, OPEN_DIR);
1731 info->base_rev = SVN_STR_TO_REV(rev);
1732 dir->base_rev = info->base_rev;
1734 info->fetch_props = FALSE;
1736 dir->base_name = apr_pstrdup(dir->pool, dirname);
1737 info->base_name = dir->base_name;
1739 /* Expand our name. */
1740 dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
1742 info->name = dir->name;
1744 dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name);
1746 if (!dir->repos_relpath)
1747 dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
1748 dir->base_name, dir->pool);
1750 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1751 strcmp(name.name, "add-directory") == 0)
1753 const char *dir_name, *cf, *cr;
1755 report_info_t *info;
1757 dir_name = svn_xml_get_attr_value("name", attrs);
1760 return svn_error_create(
1761 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1762 _("Missing name attr in add-directory element"));
1764 cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1765 cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1767 info = push_state(parser, ctx, ADD_DIR);
1771 dir->base_name = apr_pstrdup(dir->pool, dir_name);
1772 info->base_name = dir->base_name;
1774 /* Expand our name. */
1775 dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
1777 info->name = dir->name;
1779 info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1780 info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
1782 /* Mark that we don't have a base. */
1783 info->base_rev = SVN_INVALID_REVNUM;
1784 dir->base_rev = info->base_rev;
1786 /* If the server isn't included properties for added items,
1787 we'll need to fetch them ourselves. */
1788 if (! ctx->add_props_included)
1789 dir->fetch_props = TRUE;
1791 dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
1792 dir->base_name, dir->pool);
1794 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1795 strcmp(name.name, "open-file") == 0)
1797 const char *file_name, *rev;
1798 report_info_t *info;
1800 file_name = svn_xml_get_attr_value("name", attrs);
1804 return svn_error_create(
1805 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1806 _("Missing name attr in open-file element"));
1809 rev = svn_xml_get_attr_value("rev", attrs);
1813 return svn_error_create(
1814 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1815 _("Missing revision attr in open-file element"));
1818 info = push_state(parser, ctx, OPEN_FILE);
1820 info->base_rev = SVN_STR_TO_REV(rev);
1821 info->fetch_props = FALSE;
1823 info->base_name = apr_pstrdup(info->pool, file_name);
1826 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1827 strcmp(name.name, "add-file") == 0)
1829 const char *file_name, *cf, *cr;
1830 report_info_t *info;
1832 file_name = svn_xml_get_attr_value("name", attrs);
1833 cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1834 cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1838 return svn_error_create(
1839 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1840 _("Missing name attr in add-file element"));
1843 info = push_state(parser, ctx, ADD_FILE);
1845 info->base_rev = SVN_INVALID_REVNUM;
1847 /* If the server isn't in "send-all" mode, we should expect to
1848 fetch contents for added files. */
1849 if (! ctx->send_all_mode)
1850 info->fetch_file = TRUE;
1852 /* If the server isn't included properties for added items,
1853 we'll need to fetch them ourselves. */
1854 if (! ctx->add_props_included)
1855 info->fetch_props = TRUE;
1857 info->base_name = apr_pstrdup(info->pool, file_name);
1860 info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1861 info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
1863 info->final_sha1_checksum =
1864 svn_xml_get_attr_value("sha1-checksum", attrs);
1865 if (info->final_sha1_checksum)
1866 info->final_sha1_checksum = apr_pstrdup(info->pool,
1867 info->final_sha1_checksum);
1869 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1870 strcmp(name.name, "delete-entry") == 0)
1872 const char *file_name;
1873 const char *rev_str;
1874 report_info_t *info;
1875 apr_pool_t *tmppool;
1876 const char *full_path;
1877 svn_revnum_t delete_rev = SVN_INVALID_REVNUM;
1879 file_name = svn_xml_get_attr_value("name", attrs);
1883 return svn_error_create(
1884 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1885 _("Missing name attr in delete-entry element"));
1888 rev_str = svn_xml_get_attr_value("rev", attrs);
1889 if (rev_str) /* Not available on older repositories! */
1890 delete_rev = SVN_STR_TO_REV(rev_str);
1892 info = parser->state->private;
1894 SVN_ERR(ensure_dir_opened(info->dir));
1896 tmppool = svn_pool_create(info->dir->dir_baton_pool);
1898 full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
1900 SVN_ERR(ctx->update_editor->delete_entry(full_path,
1902 info->dir->dir_baton,
1905 svn_pool_destroy(tmppool);
1907 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1908 strcmp(name.name, "absent-directory") == 0)
1910 const char *file_name;
1911 report_info_t *info;
1913 file_name = svn_xml_get_attr_value("name", attrs);
1917 return svn_error_create(
1918 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1919 _("Missing name attr in absent-directory element"));
1922 info = parser->state->private;
1924 SVN_ERR(ensure_dir_opened(info->dir));
1926 SVN_ERR(ctx->update_editor->absent_directory(
1927 svn_relpath_join(info->name, file_name,
1929 info->dir->dir_baton,
1932 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1933 strcmp(name.name, "absent-file") == 0)
1935 const char *file_name;
1936 report_info_t *info;
1938 file_name = svn_xml_get_attr_value("name", attrs);
1942 return svn_error_create(
1943 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1944 _("Missing name attr in absent-file element"));
1947 info = parser->state->private;
1949 SVN_ERR(ensure_dir_opened(info->dir));
1951 SVN_ERR(ctx->update_editor->absent_file(
1952 svn_relpath_join(info->name, file_name,
1954 info->dir->dir_baton,
1957 else if (state == OPEN_DIR || state == ADD_DIR)
1959 report_info_t *info;
1961 if (strcmp(name.name, "checked-in") == 0)
1963 info = push_state(parser, ctx, IGNORE_PROP_NAME);
1964 info->prop_ns = name.namespace;
1965 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
1966 info->prop_encoding = NULL;
1967 svn_stringbuf_setempty(info->prop_value);
1969 else if (strcmp(name.name, "set-prop") == 0 ||
1970 strcmp(name.name, "remove-prop") == 0)
1972 const char *full_prop_name;
1975 info = push_state(parser, ctx, PROP);
1977 full_prop_name = svn_xml_get_attr_value("name", attrs);
1978 if (!full_prop_name)
1980 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1981 _("Missing name attr in %s element"),
1985 colon = strchr(full_prop_name, ':');
1990 colon = full_prop_name;
1992 info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
1993 colon - full_prop_name);
1994 info->prop_name = apr_pstrdup(parser->state->pool, colon);
1995 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
1996 svn_stringbuf_setempty(info->prop_value);
1998 else if (strcmp(name.name, "prop") == 0)
2000 /* need to fetch it. */
2001 push_state(parser, ctx, NEED_PROP_NAME);
2003 else if (strcmp(name.name, "fetch-props") == 0)
2005 info = parser->state->private;
2007 info->dir->fetch_props = TRUE;
2011 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2012 _("Unknown tag '%s' while at state %d"),
2017 else if (state == OPEN_FILE || state == ADD_FILE)
2019 report_info_t *info;
2021 if (strcmp(name.name, "checked-in") == 0)
2023 info = push_state(parser, ctx, IGNORE_PROP_NAME);
2024 info->prop_ns = name.namespace;
2025 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
2026 info->prop_encoding = NULL;
2027 svn_stringbuf_setempty(info->prop_value);
2029 else if (strcmp(name.name, "prop") == 0)
2031 /* need to fetch it. */
2032 push_state(parser, ctx, NEED_PROP_NAME);
2034 else if (strcmp(name.name, "fetch-props") == 0)
2036 info = parser->state->private;
2038 info->fetch_props = TRUE;
2040 else if (strcmp(name.name, "fetch-file") == 0)
2042 info = parser->state->private;
2043 info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs);
2045 if (info->base_checksum)
2046 info->base_checksum = apr_pstrdup(info->pool, info->base_checksum);
2048 info->final_sha1_checksum =
2049 svn_xml_get_attr_value("sha1-checksum", attrs);
2050 if (info->final_sha1_checksum)
2051 info->final_sha1_checksum = apr_pstrdup(info->pool,
2052 info->final_sha1_checksum);
2054 info->fetch_file = TRUE;
2056 else if (strcmp(name.name, "set-prop") == 0 ||
2057 strcmp(name.name, "remove-prop") == 0)
2059 const char *full_prop_name;
2062 info = push_state(parser, ctx, PROP);
2064 full_prop_name = svn_xml_get_attr_value("name", attrs);
2065 if (!full_prop_name)
2067 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2068 _("Missing name attr in %s element"),
2071 colon = strchr(full_prop_name, ':');
2076 colon = full_prop_name;
2078 info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
2079 colon - full_prop_name);
2080 info->prop_name = apr_pstrdup(parser->state->pool, colon);
2081 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2082 svn_stringbuf_setempty(info->prop_value);
2084 else if (strcmp(name.name, "txdelta") == 0)
2086 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
2087 addition to <fetch-file>s and such) when *not* in
2088 "send-all" mode. As a client, we're smart enough to know
2089 that's wrong, so we'll just ignore these tags. */
2090 if (ctx->send_all_mode)
2092 const svn_delta_editor_t *update_editor = ctx->update_editor;
2094 info = push_state(parser, ctx, TXDELTA);
2096 if (! info->file_baton)
2098 SVN_ERR(open_updated_file(info, FALSE, info->pool));
2101 info->base_checksum = svn_xml_get_attr_value("base-checksum",
2103 SVN_ERR(update_editor->apply_textdelta(info->file_baton,
2104 info->base_checksum,
2107 &info->textdelta_baton));
2108 info->svndiff_decoder = svn_txdelta_parse_svndiff(
2110 info->textdelta_baton,
2112 info->base64_decoder = svn_base64_decode(info->svndiff_decoder,
2118 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2119 _("Unknown tag '%s' while at state %d"),
2123 else if (state == IGNORE_PROP_NAME)
2125 report_info_t *info = push_state(parser, ctx, PROP);
2126 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2128 else if (state == NEED_PROP_NAME)
2130 report_info_t *info;
2132 info = push_state(parser, ctx, PROP);
2134 info->prop_ns = name.namespace;
2135 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
2136 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2137 svn_stringbuf_setempty(info->prop_value);
2140 return SVN_NO_ERROR;
2143 static svn_error_t *
2144 end_report(svn_ra_serf__xml_parser_t *parser,
2145 svn_ra_serf__dav_props_t name,
2146 apr_pool_t *scratch_pool)
2148 report_context_t *ctx = parser->user_data;
2149 report_state_e state;
2151 state = parser->state->current_state;
2155 if (strcmp(name.name, "update-report") == 0)
2157 ctx->report_completed = TRUE;
2161 /* nothing to close yet. */
2162 return SVN_NO_ERROR;
2166 if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
2167 (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0))))
2169 const char *checked_in_url;
2170 report_info_t *info = parser->state->private;
2172 /* We've now closed this directory; note it. */
2173 info->dir->tag_closed = TRUE;
2175 /* go fetch info->file_name from DAV:checked-in */
2177 svn_ra_serf__get_ver_prop(info->dir->props, info->base_name,
2178 info->base_rev, "DAV:", "checked-in");
2180 /* If we were expecting to have the properties and we aren't able to
2183 if (!checked_in_url &&
2184 (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
2186 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2187 _("The REPORT or PROPFIND response did not "
2188 "include the requested checked-in value"));
2191 info->dir->url = checked_in_url;
2193 /* At this point, we should have the checked-in href.
2194 * If needed, create the PROPFIND to retrieve the dir's properties.
2196 if (info->dir->fetch_props)
2198 svn_ra_serf__list_t *list_item;
2200 SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler,
2201 info->dir->props, ctx->sess,
2202 get_best_connection(ctx),
2204 ctx->target_rev, "0",
2206 &ctx->done_dir_propfinds,
2208 SVN_ERR_ASSERT(info->dir->propfind_handler);
2210 /* Create a serf request for the PROPFIND. */
2211 svn_ra_serf__request_create(info->dir->propfind_handler);
2213 ctx->num_active_propfinds++;
2215 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
2216 list_item->data = info->dir;
2217 list_item->next = ctx->active_dir_propfinds;
2218 ctx->active_dir_propfinds = list_item;
2220 if (ctx->num_active_fetches + ctx->num_active_propfinds
2221 > REQUEST_COUNT_TO_PAUSE)
2222 ctx->parser_ctx->paused = TRUE;
2226 info->dir->propfind_handler = NULL;
2229 /* See if this directory (and perhaps even parents of that) can
2230 be closed now. This is likely to be the case only if we
2231 didn't need to contact the server for supplemental
2232 information required to handle any of this directory's
2234 SVN_ERR(maybe_close_dir_chain(info->dir));
2235 svn_ra_serf__xml_pop_state(parser);
2237 else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
2239 report_info_t *info = parser->state->private;
2241 /* Expand our full name now if we haven't done so yet. */
2244 info->name = svn_relpath_join(info->dir->name, info->base_name,
2248 if (info->lock_token && !info->fetch_props)
2249 info->fetch_props = TRUE;
2251 /* If possible, we'd like to fetch only a delta against a
2252 * version of the file we already have in our working copy,
2253 * rather than fetching a fulltext.
2255 * In HTTP v2, we can simply construct the URL we need given the
2256 * repos_relpath and base revision number.
2258 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
2260 const char *repos_relpath;
2262 /* If this file is switched vs the editor root we should provide
2263 its real url instead of the one calculated from the session root.
2265 repos_relpath = svn_hash_gets(ctx->switched_paths, info->name);
2269 if (ctx->root_is_switched)
2271 /* We are updating a direct target (most likely a file)
2272 that is switched vs its parent url */
2273 SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool)
2276 repos_relpath = svn_hash_gets(ctx->switched_paths, "");
2279 repos_relpath = svn_relpath_join(info->dir->repos_relpath,
2280 info->base_name, info->pool);
2283 info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s",
2284 ctx->sess->rev_root_stub,
2286 svn_path_uri_encode(repos_relpath,
2289 else if (ctx->sess->wc_callbacks->get_wc_prop)
2291 /* If we have a WC, we might be able to dive all the way into the WC
2292 * to get the previous URL so we can do a differential GET with the
2295 const svn_string_t *value = NULL;
2296 SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
2297 ctx->sess->wc_callback_baton, info->name,
2298 SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool));
2300 info->delta_base = value ? value->data : NULL;
2303 /* go fetch info->name from DAV:checked-in */
2304 info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
2305 info->base_rev, "DAV:", "checked-in");
2308 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2309 _("The REPORT or PROPFIND response did not "
2310 "include the requested checked-in value"));
2313 /* If the server is in "send-all" mode, we might have opened the
2314 file when we started seeing content for it. If we didn't get
2315 any content for it, we still need to open the file. But in
2316 any case, we can then immediately close it. */
2317 if (ctx->send_all_mode)
2319 if (! info->file_baton)
2321 SVN_ERR(open_updated_file(info, FALSE, info->pool));
2323 SVN_ERR(close_updated_file(info, info->pool));
2324 info->dir->ref_count--;
2326 /* Otherwise, if the server is *not* in "send-all" mode, we
2327 should be at a point where we can queue up any auxiliary
2328 content-fetching requests. */
2331 SVN_ERR(fetch_file(ctx, info));
2334 svn_ra_serf__xml_pop_state(parser);
2336 else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
2338 report_info_t *info = parser->state->private;
2340 /* go fetch info->name from DAV:checked-in */
2341 info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
2342 info->base_rev, "DAV:", "checked-in");
2345 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2346 _("The REPORT or PROPFIND response did not "
2347 "include the requested checked-in value"));
2350 /* If the server is in "send-all" mode, we might have opened the
2351 file when we started seeing content for it. If we didn't get
2352 any content for it, we still need to open the file. But in
2353 any case, we can then immediately close it. */
2354 if (ctx->send_all_mode)
2356 if (! info->file_baton)
2358 SVN_ERR(open_updated_file(info, FALSE, info->pool));
2360 SVN_ERR(close_updated_file(info, info->pool));
2361 info->dir->ref_count--;
2363 /* Otherwise, if the server is *not* in "send-all" mode, we
2364 should be at a point where we can queue up any auxiliary
2365 content-fetching requests. */
2368 SVN_ERR(fetch_file(ctx, info));
2371 svn_ra_serf__xml_pop_state(parser);
2373 else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0)
2375 report_info_t *info = parser->state->private;
2377 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2378 <fetch-file>s and such) when *not* in "send-all" mode. As a
2379 client, we're smart enough to know that's wrong, so when not
2380 in "receiving-all" mode, we'll ignore these tags. */
2381 if (ctx->send_all_mode)
2383 SVN_ERR(svn_stream_close(info->base64_decoder));
2386 svn_ra_serf__xml_pop_state(parser);
2388 else if (state == PROP)
2390 /* We need to move the prop_ns, prop_name, and prop_value into the
2391 * same lifetime as the dir->pool.
2393 svn_ra_serf__ns_t *ns, *ns_name_match;
2394 svn_boolean_t found = FALSE;
2395 report_info_t *info;
2398 const svn_string_t *set_val_str;
2401 info = parser->state->private;
2404 /* We're going to be slightly tricky. We don't care what the ->url
2405 * field is here at this point. So, we're going to stick a single
2406 * copy of the property name inside of the ->url field.
2408 ns_name_match = NULL;
2409 for (ns = dir->ns_list; ns; ns = ns->next)
2411 if (strcmp(ns->namespace, info->prop_ns) == 0)
2414 if (strcmp(ns->url, info->prop_name) == 0)
2424 ns = apr_palloc(dir->pool, sizeof(*ns));
2427 ns->namespace = apr_pstrdup(dir->pool, info->prop_ns);
2431 ns->namespace = ns_name_match->namespace;
2433 ns->url = apr_pstrdup(dir->pool, info->prop_name);
2435 ns->next = dir->ns_list;
2439 if (strcmp(name.name, "remove-prop") != 0)
2441 props = info->props;
2446 props = dir->removed_props;
2448 svn_stringbuf_setempty(info->prop_value);
2451 if (info->prop_encoding)
2453 if (strcmp(info->prop_encoding, "base64") == 0)
2457 /* Don't use morph_info_string cuz we need prop_value to
2459 tmp.data = info->prop_value->data;
2460 tmp.len = info->prop_value->len;
2462 set_val_str = svn_base64_decode_string(&tmp, pool);
2466 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
2468 _("Got unrecognized encoding '%s'"),
2469 info->prop_encoding);
2474 set_val_str = svn_string_create_from_buf(info->prop_value, pool);
2477 svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
2478 ns->namespace, ns->url, set_val_str, pool);
2480 /* Advance handling: if we spotted the md5-checksum property on
2481 the wire, remember it's value. */
2482 if (strcmp(ns->url, "md5-checksum") == 0
2483 && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0)
2484 info->final_checksum = apr_pstrdup(info->pool, set_val_str->data);
2486 svn_ra_serf__xml_pop_state(parser);
2488 else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
2490 svn_ra_serf__xml_pop_state(parser);
2493 return SVN_NO_ERROR;
2496 static svn_error_t *
2497 cdata_report(svn_ra_serf__xml_parser_t *parser,
2500 apr_pool_t *scratch_pool)
2502 report_context_t *ctx = parser->user_data;
2506 if (parser->state->current_state == PROP)
2508 report_info_t *info = parser->state->private;
2510 svn_stringbuf_appendbytes(info->prop_value, data, len);
2512 else if (parser->state->current_state == TXDELTA)
2514 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2515 <fetch-file>s and such) when *not* in "send-all" mode. As a
2516 client, we're smart enough to know that's wrong, so when not
2517 in "receiving-all" mode, we'll ignore these tags. */
2518 if (ctx->send_all_mode)
2520 apr_size_t nlen = len;
2521 report_info_t *info = parser->state->private;
2523 SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen));
2526 /* Short write without associated error? "Can't happen." */
2527 return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
2528 _("Error writing to '%s': unexpected EOF"),
2534 return SVN_NO_ERROR;
2538 /** Editor callbacks given to callers to create request body */
2540 /* Helper to create simple xml tag without attributes. */
2542 make_simple_xml_tag(svn_stringbuf_t **buf_p,
2543 const char *tagname,
2547 svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL);
2548 svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
2549 svn_xml_make_close_tag(buf_p, pool, tagname);
2552 static svn_error_t *
2553 set_path(void *report_baton,
2555 svn_revnum_t revision,
2557 svn_boolean_t start_empty,
2558 const char *lock_token,
2561 report_context_t *report = report_baton;
2562 svn_stringbuf_t *buf = NULL;
2564 svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2565 "rev", apr_ltoa(pool, revision),
2566 "lock-token", lock_token,
2567 "depth", svn_depth_to_word(depth),
2568 "start-empty", start_empty ? "true" : NULL,
2570 svn_xml_escape_cdata_cstring(&buf, path, pool);
2571 svn_xml_make_close_tag(&buf, pool, "S:entry");
2573 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2576 return SVN_NO_ERROR;
2579 static svn_error_t *
2580 delete_path(void *report_baton,
2584 report_context_t *report = report_baton;
2585 svn_stringbuf_t *buf = NULL;
2587 make_simple_xml_tag(&buf, "S:missing", path, pool);
2589 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2592 return SVN_NO_ERROR;
2595 static svn_error_t *
2596 link_path(void *report_baton,
2599 svn_revnum_t revision,
2601 svn_boolean_t start_empty,
2602 const char *lock_token,
2605 report_context_t *report = report_baton;
2606 const char *link, *report_target;
2608 apr_status_t status;
2609 svn_stringbuf_t *buf = NULL;
2611 /* We need to pass in the baseline relative path.
2613 * TODO Confirm that it's on the same server?
2615 status = apr_uri_parse(pool, url, &uri);
2618 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2619 _("Unable to parse URL '%s'"), url);
2622 SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess,
2624 SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess,
2627 link = apr_pstrcat(pool, "/", link, (char *)NULL);
2629 svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2630 "rev", apr_ltoa(pool, revision),
2631 "lock-token", lock_token,
2632 "depth", svn_depth_to_word(depth),
2634 "start-empty", start_empty ? "true" : NULL,
2636 svn_xml_escape_cdata_cstring(&buf, path, pool);
2637 svn_xml_make_close_tag(&buf, pool, "S:entry");
2639 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2642 /* Store the switch roots to allow generating repos_relpaths from just
2643 the working copy paths. (Needed for HTTPv2) */
2644 path = apr_pstrdup(report->pool, path);
2645 svn_hash_sets(report->switched_paths,
2646 path, apr_pstrdup(report->pool, link + 1));
2649 report->root_is_switched = TRUE;
2654 /** Minimum nr. of outstanding requests needed before a new connection is
2656 #define REQS_PER_CONN 8
2658 /** This function creates a new connection for this serf session, but only
2659 * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
2660 * only one main connection open.
2662 static svn_error_t *
2663 open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
2665 /* For each REQS_PER_CONN outstanding requests open a new connection, with
2666 * a minimum of 1 extra connection. */
2667 if (sess->num_conns == 1 ||
2668 ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
2670 int cur = sess->num_conns;
2671 apr_status_t status;
2673 sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
2674 sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
2676 sess->conns[cur]->last_status_code = -1;
2677 sess->conns[cur]->session = sess;
2678 status = serf_connection_create2(&sess->conns[cur]->conn,
2681 svn_ra_serf__conn_setup,
2683 svn_ra_serf__conn_closed,
2687 return svn_ra_serf__wrap_err(status, NULL);
2692 return SVN_NO_ERROR;
2695 /* Serf callback to create update request body bucket. */
2696 static svn_error_t *
2697 create_update_report_body(serf_bucket_t **body_bkt,
2699 serf_bucket_alloc_t *alloc,
2702 report_context_t *report = baton;
2706 apr_file_seek(report->body_file, APR_SET, &offset);
2708 *body_bkt = serf_bucket_file_create(report->body_file, alloc);
2710 return SVN_NO_ERROR;
2713 /* Serf callback to setup update request headers. */
2714 static svn_error_t *
2715 setup_update_report_headers(serf_bucket_t *headers,
2719 report_context_t *report = baton;
2721 if (report->sess->using_compression)
2723 serf_bucket_headers_setn(headers, "Accept-Encoding",
2724 "gzip,svndiff1;q=0.9,svndiff;q=0.8");
2728 serf_bucket_headers_setn(headers, "Accept-Encoding",
2729 "svndiff1;q=0.9,svndiff;q=0.8");
2732 return SVN_NO_ERROR;
2735 static svn_error_t *
2736 finish_report(void *report_baton,
2739 report_context_t *report = report_baton;
2740 svn_ra_serf__session_t *sess = report->sess;
2741 svn_ra_serf__handler_t *handler;
2742 svn_ra_serf__xml_parser_t *parser_ctx;
2743 const char *report_target;
2744 svn_stringbuf_t *buf = NULL;
2745 apr_pool_t *iterpool = svn_pool_create(pool);
2747 apr_interval_time_t waittime_left = sess->timeout;
2749 svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
2750 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2753 /* We need to flush the file, make it unbuffered (so that it can be
2754 * zero-copied via mmap), and reset the position before attempting to
2757 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
2758 * and zero-copy the PUT body. However, on older APR versions, we can't
2759 * check the buffer status; but serf will fall through and create a file
2760 * bucket for us on the buffered svndiff handle.
2762 apr_file_flush(report->body_file);
2763 #if APR_VERSION_AT_LEAST(1, 3, 0)
2764 apr_file_buffer_set(report->body_file, NULL, 0);
2767 SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool));
2769 /* create and deliver request */
2770 report->path = report_target;
2772 handler = apr_pcalloc(pool, sizeof(*handler));
2774 handler->handler_pool = pool;
2775 handler->method = "REPORT";
2776 handler->path = report->path;
2777 handler->body_delegate = create_update_report_body;
2778 handler->body_delegate_baton = report;
2779 handler->body_type = "text/xml";
2780 handler->custom_accept_encoding = TRUE;
2781 handler->header_delegate = setup_update_report_headers;
2782 handler->header_delegate_baton = report;
2783 handler->conn = sess->conns[0];
2784 handler->session = sess;
2786 parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
2788 parser_ctx->pool = pool;
2789 parser_ctx->response_type = "update-report";
2790 parser_ctx->user_data = report;
2791 parser_ctx->start = start_report;
2792 parser_ctx->end = end_report;
2793 parser_ctx->cdata = cdata_report;
2794 parser_ctx->done = &report->done;
2796 handler->response_handler = svn_ra_serf__handle_xml_parser;
2797 handler->response_baton = parser_ctx;
2799 report->parser_ctx = parser_ctx;
2801 svn_ra_serf__request_create(handler);
2803 /* Open the first extra connection. */
2804 SVN_ERR(open_connection_if_needed(sess, 0));
2808 /* Note that we may have no active GET or PROPFIND requests, yet the
2809 processing has not been completed. This could be from a delay on the
2810 network or because we've spooled the entire response into our "pending"
2811 content of the XML parser. The DONE flag will get set when all the
2812 XML content has been received *and* parsed. */
2813 while (!report->done
2814 || report->num_active_fetches
2815 || report->num_active_propfinds)
2817 apr_pool_t *iterpool_inner;
2818 svn_ra_serf__list_t *done_list;
2820 apr_status_t status;
2822 /* Note: this throws out the old ITERPOOL_INNER. */
2823 svn_pool_clear(iterpool);
2825 if (sess->cancel_func)
2826 SVN_ERR(sess->cancel_func(sess->cancel_baton));
2828 /* We need to be careful between the outer and inner ITERPOOLs,
2829 and what items are allocated within. */
2830 iterpool_inner = svn_pool_create(iterpool);
2832 status = serf_context_run(sess->context,
2833 SVN_RA_SERF__CONTEXT_RUN_DURATION,
2836 err = sess->pending_error;
2837 sess->pending_error = SVN_NO_ERROR;
2839 if (!err && handler->done && handler->server_error)
2841 err = handler->server_error->error;
2844 /* If the context duration timeout is up, we'll subtract that
2845 duration from the total time alloted for such things. If
2846 there's no time left, we fail with a message indicating that
2847 the connection timed out. */
2848 if (APR_STATUS_IS_TIMEUP(status))
2850 svn_error_clear(err);
2856 if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
2858 waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
2862 return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
2863 _("Connection timed out"));
2869 waittime_left = sess->timeout;
2872 if (status && handler->sline.code != 200)
2874 return svn_error_trace(
2875 svn_error_compose_create(
2876 svn_ra_serf__error_on_status(handler->sline,
2884 return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
2887 /* Open extra connections if we have enough requests to send. */
2888 if (sess->num_conns < sess->max_connections)
2889 SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches +
2890 report->num_active_propfinds));
2892 /* Prune completed file PROPFINDs. */
2893 done_list = report->done_propfinds;
2896 svn_ra_serf__list_t *next_done = done_list->next;
2898 svn_pool_clear(iterpool_inner);
2900 report->num_active_propfinds--;
2902 /* If we have some files that we won't be fetching the content
2903 * for, ensure that we update the file with any altered props.
2905 if (report->file_propchanges_only)
2907 svn_ra_serf__list_t *cur, *prev;
2910 cur = report->file_propchanges_only;
2914 report_info_t *item = cur->data;
2916 if (item->propfind_handler == done_list->data)
2925 /* If we found a match, set the new props and remove this
2926 * propchange from our list.
2930 report_info_t *info = cur->data;
2934 report->file_propchanges_only = cur->next;
2938 prev->next = cur->next;
2941 /* If we've got cached file content for this file,
2942 take care of the locally collected properties and
2943 file content at once. Otherwise, just deal with
2944 the collected properties.
2946 NOTE: These functions below could delete
2947 info->dir->pool (via maybe_close_dir_chain()),
2948 from which is allocated the list item in
2949 report->file_propchanges_only.
2951 if (info->cached_contents)
2953 SVN_ERR(handle_local_content(info, iterpool_inner));
2957 SVN_ERR(handle_propchange_only(info, iterpool_inner));
2962 done_list = next_done;
2964 report->done_propfinds = NULL;
2966 /* Prune completed fetches from our list. */
2967 done_list = report->done_fetches;
2970 report_fetch_t *done_fetch = done_list->data;
2971 svn_ra_serf__list_t *next_done = done_list->next;
2972 report_dir_t *cur_dir;
2974 /* Decrease the refcount in the parent directory of the file
2975 whose fetch has completed. */
2976 cur_dir = done_fetch->info->dir;
2977 cur_dir->ref_count--;
2979 /* Decrement our active fetch count. */
2980 report->num_active_fetches--;
2982 /* See if the parent directory of this fetched item (and
2983 perhaps even parents of that) can be closed now.
2985 NOTE: This could delete cur_dir->pool, from which is
2986 allocated the list item in report->done_fetches.
2988 SVN_ERR(maybe_close_dir_chain(cur_dir));
2990 done_list = next_done;
2992 report->done_fetches = NULL;
2994 /* Prune completed directory PROPFINDs. */
2995 done_list = report->done_dir_propfinds;
2998 svn_ra_serf__list_t *next_done = done_list->next;
3000 report->num_active_propfinds--;
3002 if (report->active_dir_propfinds)
3004 svn_ra_serf__list_t *cur, *prev;
3007 cur = report->active_dir_propfinds;
3011 report_dir_t *item = cur->data;
3013 if (item->propfind_handler == done_list->data)
3021 SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */
3023 /* If we found a match, set the new props and remove this
3024 * propchange from our list.
3028 report_dir_t *cur_dir = cur->data;
3032 report->active_dir_propfinds = cur->next;
3036 prev->next = cur->next;
3039 /* See if this directory (and perhaps even parents of that)
3042 NOTE: This could delete cur_dir->pool, from which is
3043 allocated the list item in report->active_dir_propfinds.
3045 SVN_ERR(maybe_close_dir_chain(cur_dir));
3049 done_list = next_done;
3051 report->done_dir_propfinds = NULL;
3053 /* If the parser is paused, and the number of active requests has
3054 dropped far enough, then resume parsing. */
3055 if (parser_ctx->paused
3056 && (report->num_active_fetches + report->num_active_propfinds
3057 < REQUEST_COUNT_TO_RESUME))
3058 parser_ctx->paused = FALSE;
3060 /* If we have not paused the parser and it looks like data MAY be
3061 present (we can't know for sure because of the private structure),
3062 then go process the pending content. */
3063 if (!parser_ctx->paused && parser_ctx->pending != NULL)
3064 SVN_ERR(svn_ra_serf__process_pending(parser_ctx,
3065 &report->report_received,
3068 /* Debugging purposes only! */
3069 for (i = 0; i < sess->num_conns; i++)
3071 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
3075 /* If we got a complete report, close the edit. Otherwise, abort it. */
3076 if (report->report_completed)
3078 /* Ensure that we opened and closed our root dir and that we closed
3079 * all of our children. */
3080 if (!report->closed_root && report->root_dir != NULL)
3082 SVN_ERR(close_all_dirs(report->root_dir));
3085 err = report->update_editor->close_edit(report->update_baton, iterpool);
3089 /* Tell the editor that something failed */
3090 err = report->update_editor->abort_edit(report->update_baton, iterpool);
3092 err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
3093 _("Missing update-report close tag"));
3096 svn_pool_destroy(iterpool);
3097 return svn_error_trace(err);
3101 static svn_error_t *
3102 abort_report(void *report_baton,
3106 report_context_t *report = report_baton;
3109 /* Should we perform some cleanup here? */
3111 return SVN_NO_ERROR;
3114 static const svn_ra_reporter3_t ra_serf_reporter = {
3123 /** RA function implementations and body */
3125 static svn_error_t *
3126 make_update_reporter(svn_ra_session_t *ra_session,
3127 const svn_ra_reporter3_t **reporter,
3128 void **report_baton,
3129 svn_revnum_t revision,
3130 const char *src_path,
3131 const char *dest_path,
3132 const char *update_target,
3134 svn_boolean_t ignore_ancestry,
3135 svn_boolean_t text_deltas,
3136 svn_boolean_t send_copyfrom_args,
3137 const svn_delta_editor_t *update_editor,
3139 apr_pool_t *result_pool,
3140 apr_pool_t *scratch_pool)
3142 report_context_t *report;
3143 const svn_delta_editor_t *filter_editor;
3145 svn_boolean_t has_target = *update_target != '\0';
3146 svn_boolean_t server_supports_depth;
3147 svn_ra_serf__session_t *sess = ra_session->priv;
3148 svn_stringbuf_t *buf = NULL;
3149 svn_boolean_t use_bulk_updates;
3151 SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
3152 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
3153 /* We can skip the depth filtering when the user requested
3154 depth_files or depth_infinity because the server will
3155 transmit the right stuff anyway. */
3156 if ((depth != svn_depth_files)
3157 && (depth != svn_depth_infinity)
3158 && ! server_supports_depth)
3160 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
3166 update_editor = filter_editor;
3167 update_baton = filter_baton;
3170 report = apr_pcalloc(result_pool, sizeof(*report));
3171 report->pool = result_pool;
3172 report->sess = sess;
3173 report->conn = report->sess->conns[0];
3174 report->target_rev = revision;
3175 report->ignore_ancestry = ignore_ancestry;
3176 report->send_copyfrom_args = send_copyfrom_args;
3177 report->text_deltas = text_deltas;
3178 report->switched_paths = apr_hash_make(report->pool);
3180 report->source = src_path;
3181 report->destination = dest_path;
3182 report->update_target = update_target;
3184 report->update_editor = update_editor;
3185 report->update_baton = update_baton;
3186 report->done = FALSE;
3188 *reporter = &ra_serf_reporter;
3189 *report_baton = report;
3191 SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL,
3192 svn_io_file_del_on_pool_cleanup,
3193 report->pool, scratch_pool));
3195 if (sess->bulk_updates == svn_tristate_true)
3197 /* User would like to use bulk updates. */
3198 use_bulk_updates = TRUE;
3200 else if (sess->bulk_updates == svn_tristate_false)
3202 /* User doesn't want bulk updates. */
3203 use_bulk_updates = FALSE;
3207 /* User doesn't have any preferences on bulk updates. Decide on server
3208 preferences and capabilities. */
3209 if (sess->server_allows_bulk)
3211 if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
3213 /* Server doesn't want bulk updates */
3214 use_bulk_updates = FALSE;
3216 else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
3218 /* Server prefers bulk updates, and we respect that */
3219 use_bulk_updates = TRUE;
3223 /* Server allows bulk updates, but doesn't dictate its use. Do
3224 whatever is the default. */
3225 use_bulk_updates = FALSE;
3230 /* Pre-1.8 server didn't send the bulk_updates header. Check if server
3231 supports inlining properties in update editor report. */
3232 if (sess->supports_inline_props)
3234 /* Inline props supported: do not use bulk updates. */
3235 use_bulk_updates = FALSE;
3239 /* Inline props are not supported: use bulk updates to avoid
3240 * PROPFINDs for every added node. */
3241 use_bulk_updates = TRUE;
3246 if (use_bulk_updates)
3248 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
3250 "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
3255 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
3257 "xmlns:S", SVN_XML_NAMESPACE,
3259 /* Subversion 1.8+ servers can be told to send properties for newly
3260 added items inline even when doing a skelta response. */
3261 make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
3264 make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
3266 if (SVN_IS_VALID_REVNUM(report->target_rev))
3268 make_simple_xml_tag(&buf, "S:target-revision",
3269 apr_ltoa(scratch_pool, report->target_rev),
3273 if (report->destination && *report->destination)
3275 make_simple_xml_tag(&buf, "S:dst-path", report->destination,
3279 if (report->update_target && *report->update_target)
3281 make_simple_xml_tag(&buf, "S:update-target", report->update_target,
3285 if (report->ignore_ancestry)
3287 make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
3290 if (report->send_copyfrom_args)
3292 make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
3295 /* Old servers know "recursive" but not "depth"; help them DTRT. */
3296 if (depth == svn_depth_files || depth == svn_depth_empty)
3298 make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
3301 /* When in 'send-all' mode, mod_dav_svn will assume that it should
3302 calculate and transmit real text-deltas (instead of empty windows
3303 that merely indicate "text is changed") unless it finds this
3306 NOTE: Do NOT count on servers actually obeying this, as some exist
3307 which obey send-all, but do not check for this directive at all!
3309 NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
3310 override our request and send text-deltas. */
3313 make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
3316 make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
3318 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
3319 NULL, scratch_pool));
3321 return SVN_NO_ERROR;
3325 svn_ra_serf__do_update(svn_ra_session_t *ra_session,
3326 const svn_ra_reporter3_t **reporter,
3327 void **report_baton,
3328 svn_revnum_t revision_to_update_to,
3329 const char *update_target,
3331 svn_boolean_t send_copyfrom_args,
3332 svn_boolean_t ignore_ancestry,
3333 const svn_delta_editor_t *update_editor,
3335 apr_pool_t *result_pool,
3336 apr_pool_t *scratch_pool)
3338 svn_ra_serf__session_t *session = ra_session->priv;
3340 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3341 revision_to_update_to,
3342 session->session_url.path, NULL, update_target,
3343 depth, ignore_ancestry, TRUE /* text_deltas */,
3345 update_editor, update_baton,
3346 result_pool, scratch_pool));
3347 return SVN_NO_ERROR;
3351 svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
3352 const svn_ra_reporter3_t **reporter,
3353 void **report_baton,
3354 svn_revnum_t revision,
3355 const char *diff_target,
3357 svn_boolean_t ignore_ancestry,
3358 svn_boolean_t text_deltas,
3359 const char *versus_url,
3360 const svn_delta_editor_t *diff_editor,
3364 svn_ra_serf__session_t *session = ra_session->priv;
3365 apr_pool_t *scratch_pool = svn_pool_create(pool);
3367 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3369 session->session_url.path, versus_url, diff_target,
3370 depth, ignore_ancestry, text_deltas, FALSE,
3371 diff_editor, diff_baton,
3372 pool, scratch_pool));
3373 svn_pool_destroy(scratch_pool);
3374 return SVN_NO_ERROR;
3378 svn_ra_serf__do_status(svn_ra_session_t *ra_session,
3379 const svn_ra_reporter3_t **reporter,
3380 void **report_baton,
3381 const char *status_target,
3382 svn_revnum_t revision,
3384 const svn_delta_editor_t *status_editor,
3388 svn_ra_serf__session_t *session = ra_session->priv;
3389 apr_pool_t *scratch_pool = svn_pool_create(pool);
3391 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3393 session->session_url.path, NULL, status_target,
3394 depth, FALSE, FALSE, FALSE,
3395 status_editor, status_baton,
3396 pool, scratch_pool));
3397 svn_pool_destroy(scratch_pool);
3398 return SVN_NO_ERROR;
3402 svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
3403 const svn_ra_reporter3_t **reporter,
3404 void **report_baton,
3405 svn_revnum_t revision_to_switch_to,
3406 const char *switch_target,
3408 const char *switch_url,
3409 svn_boolean_t send_copyfrom_args,
3410 svn_boolean_t ignore_ancestry,
3411 const svn_delta_editor_t *switch_editor,
3413 apr_pool_t *result_pool,
3414 apr_pool_t *scratch_pool)
3416 svn_ra_serf__session_t *session = ra_session->priv;
3418 return make_update_reporter(ra_session, reporter, report_baton,
3419 revision_to_switch_to,
3420 session->session_url.path,
3421 switch_url, switch_target,
3424 TRUE /* text_deltas */,
3426 switch_editor, switch_baton,
3427 result_pool, scratch_pool);
3430 /* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
3431 * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
3434 * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
3436 * Performs all temporary allocations in POOL.
3438 static svn_error_t *
3439 try_get_wc_contents(svn_boolean_t *found_p,
3440 svn_ra_serf__session_t *session,
3442 svn_stream_t *dst_stream,
3445 apr_hash_t *svn_props;
3446 const char *sha1_checksum_prop;
3447 svn_checksum_t *checksum;
3448 svn_stream_t *wc_stream;
3451 /* No contents found by default. */
3454 if (!session->wc_callbacks->get_wc_contents)
3456 /* No callback, nothing to do. */
3457 return SVN_NO_ERROR;
3461 svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
3464 /* No properties -- therefore no checksum property -- in response. */
3465 return SVN_NO_ERROR;
3468 sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum");
3469 if (sha1_checksum_prop == NULL)
3471 /* No checksum property in response. */
3472 return SVN_NO_ERROR;
3475 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
3476 sha1_checksum_prop, pool));
3478 err = session->wc_callbacks->get_wc_contents(
3479 session->wc_callback_baton, &wc_stream, checksum, pool);
3483 svn_error_clear(err);
3485 /* Ignore errors for now. */
3486 return SVN_NO_ERROR;
3491 SVN_ERR(svn_stream_copy3(wc_stream,
3492 svn_stream_disown(dst_stream, pool),
3497 return SVN_NO_ERROR;
3501 svn_ra_serf__get_file(svn_ra_session_t *ra_session,
3503 svn_revnum_t revision,
3504 svn_stream_t *stream,
3505 svn_revnum_t *fetched_rev,
3509 svn_ra_serf__session_t *session = ra_session->priv;
3510 svn_ra_serf__connection_t *conn;
3511 const char *fetch_url;
3512 apr_hash_t *fetch_props;
3513 svn_node_kind_t res_kind;
3514 const svn_ra_serf__dav_props_t *which_props;
3516 /* What connection should we go on? */
3517 conn = session->conns[session->cur_conn];
3519 /* Fetch properties. */
3521 fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
3523 /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
3525 * Otherwise, we need to get the baseline version for this particular
3526 * revision and then fetch that file.
3528 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
3530 SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
3532 fetch_url, revision,
3534 revision = SVN_INVALID_REVNUM;
3536 /* REVISION is always SVN_INVALID_REVNUM */
3537 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
3541 which_props = all_props;
3543 else if (stream && session->wc_callbacks->get_wc_contents)
3545 which_props = type_and_checksum_props;
3549 which_props = check_path_props;
3552 SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url,
3557 /* Verify that resource type is not collection. */
3558 SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props));
3559 if (res_kind != svn_node_file)
3561 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
3562 _("Can't get text contents of a directory"));
3565 /* TODO Filter out all of our props into a usable format. */
3568 /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
3569 ### put them into POOL, so we're okay. */
3570 SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props,
3576 svn_boolean_t found;
3577 SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool));
3579 /* No contents found in the WC, let's fetch from server. */
3582 report_fetch_t *stream_ctx;
3583 svn_ra_serf__handler_t *handler;
3585 /* Create the fetch context. */
3586 stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
3587 stream_ctx->target_stream = stream;
3588 stream_ctx->sess = session;
3589 stream_ctx->conn = conn;
3590 stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
3591 stream_ctx->info->name = fetch_url;
3593 handler = apr_pcalloc(pool, sizeof(*handler));
3595 handler->handler_pool = pool;
3596 handler->method = "GET";
3597 handler->path = fetch_url;
3598 handler->conn = conn;
3599 handler->session = session;
3601 handler->custom_accept_encoding = TRUE;
3602 handler->header_delegate = headers_fetch;
3603 handler->header_delegate_baton = stream_ctx;
3605 handler->response_handler = handle_stream;
3606 handler->response_baton = stream_ctx;
3608 handler->response_error = cancel_fetch;
3609 handler->response_error_baton = stream_ctx;
3611 stream_ctx->handler = handler;
3613 svn_ra_serf__request_create(handler);
3615 SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
3619 return SVN_NO_ERROR;