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 -> lock token mapping. */
333 apr_hash_t *lock_path_tokens;
335 /* Path -> const char *repos_relpath mapping */
336 apr_hash_t *switched_paths;
338 /* Boolean indicating whether "" is switched.
339 (This indicates that the we are updating a single file) */
340 svn_boolean_t root_is_switched;
342 /* Our master update editor and baton. */
343 const svn_delta_editor_t *update_editor;
346 /* The file holding request body for the REPORT.
348 * ### todo: It will be better for performance to store small
349 * request bodies (like 4k) in memory and bigger bodies on disk.
351 apr_file_t *body_file;
353 /* root directory object */
354 report_dir_t *root_dir;
356 /* number of pending GET requests */
357 unsigned int num_active_fetches;
359 /* completed fetches (contains report_fetch_t) */
360 svn_ra_serf__list_t *done_fetches;
362 /* number of pending PROPFIND requests */
363 unsigned int num_active_propfinds;
365 /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */
366 svn_ra_serf__list_t *done_propfinds;
367 svn_ra_serf__list_t *done_dir_propfinds;
369 /* list of outstanding prop changes (contains report_dir_t) */
370 svn_ra_serf__list_t *active_dir_propfinds;
372 /* list of files that only have prop changes (contains report_info_t) */
373 svn_ra_serf__list_t *file_propchanges_only;
375 /* The path to the REPORT request */
378 /* Are we done parsing the REPORT response? */
381 /* Did we receive all data from the network? */
382 svn_boolean_t report_received;
384 /* Did we get a complete (non-truncated) report? */
385 svn_boolean_t report_completed;
387 /* The XML parser context for the REPORT response. */
388 svn_ra_serf__xml_parser_t *parser_ctx;
390 /* Did we close the root directory? */
391 svn_boolean_t closed_root;
398 #define S_ SVN_XML_NAMESPACE
399 static const svn_ra_serf__xml_transition_t update_ttable[] = {
400 { INITIAL, S_, "update-report", UPDATE_REPORT,
401 FALSE, { NULL }, FALSE },
403 { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
404 FALSE, { "rev", NULL }, TRUE },
406 { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
407 FALSE, { "rev", NULL }, TRUE },
409 { OPEN_DIR, S_, "open-directory", OPEN_DIR,
410 FALSE, { "rev", "name", NULL }, TRUE },
412 { OPEN_DIR, S_, "add-directory", ADD_DIR,
413 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
415 { ADD_DIR, S_, "add-directory", ADD_DIR,
416 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
418 { OPEN_DIR, S_, "open-file", OPEN_FILE,
419 FALSE, { "rev", "name", NULL }, TRUE },
421 { OPEN_DIR, S_, "add-file", ADD_FILE,
422 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
424 { ADD_DIR, S_, "add-file", ADD_FILE,
425 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
427 { OPEN_DIR, S_, "delete-entry", OPEN_FILE,
428 FALSE, { "?rev", "name", NULL }, TRUE },
430 { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
431 FALSE, { "name", NULL }, TRUE },
433 { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
434 FALSE, { "name", NULL }, TRUE },
436 { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
437 FALSE, { "name", NULL }, TRUE },
439 { ADD_DIR, S_, "absent-file", ABSENT_FILE,
440 FALSE, { "name", NULL }, TRUE },
447 /* Conforms to svn_ra_serf__xml_opened_t */
449 update_opened(svn_ra_serf__xml_estate_t *xes,
452 const svn_ra_serf__dav_props_t *tag,
453 apr_pool_t *scratch_pool)
455 report_context_t *ctx = baton;
462 /* Conforms to svn_ra_serf__xml_closed_t */
464 update_closed(svn_ra_serf__xml_estate_t *xes,
467 const svn_string_t *cdata,
469 apr_pool_t *scratch_pool)
471 report_context_t *ctx = baton;
473 if (leaving_state == TARGET_REVISION)
475 const char *rev = svn_hash_gets(attrs, "rev");
477 SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
486 /* Conforms to svn_ra_serf__xml_cdata_t */
488 update_cdata(svn_ra_serf__xml_estate_t *xes,
493 apr_pool_t *scratch_pool)
495 report_context_t *ctx = baton;
500 #endif /* NOT_USED_YET */
503 /* Returns best connection for fetching files/properties. */
504 static svn_ra_serf__connection_t *
505 get_best_connection(report_context_t *ctx)
507 svn_ra_serf__connection_t *conn;
510 /* Skip the first connection if the REPORT response hasn't been completely
511 received yet or if we're being told to limit our connections to
512 2 (because this could be an attempt to ensure that we do all our
513 auxiliary GETs/PROPFINDs on a single connection).
515 ### FIXME: This latter requirement (max_connections > 2) is
516 ### really just a hack to work around the fact that some update
517 ### editor implementations (such as svnrdump's dump editor)
518 ### simply can't handle the way ra_serf violates the editor v1
519 ### drive ordering requirements.
521 ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
523 if (ctx->report_received && (ctx->sess->max_connections > 2))
526 /* Currently, we just cycle connections. In the future we could
527 store the number of pending requests on each connection, or
528 perform other heuristics, to achieve better connection usage.
529 (As an optimization, if there's only one available auxiliary
530 connection to use, don't bother doing all the cur_conn math --
531 just return that one connection.) */
532 if (ctx->sess->num_conns - first_conn == 1)
534 conn = ctx->sess->conns[first_conn];
538 conn = ctx->sess->conns[ctx->sess->cur_conn];
539 ctx->sess->cur_conn++;
540 if (ctx->sess->cur_conn >= ctx->sess->num_conns)
541 ctx->sess->cur_conn = first_conn;
547 /** Report state management helper **/
549 static report_info_t *
550 push_state(svn_ra_serf__xml_parser_t *parser,
551 report_context_t *ctx,
552 report_state_e state)
555 apr_pool_t *info_parent_pool;
557 svn_ra_serf__xml_push_state(parser, state);
559 info = parser->state->private;
561 /* Our private pool needs to be disjoint from the state pool. */
564 info_parent_pool = ctx->pool;
568 info_parent_pool = info->pool;
571 if (state == OPEN_DIR || state == ADD_DIR)
573 report_info_t *new_info;
575 new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
576 new_info->pool = svn_pool_create(info_parent_pool);
577 new_info->lock_token = NULL;
578 new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
580 new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
581 new_info->dir->pool = new_info->pool;
583 /* Create the root property tree. */
584 new_info->dir->props = apr_hash_make(new_info->pool);
585 new_info->props = new_info->dir->props;
586 new_info->dir->removed_props = apr_hash_make(new_info->pool);
588 new_info->dir->report_context = ctx;
592 info->dir->ref_count++;
594 new_info->dir->parent_dir = info->dir;
596 /* Point our ns_list at our parents to try to reuse it. */
597 new_info->dir->ns_list = info->dir->ns_list;
599 /* Add ourselves to our parent's list */
600 new_info->dir->sibling = info->dir->children;
601 info->dir->children = new_info->dir;
605 /* Allow us to be found later. */
606 ctx->root_dir = new_info->dir;
609 parser->state->private = new_info;
611 else if (state == OPEN_FILE || state == ADD_FILE)
613 report_info_t *new_info;
615 new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
616 new_info->pool = svn_pool_create(info_parent_pool);
617 new_info->file_baton = NULL;
618 new_info->lock_token = NULL;
619 new_info->fetch_file = FALSE;
620 new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
622 /* Point at our parent's directory state. */
623 new_info->dir = info->dir;
624 info->dir->ref_count++;
626 new_info->props = apr_hash_make(new_info->pool);
628 parser->state->private = new_info;
631 return parser->state->private;
635 /** Wrappers around our various property walkers **/
638 set_file_props(void *baton,
641 const svn_string_t *val,
642 apr_pool_t *scratch_pool)
644 report_info_t *info = baton;
645 const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
646 const char *prop_name;
648 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
649 if (prop_name != NULL)
650 return svn_error_trace(editor->change_file_prop(info->file_baton,
659 set_dir_props(void *baton,
662 const svn_string_t *val,
663 apr_pool_t *scratch_pool)
665 report_dir_t *dir = baton;
666 const svn_delta_editor_t *editor = dir->report_context->update_editor;
667 const char *prop_name;
669 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
670 if (prop_name != NULL)
671 return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
680 remove_file_props(void *baton,
683 const svn_string_t *val,
684 apr_pool_t *scratch_pool)
686 report_info_t *info = baton;
687 const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
688 const char *prop_name;
690 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
691 if (prop_name != NULL)
692 return svn_error_trace(editor->change_file_prop(info->file_baton,
701 remove_dir_props(void *baton,
704 const svn_string_t *val,
705 apr_pool_t *scratch_pool)
707 report_dir_t *dir = baton;
708 const svn_delta_editor_t *editor = dir->report_context->update_editor;
709 const char *prop_name;
711 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
712 if (prop_name != NULL)
713 return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
721 /** Helpers to open and close directories */
724 ensure_dir_opened(report_dir_t *dir)
726 report_context_t *ctx = dir->report_context;
728 /* if we're already open, return now */
734 if (dir->base_name[0] == '\0')
736 dir->dir_baton_pool = svn_pool_create(dir->pool);
739 && ctx->sess->wc_callbacks->invalidate_wc_props)
741 SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
742 ctx->sess->wc_callback_baton,
744 SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
747 SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev,
753 SVN_ERR(ensure_dir_opened(dir->parent_dir));
755 dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool);
757 if (SVN_IS_VALID_REVNUM(dir->base_rev))
759 SVN_ERR(ctx->update_editor->open_directory(dir->name,
760 dir->parent_dir->dir_baton,
767 SVN_ERR(ctx->update_editor->add_directory(dir->name,
768 dir->parent_dir->dir_baton,
769 NULL, SVN_INVALID_REVNUM,
779 close_dir(report_dir_t *dir)
782 report_dir_t *sibling;
784 /* ### is there a better pool... this is tossed at end-of-func */
785 apr_pool_t *scratch_pool = dir->dir_baton_pool;
787 SVN_ERR_ASSERT(! dir->ref_count);
789 SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name,
794 SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name,
795 dir->base_rev, remove_dir_props, dir,
798 if (dir->fetch_props)
800 SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url,
801 dir->report_context->target_rev,
806 SVN_ERR(dir->report_context->update_editor->close_directory(
807 dir->dir_baton, scratch_pool));
809 /* remove us from our parent's children list */
813 sibling = dir->parent_dir->children;
815 while (sibling != dir)
818 sibling = sibling->sibling;
820 SVN_ERR_MALFUNCTION();
825 dir->parent_dir->children = dir->sibling;
829 prev->sibling = dir->sibling;
833 svn_pool_destroy(dir->dir_baton_pool);
834 svn_pool_destroy(dir->pool);
839 static svn_error_t *close_all_dirs(report_dir_t *dir)
841 while (dir->children)
843 SVN_ERR(close_all_dirs(dir->children));
847 SVN_ERR_ASSERT(! dir->ref_count);
849 SVN_ERR(ensure_dir_opened(dir));
851 return close_dir(dir);
855 /** Routines called when we are fetching a file */
857 /* This function works around a bug in some older versions of
858 * mod_dav_svn in that it will not send remove-prop in the update
859 * report when a lock property disappears when send-all is false.
861 * Therefore, we'll try to look at our properties and see if there's
862 * an active lock. If not, then we'll assume there isn't a lock
866 check_lock(report_info_t *info)
868 const char *lock_val;
870 lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
871 info->dir->report_context->target_rev,
872 "DAV:", "lockdiscovery");
877 new_lock = apr_pstrdup(info->editor_pool, lock_val);
878 apr_collapse_spaces(new_lock, new_lock);
882 if (!lock_val || lock_val[0] == '\0')
886 str = svn_string_ncreate("", 1, info->editor_pool);
888 svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name,
889 info->base_rev, "DAV:", "lock-token",
890 str, info->dir->pool);
895 headers_fetch(serf_bucket_t *headers,
899 report_fetch_t *fetch_ctx = baton;
901 /* note that we have old VC URL */
902 if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) &&
903 fetch_ctx->info->delta_base)
905 serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
906 fetch_ctx->info->delta_base);
907 serf_bucket_headers_setn(headers, "Accept-Encoding",
908 "svndiff1;q=0.9,svndiff;q=0.8");
910 else if (fetch_ctx->sess->using_compression)
912 serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
919 cancel_fetch(serf_request_t *request,
920 serf_bucket_t *response,
924 report_fetch_t *fetch_ctx = baton;
926 /* Uh-oh. Our connection died on us.
928 * The core ra_serf layer will requeue our request - we just need to note
929 * that we got cut off in the middle of our song.
933 /* If we already started the fetch and opened the file handle, we need
934 * to hold subsequent read() ops until we get back to where we were
935 * before the close and we can then resume the textdelta() calls.
937 if (fetch_ctx->read_headers)
939 if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
941 fetch_ctx->aborted_read = TRUE;
942 fetch_ctx->aborted_read_size = fetch_ctx->read_size;
944 fetch_ctx->read_size = 0;
950 /* We have no idea what went wrong. */
951 SVN_ERR_MALFUNCTION();
955 error_fetch(serf_request_t *request,
956 report_fetch_t *fetch_ctx,
959 fetch_ctx->done = TRUE;
961 fetch_ctx->done_item.data = fetch_ctx;
962 fetch_ctx->done_item.next = *fetch_ctx->done_list;
963 *fetch_ctx->done_list = &fetch_ctx->done_item;
965 /* Discard the rest of this request
966 (This makes sure it doesn't error when the request is aborted later) */
967 serf_request_set_handler(request,
968 svn_ra_serf__response_discard_handler, NULL);
970 /* Some errors would be handled by serf; make sure they really make
971 the update fail by wrapping it in a different error. */
972 if (!SERF_BUCKET_READ_ERROR(err->apr_err))
973 return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
978 /* Wield the editor referenced by INFO to open (or add) the file
979 file also associated with INFO, setting properties on the file and
980 calling the editor's apply_textdelta() function on it if necessary
981 (or if FORCE_APPLY_TEXTDELTA is set).
983 Callers will probably want to also see the function that serves
984 the opposite purpose of this one, close_updated_file(). */
986 open_updated_file(report_info_t *info,
987 svn_boolean_t force_apply_textdelta,
988 apr_pool_t *scratch_pool)
990 report_context_t *ctx = info->dir->report_context;
991 const svn_delta_editor_t *update_editor = ctx->update_editor;
993 /* Ensure our parent is open. */
994 SVN_ERR(ensure_dir_opened(info->dir));
995 info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
997 /* Expand our full name now if we haven't done so yet. */
1000 info->name = svn_relpath_join(info->dir->name, info->base_name,
1004 /* Open (or add) the file. */
1005 if (SVN_IS_VALID_REVNUM(info->base_rev))
1007 SVN_ERR(update_editor->open_file(info->name,
1008 info->dir->dir_baton,
1011 &info->file_baton));
1015 SVN_ERR(update_editor->add_file(info->name,
1016 info->dir->dir_baton,
1017 info->copyfrom_path,
1020 &info->file_baton));
1023 /* Check for lock information. */
1024 if (info->lock_token)
1027 /* Get (maybe) a textdelta window handler for transmitting file
1029 if (info->fetch_file || force_apply_textdelta)
1031 SVN_ERR(update_editor->apply_textdelta(info->file_baton,
1032 info->base_checksum,
1035 &info->textdelta_baton));
1038 return SVN_NO_ERROR;
1041 /* Close the file associated with INFO->file_baton, and cleanup other
1042 bits of that structure managed by open_updated_file(). */
1043 static svn_error_t *
1044 close_updated_file(report_info_t *info,
1045 apr_pool_t *scratch_pool)
1047 report_context_t *ctx = info->dir->report_context;
1049 /* Set all of the properties we received */
1050 SVN_ERR(svn_ra_serf__walk_all_props(info->props,
1053 set_file_props, info,
1055 SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
1058 remove_file_props, info,
1060 if (info->fetch_props)
1062 SVN_ERR(svn_ra_serf__walk_all_props(info->props,
1065 set_file_props, info,
1069 /* Close the file via the editor. */
1070 SVN_ERR(info->dir->report_context->update_editor->close_file(
1071 info->file_baton, info->final_checksum, scratch_pool));
1073 /* We're done with our editor pool. */
1074 svn_pool_destroy(info->editor_pool);
1076 return SVN_NO_ERROR;
1079 /* Implements svn_ra_serf__response_handler_t */
1080 static svn_error_t *
1081 handle_fetch(serf_request_t *request,
1082 serf_bucket_t *response,
1083 void *handler_baton,
1088 apr_status_t status;
1089 report_fetch_t *fetch_ctx = handler_baton;
1092 /* ### new field. make sure we didn't miss some initialization. */
1093 SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1095 if (!fetch_ctx->read_headers)
1097 serf_bucket_t *hdrs;
1099 report_info_t *info;
1101 hdrs = serf_bucket_response_get_headers(response);
1102 val = serf_bucket_headers_get(hdrs, "Content-Type");
1103 info = fetch_ctx->info;
1105 if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
1107 fetch_ctx->delta_stream =
1108 svn_txdelta_parse_svndiff(info->textdelta,
1109 info->textdelta_baton,
1110 TRUE, info->editor_pool);
1112 /* Validate the delta base claimed by the server matches
1113 what we asked for! */
1114 val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
1115 if (val && (strcmp(val, info->delta_base) != 0))
1117 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1118 _("GET request returned unexpected "
1119 "delta base: %s"), val);
1120 return error_fetch(request, fetch_ctx, err);
1125 fetch_ctx->delta_stream = NULL;
1128 fetch_ctx->read_headers = TRUE;
1131 /* If the error code wasn't 200, something went wrong. Don't use the returned
1132 data as its probably an error message. Just bail out instead. */
1133 if (fetch_ctx->handler->sline.code != 200)
1135 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1136 _("GET request failed: %d %s"),
1137 fetch_ctx->handler->sline.code,
1138 fetch_ctx->handler->sline.reason);
1139 return error_fetch(request, fetch_ctx, err);
1144 svn_txdelta_window_t delta_window = { 0 };
1145 svn_txdelta_op_t delta_op;
1146 svn_string_t window_data;
1148 status = serf_bucket_read(response, 8000, &data, &len);
1149 if (SERF_BUCKET_READ_ERROR(status))
1151 return svn_ra_serf__wrap_err(status, NULL);
1154 fetch_ctx->read_size += len;
1156 if (fetch_ctx->aborted_read)
1159 /* We haven't caught up to where we were before. */
1160 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1162 /* Eek. What did the file shrink or something? */
1163 if (APR_STATUS_IS_EOF(status))
1165 SVN_ERR_MALFUNCTION();
1168 /* Skip on to the next iteration of this loop. */
1169 if (APR_STATUS_IS_EAGAIN(status))
1171 return svn_ra_serf__wrap_err(status, NULL);
1176 /* Woo-hoo. We're back. */
1177 fetch_ctx->aborted_read = FALSE;
1179 /* Update data and len to just provide the new data. */
1180 skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
1185 if (fetch_ctx->delta_stream)
1187 err = svn_stream_write(fetch_ctx->delta_stream, data, &len);
1190 return error_fetch(request, fetch_ctx, err);
1193 /* otherwise, manually construct the text delta window. */
1196 window_data.data = data;
1197 window_data.len = len;
1199 delta_op.action_code = svn_txdelta_new;
1200 delta_op.offset = 0;
1201 delta_op.length = len;
1203 delta_window.tview_len = len;
1204 delta_window.num_ops = 1;
1205 delta_window.ops = &delta_op;
1206 delta_window.new_data = &window_data;
1208 /* write to the file located in the info. */
1209 err = fetch_ctx->info->textdelta(&delta_window,
1210 fetch_ctx->info->textdelta_baton);
1213 return error_fetch(request, fetch_ctx, err);
1217 if (APR_STATUS_IS_EOF(status))
1219 report_info_t *info = fetch_ctx->info;
1221 if (fetch_ctx->delta_stream)
1222 err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream));
1224 err = svn_error_trace(info->textdelta(NULL,
1225 info->textdelta_baton));
1228 return error_fetch(request, fetch_ctx, err);
1231 err = close_updated_file(info, info->pool);
1234 return svn_error_trace(error_fetch(request, fetch_ctx, err));
1237 fetch_ctx->done = TRUE;
1239 fetch_ctx->done_item.data = fetch_ctx;
1240 fetch_ctx->done_item.next = *fetch_ctx->done_list;
1241 *fetch_ctx->done_list = &fetch_ctx->done_item;
1243 /* We're done with our pool. */
1244 svn_pool_destroy(info->pool);
1247 return svn_ra_serf__wrap_err(status, NULL);
1249 if (APR_STATUS_IS_EAGAIN(status))
1251 return svn_ra_serf__wrap_err(status, NULL);
1257 /* Implements svn_ra_serf__response_handler_t */
1258 static svn_error_t *
1259 handle_stream(serf_request_t *request,
1260 serf_bucket_t *response,
1261 void *handler_baton,
1264 report_fetch_t *fetch_ctx = handler_baton;
1266 apr_status_t status;
1268 /* ### new field. make sure we didn't miss some initialization. */
1269 SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1271 err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline,
1272 fetch_ctx->info->name,
1273 fetch_ctx->handler->location);
1276 fetch_ctx->handler->done = TRUE;
1278 err = svn_error_compose_create(
1280 svn_ra_serf__handle_discard_body(request, response, NULL, pool));
1282 return svn_error_trace(err);
1290 status = serf_bucket_read(response, 8000, &data, &len);
1291 if (SERF_BUCKET_READ_ERROR(status))
1293 return svn_ra_serf__wrap_err(status, NULL);
1296 fetch_ctx->read_size += len;
1298 if (fetch_ctx->aborted_read)
1300 /* We haven't caught up to where we were before. */
1301 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1303 /* Eek. What did the file shrink or something? */
1304 if (APR_STATUS_IS_EOF(status))
1306 SVN_ERR_MALFUNCTION();
1309 /* Skip on to the next iteration of this loop. */
1310 if (APR_STATUS_IS_EAGAIN(status))
1312 return svn_ra_serf__wrap_err(status, NULL);
1317 /* Woo-hoo. We're back. */
1318 fetch_ctx->aborted_read = FALSE;
1320 /* Increment data and len by the difference. */
1321 data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1322 len += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1327 apr_size_t written_len;
1331 SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data,
1335 if (APR_STATUS_IS_EOF(status))
1337 fetch_ctx->done = TRUE;
1342 return svn_ra_serf__wrap_err(status, NULL);
1348 /* Close the directory represented by DIR -- and any suitable parents
1349 thereof -- if we are able to do so. This is the case whenever:
1351 - there are no remaining open items within the directory, and
1352 - the directory's XML close tag has been processed (so we know
1353 there are no more children to worry about in the future), and
1355 - we aren't fetching properties for this directory, or
1356 - we've already finished fetching those properties.
1358 static svn_error_t *
1359 maybe_close_dir_chain(report_dir_t *dir)
1361 report_dir_t *cur_dir = dir;
1363 SVN_ERR(ensure_dir_opened(cur_dir));
1366 && !cur_dir->ref_count
1367 && cur_dir->tag_closed
1368 && (!cur_dir->fetch_props || cur_dir->propfind_handler->done))
1370 report_dir_t *parent = cur_dir->parent_dir;
1371 report_context_t *report_context = cur_dir->report_context;
1372 svn_boolean_t propfind_in_done_list = FALSE;
1373 svn_ra_serf__list_t *done_list;
1375 /* Make sure there are no references to this dir in the
1376 active_dir_propfinds list. If there are, don't close the
1377 directory -- which would delete the pool from which the
1378 relevant active_dir_propfinds list item is allocated -- and
1379 of course don't crawl upward to check the parents for
1380 a closure opportunity, either. */
1381 done_list = report_context->active_dir_propfinds;
1384 if (done_list->data == cur_dir)
1386 propfind_in_done_list = TRUE;
1389 done_list = done_list->next;
1391 if (propfind_in_done_list)
1394 SVN_ERR(close_dir(cur_dir));
1397 parent->ref_count--;
1401 report_context->closed_root = TRUE;
1406 return SVN_NO_ERROR;
1409 /* Open the file associated with INFO for editing, pass along any
1410 propchanges we've recorded for it, and then close the file. */
1411 static svn_error_t *
1412 handle_propchange_only(report_info_t *info,
1413 apr_pool_t *scratch_pool)
1415 SVN_ERR(open_updated_file(info, FALSE, scratch_pool));
1416 SVN_ERR(close_updated_file(info, scratch_pool));
1418 /* We're done with our pool. */
1419 svn_pool_destroy(info->pool);
1421 info->dir->ref_count--;
1423 /* See if the parent directory of this file (and perhaps even
1424 parents of that) can be closed now. */
1425 SVN_ERR(maybe_close_dir_chain(info->dir));
1427 return SVN_NO_ERROR;
1430 /* "Fetch" a file whose contents were made available via the
1431 get_wc_contents() callback (as opposed to requiring a GET to the
1432 server), and feed the information through the associated update
1433 editor. In editor-speak, this will add/open the file, transmit any
1434 property changes, handle the contents, and then close the file. */
1435 static svn_error_t *
1436 handle_local_content(report_info_t *info,
1437 apr_pool_t *scratch_pool)
1439 SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta,
1440 info->textdelta_baton, NULL, scratch_pool));
1441 SVN_ERR(svn_stream_close(info->cached_contents));
1442 info->cached_contents = NULL;
1443 SVN_ERR(close_updated_file(info, scratch_pool));
1445 /* We're done with our pool. */
1446 svn_pool_destroy(info->pool);
1448 info->dir->ref_count--;
1450 /* See if the parent directory of this fetched item (and
1451 perhaps even parents of that) can be closed now. */
1452 SVN_ERR(maybe_close_dir_chain(info->dir));
1454 return SVN_NO_ERROR;
1457 /* --------------------------------------------------------- */
1459 static svn_error_t *
1460 fetch_file(report_context_t *ctx, report_info_t *info)
1462 svn_ra_serf__connection_t *conn;
1463 svn_ra_serf__handler_t *handler;
1465 /* What connection should we go on? */
1466 conn = get_best_connection(ctx);
1468 /* If needed, create the PROPFIND to retrieve the file's properties. */
1469 info->propfind_handler = NULL;
1470 if (info->fetch_props)
1472 SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props,
1473 ctx->sess, conn, info->url,
1474 ctx->target_rev, "0", all_props,
1475 &ctx->done_propfinds,
1477 SVN_ERR_ASSERT(info->propfind_handler);
1479 /* Create a serf request for the PROPFIND. */
1480 svn_ra_serf__request_create(info->propfind_handler);
1482 ctx->num_active_propfinds++;
1485 /* If we've been asked to fetch the file or it's an add, do so.
1486 * Otherwise, handle the case where only the properties changed.
1488 if (info->fetch_file && ctx->text_deltas)
1490 svn_stream_t *contents = NULL;
1492 /* Open the file for editing. */
1493 SVN_ERR(open_updated_file(info, FALSE, info->pool));
1495 if (info->textdelta == svn_delta_noop_window_handler)
1497 /* There is nobody looking for an actual stream.
1499 Just report an empty stream instead of fetching
1500 to be ingored data */
1501 info->cached_contents = svn_stream_empty(info->pool);
1503 else if (ctx->sess->wc_callbacks->get_wc_contents
1504 && info->final_sha1_checksum)
1506 svn_error_t *err = NULL;
1507 svn_checksum_t *checksum = NULL;
1509 /* Parse the optional SHA1 checksum (1.7+) */
1510 err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
1511 info->final_sha1_checksum,
1514 /* Okay so far? Let's try to get a stream on some readily
1515 available matching content. */
1516 if (!err && checksum)
1518 err = ctx->sess->wc_callbacks->get_wc_contents(
1519 ctx->sess->wc_callback_baton, &contents,
1520 checksum, info->pool);
1523 info->cached_contents = contents;
1528 /* Meh. Maybe we'll care one day why we're in an
1529 errorful state, but this codepath is optional. */
1530 svn_error_clear(err);
1534 /* If the working copy can provide cached contents for this
1535 file, we don't have to fetch them from the server. */
1536 if (info->cached_contents)
1538 /* If we'll be doing a PROPFIND for this file... */
1539 if (info->propfind_handler)
1541 /* ... then we'll just leave ourselves a little "todo"
1542 about that fact (and we'll deal with the file content
1543 stuff later, after we've handled that PROPFIND
1545 svn_ra_serf__list_t *list_item;
1547 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1548 list_item->data = info;
1549 list_item->next = ctx->file_propchanges_only;
1550 ctx->file_propchanges_only = list_item;
1554 /* Otherwise, if we've no PROPFIND to do, we might as
1555 well take care of those locally accessible file
1557 SVN_ERR(handle_local_content(info, info->pool));
1562 /* Otherwise, we use a GET request for the file's contents. */
1563 report_fetch_t *fetch_ctx;
1565 fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
1566 fetch_ctx->info = info;
1567 fetch_ctx->done_list = &ctx->done_fetches;
1568 fetch_ctx->sess = ctx->sess;
1569 fetch_ctx->conn = conn;
1571 handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
1573 handler->handler_pool = info->dir->pool;
1574 handler->method = "GET";
1575 handler->path = fetch_ctx->info->url;
1577 handler->conn = conn;
1578 handler->session = ctx->sess;
1580 handler->custom_accept_encoding = TRUE;
1581 handler->header_delegate = headers_fetch;
1582 handler->header_delegate_baton = fetch_ctx;
1584 handler->response_handler = handle_fetch;
1585 handler->response_baton = fetch_ctx;
1587 handler->response_error = cancel_fetch;
1588 handler->response_error_baton = fetch_ctx;
1590 fetch_ctx->handler = handler;
1592 svn_ra_serf__request_create(handler);
1594 ctx->num_active_fetches++;
1597 else if (info->propfind_handler)
1599 svn_ra_serf__list_t *list_item;
1601 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1602 list_item->data = info;
1603 list_item->next = ctx->file_propchanges_only;
1604 ctx->file_propchanges_only = list_item;
1608 /* No propfind or GET request. Just handle the prop changes now. */
1609 SVN_ERR(handle_propchange_only(info, info->pool));
1612 if (ctx->num_active_fetches + ctx->num_active_propfinds
1613 > REQUEST_COUNT_TO_PAUSE)
1614 ctx->parser_ctx->paused = TRUE;
1616 return SVN_NO_ERROR;
1620 /** XML callbacks for our update-report response parsing */
1622 static svn_error_t *
1623 start_report(svn_ra_serf__xml_parser_t *parser,
1624 svn_ra_serf__dav_props_t name,
1626 apr_pool_t *scratch_pool)
1628 report_context_t *ctx = parser->user_data;
1629 report_state_e state;
1631 state = parser->state->current_state;
1633 if (state == NONE && strcmp(name.name, "update-report") == 0)
1637 val = svn_xml_get_attr_value("inline-props", attrs);
1638 if (val && (strcmp(val, "true") == 0))
1639 ctx->add_props_included = TRUE;
1641 val = svn_xml_get_attr_value("send-all", attrs);
1642 if (val && (strcmp(val, "true") == 0))
1644 ctx->send_all_mode = TRUE;
1646 /* All properties are included in send-all mode. */
1647 ctx->add_props_included = TRUE;
1650 else if (state == NONE && strcmp(name.name, "target-revision") == 0)
1654 rev = svn_xml_get_attr_value("rev", attrs);
1658 return svn_error_create(
1659 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1660 _("Missing revision attr in target-revision element"));
1663 SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
1664 SVN_STR_TO_REV(rev),
1667 else if (state == NONE && strcmp(name.name, "open-directory") == 0)
1670 report_info_t *info;
1672 rev = svn_xml_get_attr_value("rev", attrs);
1676 return svn_error_create(
1677 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1678 _("Missing revision attr in open-directory element"));
1681 info = push_state(parser, ctx, OPEN_DIR);
1683 info->base_rev = SVN_STR_TO_REV(rev);
1684 info->dir->base_rev = info->base_rev;
1685 info->fetch_props = TRUE;
1687 info->dir->base_name = "";
1688 info->dir->name = "";
1690 info->base_name = info->dir->base_name;
1691 info->name = info->dir->name;
1693 info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, "");
1695 if (!info->dir->repos_relpath)
1696 SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath,
1697 ctx->sess->session_url.path,
1698 ctx->sess, ctx->conn,
1701 else if (state == NONE)
1703 /* do nothing as we haven't seen our valid start tag yet. */
1705 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1706 strcmp(name.name, "open-directory") == 0)
1708 const char *rev, *dirname;
1710 report_info_t *info;
1712 rev = svn_xml_get_attr_value("rev", attrs);
1716 return svn_error_create(
1717 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1718 _("Missing revision attr in open-directory element"));
1721 dirname = svn_xml_get_attr_value("name", attrs);
1725 return svn_error_create(
1726 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1727 _("Missing name attr in open-directory element"));
1730 info = push_state(parser, ctx, OPEN_DIR);
1734 info->base_rev = SVN_STR_TO_REV(rev);
1735 dir->base_rev = info->base_rev;
1737 info->fetch_props = FALSE;
1739 dir->base_name = apr_pstrdup(dir->pool, dirname);
1740 info->base_name = dir->base_name;
1742 /* Expand our name. */
1743 dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
1745 info->name = dir->name;
1747 dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name);
1749 if (!dir->repos_relpath)
1750 dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
1751 dir->base_name, dir->pool);
1753 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1754 strcmp(name.name, "add-directory") == 0)
1756 const char *dir_name, *cf, *cr;
1758 report_info_t *info;
1760 dir_name = svn_xml_get_attr_value("name", attrs);
1763 return svn_error_create(
1764 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1765 _("Missing name attr in add-directory element"));
1767 cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1768 cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1770 info = push_state(parser, ctx, ADD_DIR);
1774 dir->base_name = apr_pstrdup(dir->pool, dir_name);
1775 info->base_name = dir->base_name;
1777 /* Expand our name. */
1778 dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
1780 info->name = dir->name;
1782 info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1783 info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
1785 /* Mark that we don't have a base. */
1786 info->base_rev = SVN_INVALID_REVNUM;
1787 dir->base_rev = info->base_rev;
1789 /* If the server isn't included properties for added items,
1790 we'll need to fetch them ourselves. */
1791 if (! ctx->add_props_included)
1792 dir->fetch_props = TRUE;
1794 dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
1795 dir->base_name, dir->pool);
1797 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1798 strcmp(name.name, "open-file") == 0)
1800 const char *file_name, *rev;
1801 report_info_t *info;
1803 file_name = svn_xml_get_attr_value("name", attrs);
1807 return svn_error_create(
1808 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1809 _("Missing name attr in open-file element"));
1812 rev = svn_xml_get_attr_value("rev", attrs);
1816 return svn_error_create(
1817 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1818 _("Missing revision attr in open-file element"));
1821 info = push_state(parser, ctx, OPEN_FILE);
1823 info->base_rev = SVN_STR_TO_REV(rev);
1824 info->fetch_props = FALSE;
1826 info->base_name = apr_pstrdup(info->pool, file_name);
1829 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1830 strcmp(name.name, "add-file") == 0)
1832 const char *file_name, *cf, *cr;
1833 report_info_t *info;
1835 file_name = svn_xml_get_attr_value("name", attrs);
1836 cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1837 cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1841 return svn_error_create(
1842 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1843 _("Missing name attr in add-file element"));
1846 info = push_state(parser, ctx, ADD_FILE);
1848 info->base_rev = SVN_INVALID_REVNUM;
1850 /* If the server isn't in "send-all" mode, we should expect to
1851 fetch contents for added files. */
1852 if (! ctx->send_all_mode)
1853 info->fetch_file = TRUE;
1855 /* If the server isn't included properties for added items,
1856 we'll need to fetch them ourselves. */
1857 if (! ctx->add_props_included)
1858 info->fetch_props = TRUE;
1860 info->base_name = apr_pstrdup(info->pool, file_name);
1863 info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1864 info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
1866 info->final_sha1_checksum =
1867 svn_xml_get_attr_value("sha1-checksum", attrs);
1868 if (info->final_sha1_checksum)
1869 info->final_sha1_checksum = apr_pstrdup(info->pool,
1870 info->final_sha1_checksum);
1872 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1873 strcmp(name.name, "delete-entry") == 0)
1875 const char *file_name;
1876 const char *rev_str;
1877 report_info_t *info;
1878 apr_pool_t *tmppool;
1879 const char *full_path;
1880 svn_revnum_t delete_rev = SVN_INVALID_REVNUM;
1882 file_name = svn_xml_get_attr_value("name", attrs);
1886 return svn_error_create(
1887 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1888 _("Missing name attr in delete-entry element"));
1891 rev_str = svn_xml_get_attr_value("rev", attrs);
1892 if (rev_str) /* Not available on older repositories! */
1893 delete_rev = SVN_STR_TO_REV(rev_str);
1895 info = parser->state->private;
1897 SVN_ERR(ensure_dir_opened(info->dir));
1899 tmppool = svn_pool_create(info->dir->dir_baton_pool);
1901 full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
1903 SVN_ERR(ctx->update_editor->delete_entry(full_path,
1905 info->dir->dir_baton,
1908 svn_pool_destroy(tmppool);
1910 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1911 strcmp(name.name, "absent-directory") == 0)
1913 const char *file_name;
1914 report_info_t *info;
1916 file_name = svn_xml_get_attr_value("name", attrs);
1920 return svn_error_create(
1921 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1922 _("Missing name attr in absent-directory element"));
1925 info = parser->state->private;
1927 SVN_ERR(ensure_dir_opened(info->dir));
1929 SVN_ERR(ctx->update_editor->absent_directory(
1930 svn_relpath_join(info->name, file_name,
1932 info->dir->dir_baton,
1935 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1936 strcmp(name.name, "absent-file") == 0)
1938 const char *file_name;
1939 report_info_t *info;
1941 file_name = svn_xml_get_attr_value("name", attrs);
1945 return svn_error_create(
1946 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1947 _("Missing name attr in absent-file element"));
1950 info = parser->state->private;
1952 SVN_ERR(ensure_dir_opened(info->dir));
1954 SVN_ERR(ctx->update_editor->absent_file(
1955 svn_relpath_join(info->name, file_name,
1957 info->dir->dir_baton,
1960 else if (state == OPEN_DIR || state == ADD_DIR)
1962 report_info_t *info;
1964 if (strcmp(name.name, "checked-in") == 0)
1966 info = push_state(parser, ctx, IGNORE_PROP_NAME);
1967 info->prop_ns = name.namespace;
1968 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
1969 info->prop_encoding = NULL;
1970 svn_stringbuf_setempty(info->prop_value);
1972 else if (strcmp(name.name, "set-prop") == 0 ||
1973 strcmp(name.name, "remove-prop") == 0)
1975 const char *full_prop_name;
1978 info = push_state(parser, ctx, PROP);
1980 full_prop_name = svn_xml_get_attr_value("name", attrs);
1981 if (!full_prop_name)
1983 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1984 _("Missing name attr in %s element"),
1988 colon = strchr(full_prop_name, ':');
1993 colon = full_prop_name;
1995 info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
1996 colon - full_prop_name);
1997 info->prop_name = apr_pstrdup(parser->state->pool, colon);
1998 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
1999 svn_stringbuf_setempty(info->prop_value);
2001 else if (strcmp(name.name, "prop") == 0)
2003 /* need to fetch it. */
2004 push_state(parser, ctx, NEED_PROP_NAME);
2006 else if (strcmp(name.name, "fetch-props") == 0)
2008 info = parser->state->private;
2010 info->dir->fetch_props = TRUE;
2014 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2015 _("Unknown tag '%s' while at state %d"),
2020 else if (state == OPEN_FILE || state == ADD_FILE)
2022 report_info_t *info;
2024 if (strcmp(name.name, "checked-in") == 0)
2026 info = push_state(parser, ctx, IGNORE_PROP_NAME);
2027 info->prop_ns = name.namespace;
2028 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
2029 info->prop_encoding = NULL;
2030 svn_stringbuf_setempty(info->prop_value);
2032 else if (strcmp(name.name, "prop") == 0)
2034 /* need to fetch it. */
2035 push_state(parser, ctx, NEED_PROP_NAME);
2037 else if (strcmp(name.name, "fetch-props") == 0)
2039 info = parser->state->private;
2041 info->fetch_props = TRUE;
2043 else if (strcmp(name.name, "fetch-file") == 0)
2045 info = parser->state->private;
2046 info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs);
2048 if (info->base_checksum)
2049 info->base_checksum = apr_pstrdup(info->pool, info->base_checksum);
2051 info->final_sha1_checksum =
2052 svn_xml_get_attr_value("sha1-checksum", attrs);
2053 if (info->final_sha1_checksum)
2054 info->final_sha1_checksum = apr_pstrdup(info->pool,
2055 info->final_sha1_checksum);
2057 info->fetch_file = TRUE;
2059 else if (strcmp(name.name, "set-prop") == 0 ||
2060 strcmp(name.name, "remove-prop") == 0)
2062 const char *full_prop_name;
2065 info = push_state(parser, ctx, PROP);
2067 full_prop_name = svn_xml_get_attr_value("name", attrs);
2068 if (!full_prop_name)
2070 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2071 _("Missing name attr in %s element"),
2074 colon = strchr(full_prop_name, ':');
2079 colon = full_prop_name;
2081 info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
2082 colon - full_prop_name);
2083 info->prop_name = apr_pstrdup(parser->state->pool, colon);
2084 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2085 svn_stringbuf_setempty(info->prop_value);
2087 else if (strcmp(name.name, "txdelta") == 0)
2089 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
2090 addition to <fetch-file>s and such) when *not* in
2091 "send-all" mode. As a client, we're smart enough to know
2092 that's wrong, so we'll just ignore these tags. */
2093 if (ctx->send_all_mode)
2095 const svn_delta_editor_t *update_editor = ctx->update_editor;
2097 info = push_state(parser, ctx, TXDELTA);
2099 if (! info->file_baton)
2101 SVN_ERR(open_updated_file(info, FALSE, info->pool));
2104 info->base_checksum = svn_xml_get_attr_value("base-checksum",
2106 SVN_ERR(update_editor->apply_textdelta(info->file_baton,
2107 info->base_checksum,
2110 &info->textdelta_baton));
2111 info->svndiff_decoder = svn_txdelta_parse_svndiff(
2113 info->textdelta_baton,
2115 info->base64_decoder = svn_base64_decode(info->svndiff_decoder,
2121 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2122 _("Unknown tag '%s' while at state %d"),
2126 else if (state == IGNORE_PROP_NAME)
2128 report_info_t *info = push_state(parser, ctx, PROP);
2129 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2131 else if (state == NEED_PROP_NAME)
2133 report_info_t *info;
2135 info = push_state(parser, ctx, PROP);
2137 info->prop_ns = name.namespace;
2138 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
2139 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2140 svn_stringbuf_setempty(info->prop_value);
2143 return SVN_NO_ERROR;
2146 static svn_error_t *
2147 end_report(svn_ra_serf__xml_parser_t *parser,
2148 svn_ra_serf__dav_props_t name,
2149 apr_pool_t *scratch_pool)
2151 report_context_t *ctx = parser->user_data;
2152 report_state_e state;
2154 state = parser->state->current_state;
2158 if (strcmp(name.name, "update-report") == 0)
2160 ctx->report_completed = TRUE;
2164 /* nothing to close yet. */
2165 return SVN_NO_ERROR;
2169 if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
2170 (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0))))
2172 const char *checked_in_url;
2173 report_info_t *info = parser->state->private;
2175 /* We've now closed this directory; note it. */
2176 info->dir->tag_closed = TRUE;
2178 /* go fetch info->file_name from DAV:checked-in */
2180 svn_ra_serf__get_ver_prop(info->dir->props, info->base_name,
2181 info->base_rev, "DAV:", "checked-in");
2183 /* If we were expecting to have the properties and we aren't able to
2186 if (!checked_in_url &&
2187 (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
2189 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2190 _("The REPORT or PROPFIND response did not "
2191 "include the requested checked-in value"));
2194 info->dir->url = checked_in_url;
2196 /* At this point, we should have the checked-in href.
2197 * If needed, create the PROPFIND to retrieve the dir's properties.
2199 if (info->dir->fetch_props)
2201 svn_ra_serf__list_t *list_item;
2203 SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler,
2204 info->dir->props, ctx->sess,
2205 get_best_connection(ctx),
2207 ctx->target_rev, "0",
2209 &ctx->done_dir_propfinds,
2211 SVN_ERR_ASSERT(info->dir->propfind_handler);
2213 /* Create a serf request for the PROPFIND. */
2214 svn_ra_serf__request_create(info->dir->propfind_handler);
2216 ctx->num_active_propfinds++;
2218 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
2219 list_item->data = info->dir;
2220 list_item->next = ctx->active_dir_propfinds;
2221 ctx->active_dir_propfinds = list_item;
2223 if (ctx->num_active_fetches + ctx->num_active_propfinds
2224 > REQUEST_COUNT_TO_PAUSE)
2225 ctx->parser_ctx->paused = TRUE;
2229 info->dir->propfind_handler = NULL;
2232 /* See if this directory (and perhaps even parents of that) can
2233 be closed now. This is likely to be the case only if we
2234 didn't need to contact the server for supplemental
2235 information required to handle any of this directory's
2237 SVN_ERR(maybe_close_dir_chain(info->dir));
2238 svn_ra_serf__xml_pop_state(parser);
2240 else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
2242 report_info_t *info = parser->state->private;
2244 /* Expand our full name now if we haven't done so yet. */
2247 info->name = svn_relpath_join(info->dir->name, info->base_name,
2251 info->lock_token = svn_hash_gets(ctx->lock_path_tokens, info->name);
2253 if (info->lock_token && !info->fetch_props)
2254 info->fetch_props = TRUE;
2256 /* If possible, we'd like to fetch only a delta against a
2257 * version of the file we already have in our working copy,
2258 * rather than fetching a fulltext.
2260 * In HTTP v2, we can simply construct the URL we need given the
2261 * repos_relpath and base revision number.
2263 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
2265 const char *repos_relpath;
2267 /* If this file is switched vs the editor root we should provide
2268 its real url instead of the one calculated from the session root.
2270 repos_relpath = svn_hash_gets(ctx->switched_paths, info->name);
2274 if (ctx->root_is_switched)
2276 /* We are updating a direct target (most likely a file)
2277 that is switched vs its parent url */
2278 SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool)
2281 repos_relpath = svn_hash_gets(ctx->switched_paths, "");
2284 repos_relpath = svn_relpath_join(info->dir->repos_relpath,
2285 info->base_name, info->pool);
2288 info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s",
2289 ctx->sess->rev_root_stub,
2291 svn_path_uri_encode(repos_relpath,
2294 else if (ctx->sess->wc_callbacks->get_wc_prop)
2296 /* If we have a WC, we might be able to dive all the way into the WC
2297 * to get the previous URL so we can do a differential GET with the
2300 const svn_string_t *value = NULL;
2301 SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
2302 ctx->sess->wc_callback_baton, info->name,
2303 SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool));
2305 info->delta_base = value ? value->data : NULL;
2308 /* go fetch info->name from DAV:checked-in */
2309 info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
2310 info->base_rev, "DAV:", "checked-in");
2313 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2314 _("The REPORT or PROPFIND response did not "
2315 "include the requested checked-in value"));
2318 /* If the server is in "send-all" mode, we might have opened the
2319 file when we started seeing content for it. If we didn't get
2320 any content for it, we still need to open the file. But in
2321 any case, we can then immediately close it. */
2322 if (ctx->send_all_mode)
2324 if (! info->file_baton)
2326 SVN_ERR(open_updated_file(info, FALSE, info->pool));
2328 SVN_ERR(close_updated_file(info, info->pool));
2329 info->dir->ref_count--;
2331 /* Otherwise, if the server is *not* in "send-all" mode, we
2332 should be at a point where we can queue up any auxiliary
2333 content-fetching requests. */
2336 SVN_ERR(fetch_file(ctx, info));
2339 svn_ra_serf__xml_pop_state(parser);
2341 else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
2343 report_info_t *info = parser->state->private;
2345 /* go fetch info->name from DAV:checked-in */
2346 info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
2347 info->base_rev, "DAV:", "checked-in");
2350 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2351 _("The REPORT or PROPFIND response did not "
2352 "include the requested checked-in value"));
2355 /* If the server is in "send-all" mode, we might have opened the
2356 file when we started seeing content for it. If we didn't get
2357 any content for it, we still need to open the file. But in
2358 any case, we can then immediately close it. */
2359 if (ctx->send_all_mode)
2361 if (! info->file_baton)
2363 SVN_ERR(open_updated_file(info, FALSE, info->pool));
2365 SVN_ERR(close_updated_file(info, info->pool));
2366 info->dir->ref_count--;
2368 /* Otherwise, if the server is *not* in "send-all" mode, we
2369 should be at a point where we can queue up any auxiliary
2370 content-fetching requests. */
2373 SVN_ERR(fetch_file(ctx, info));
2376 svn_ra_serf__xml_pop_state(parser);
2378 else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0)
2380 report_info_t *info = parser->state->private;
2382 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2383 <fetch-file>s and such) when *not* in "send-all" mode. As a
2384 client, we're smart enough to know that's wrong, so when not
2385 in "receiving-all" mode, we'll ignore these tags. */
2386 if (ctx->send_all_mode)
2388 SVN_ERR(svn_stream_close(info->base64_decoder));
2391 svn_ra_serf__xml_pop_state(parser);
2393 else if (state == PROP)
2395 /* We need to move the prop_ns, prop_name, and prop_value into the
2396 * same lifetime as the dir->pool.
2398 svn_ra_serf__ns_t *ns, *ns_name_match;
2399 svn_boolean_t found = FALSE;
2400 report_info_t *info;
2403 const svn_string_t *set_val_str;
2406 info = parser->state->private;
2409 /* We're going to be slightly tricky. We don't care what the ->url
2410 * field is here at this point. So, we're going to stick a single
2411 * copy of the property name inside of the ->url field.
2413 ns_name_match = NULL;
2414 for (ns = dir->ns_list; ns; ns = ns->next)
2416 if (strcmp(ns->namespace, info->prop_ns) == 0)
2419 if (strcmp(ns->url, info->prop_name) == 0)
2429 ns = apr_palloc(dir->pool, sizeof(*ns));
2432 ns->namespace = apr_pstrdup(dir->pool, info->prop_ns);
2436 ns->namespace = ns_name_match->namespace;
2438 ns->url = apr_pstrdup(dir->pool, info->prop_name);
2440 ns->next = dir->ns_list;
2444 if (strcmp(name.name, "remove-prop") != 0)
2446 props = info->props;
2451 props = dir->removed_props;
2453 svn_stringbuf_setempty(info->prop_value);
2456 if (info->prop_encoding)
2458 if (strcmp(info->prop_encoding, "base64") == 0)
2462 /* Don't use morph_info_string cuz we need prop_value to
2464 tmp.data = info->prop_value->data;
2465 tmp.len = info->prop_value->len;
2467 set_val_str = svn_base64_decode_string(&tmp, pool);
2471 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
2473 _("Got unrecognized encoding '%s'"),
2474 info->prop_encoding);
2479 set_val_str = svn_string_create_from_buf(info->prop_value, pool);
2482 svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
2483 ns->namespace, ns->url, set_val_str, pool);
2485 /* Advance handling: if we spotted the md5-checksum property on
2486 the wire, remember it's value. */
2487 if (strcmp(ns->url, "md5-checksum") == 0
2488 && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0)
2489 info->final_checksum = apr_pstrdup(info->pool, set_val_str->data);
2491 svn_ra_serf__xml_pop_state(parser);
2493 else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
2495 svn_ra_serf__xml_pop_state(parser);
2498 return SVN_NO_ERROR;
2501 static svn_error_t *
2502 cdata_report(svn_ra_serf__xml_parser_t *parser,
2505 apr_pool_t *scratch_pool)
2507 report_context_t *ctx = parser->user_data;
2511 if (parser->state->current_state == PROP)
2513 report_info_t *info = parser->state->private;
2515 svn_stringbuf_appendbytes(info->prop_value, data, len);
2517 else if (parser->state->current_state == TXDELTA)
2519 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2520 <fetch-file>s and such) when *not* in "send-all" mode. As a
2521 client, we're smart enough to know that's wrong, so when not
2522 in "receiving-all" mode, we'll ignore these tags. */
2523 if (ctx->send_all_mode)
2525 apr_size_t nlen = len;
2526 report_info_t *info = parser->state->private;
2528 SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen));
2531 /* Short write without associated error? "Can't happen." */
2532 return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
2533 _("Error writing to '%s': unexpected EOF"),
2539 return SVN_NO_ERROR;
2543 /** Editor callbacks given to callers to create request body */
2545 /* Helper to create simple xml tag without attributes. */
2547 make_simple_xml_tag(svn_stringbuf_t **buf_p,
2548 const char *tagname,
2552 svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL);
2553 svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
2554 svn_xml_make_close_tag(buf_p, pool, tagname);
2557 static svn_error_t *
2558 set_path(void *report_baton,
2560 svn_revnum_t revision,
2562 svn_boolean_t start_empty,
2563 const char *lock_token,
2566 report_context_t *report = report_baton;
2567 svn_stringbuf_t *buf = NULL;
2569 svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2570 "rev", apr_ltoa(pool, revision),
2571 "lock-token", lock_token,
2572 "depth", svn_depth_to_word(depth),
2573 "start-empty", start_empty ? "true" : NULL,
2575 svn_xml_escape_cdata_cstring(&buf, path, pool);
2576 svn_xml_make_close_tag(&buf, pool, "S:entry");
2578 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2583 svn_hash_sets(report->lock_path_tokens,
2584 apr_pstrdup(report->pool, path),
2585 apr_pstrdup(report->pool, lock_token));
2588 return SVN_NO_ERROR;
2591 static svn_error_t *
2592 delete_path(void *report_baton,
2596 report_context_t *report = report_baton;
2597 svn_stringbuf_t *buf = NULL;
2599 make_simple_xml_tag(&buf, "S:missing", path, pool);
2601 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2604 return SVN_NO_ERROR;
2607 static svn_error_t *
2608 link_path(void *report_baton,
2611 svn_revnum_t revision,
2613 svn_boolean_t start_empty,
2614 const char *lock_token,
2617 report_context_t *report = report_baton;
2618 const char *link, *report_target;
2620 apr_status_t status;
2621 svn_stringbuf_t *buf = NULL;
2623 /* We need to pass in the baseline relative path.
2625 * TODO Confirm that it's on the same server?
2627 status = apr_uri_parse(pool, url, &uri);
2630 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2631 _("Unable to parse URL '%s'"), url);
2634 SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess,
2636 SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess,
2639 link = apr_pstrcat(pool, "/", link, (char *)NULL);
2641 svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2642 "rev", apr_ltoa(pool, revision),
2643 "lock-token", lock_token,
2644 "depth", svn_depth_to_word(depth),
2646 "start-empty", start_empty ? "true" : NULL,
2648 svn_xml_escape_cdata_cstring(&buf, path, pool);
2649 svn_xml_make_close_tag(&buf, pool, "S:entry");
2651 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2654 /* Store the switch roots to allow generating repos_relpaths from just
2655 the working copy paths. (Needed for HTTPv2) */
2656 path = apr_pstrdup(report->pool, path);
2657 svn_hash_sets(report->switched_paths,
2658 path, apr_pstrdup(report->pool, link + 1));
2661 report->root_is_switched = TRUE;
2665 svn_hash_sets(report->lock_path_tokens,
2666 path, apr_pstrdup(report->pool, lock_token));
2672 /** Minimum nr. of outstanding requests needed before a new connection is
2674 #define REQS_PER_CONN 8
2676 /** This function creates a new connection for this serf session, but only
2677 * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
2678 * only one main connection open.
2680 static svn_error_t *
2681 open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
2683 /* For each REQS_PER_CONN outstanding requests open a new connection, with
2684 * a minimum of 1 extra connection. */
2685 if (sess->num_conns == 1 ||
2686 ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
2688 int cur = sess->num_conns;
2689 apr_status_t status;
2691 sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
2692 sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
2694 sess->conns[cur]->last_status_code = -1;
2695 sess->conns[cur]->session = sess;
2696 status = serf_connection_create2(&sess->conns[cur]->conn,
2699 svn_ra_serf__conn_setup,
2701 svn_ra_serf__conn_closed,
2705 return svn_ra_serf__wrap_err(status, NULL);
2710 return SVN_NO_ERROR;
2713 /* Serf callback to create update request body bucket. */
2714 static svn_error_t *
2715 create_update_report_body(serf_bucket_t **body_bkt,
2717 serf_bucket_alloc_t *alloc,
2720 report_context_t *report = baton;
2724 apr_file_seek(report->body_file, APR_SET, &offset);
2726 *body_bkt = serf_bucket_file_create(report->body_file, alloc);
2728 return SVN_NO_ERROR;
2731 /* Serf callback to setup update request headers. */
2732 static svn_error_t *
2733 setup_update_report_headers(serf_bucket_t *headers,
2737 report_context_t *report = baton;
2739 if (report->sess->using_compression)
2741 serf_bucket_headers_setn(headers, "Accept-Encoding",
2742 "gzip,svndiff1;q=0.9,svndiff;q=0.8");
2746 serf_bucket_headers_setn(headers, "Accept-Encoding",
2747 "svndiff1;q=0.9,svndiff;q=0.8");
2750 return SVN_NO_ERROR;
2753 static svn_error_t *
2754 finish_report(void *report_baton,
2757 report_context_t *report = report_baton;
2758 svn_ra_serf__session_t *sess = report->sess;
2759 svn_ra_serf__handler_t *handler;
2760 svn_ra_serf__xml_parser_t *parser_ctx;
2761 const char *report_target;
2762 svn_stringbuf_t *buf = NULL;
2763 apr_pool_t *iterpool = svn_pool_create(pool);
2765 apr_interval_time_t waittime_left = sess->timeout;
2767 svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
2768 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2771 /* We need to flush the file, make it unbuffered (so that it can be
2772 * zero-copied via mmap), and reset the position before attempting to
2775 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
2776 * and zero-copy the PUT body. However, on older APR versions, we can't
2777 * check the buffer status; but serf will fall through and create a file
2778 * bucket for us on the buffered svndiff handle.
2780 apr_file_flush(report->body_file);
2781 #if APR_VERSION_AT_LEAST(1, 3, 0)
2782 apr_file_buffer_set(report->body_file, NULL, 0);
2785 SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool));
2787 /* create and deliver request */
2788 report->path = report_target;
2790 handler = apr_pcalloc(pool, sizeof(*handler));
2792 handler->handler_pool = pool;
2793 handler->method = "REPORT";
2794 handler->path = report->path;
2795 handler->body_delegate = create_update_report_body;
2796 handler->body_delegate_baton = report;
2797 handler->body_type = "text/xml";
2798 handler->custom_accept_encoding = TRUE;
2799 handler->header_delegate = setup_update_report_headers;
2800 handler->header_delegate_baton = report;
2801 handler->conn = sess->conns[0];
2802 handler->session = sess;
2804 parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
2806 parser_ctx->pool = pool;
2807 parser_ctx->response_type = "update-report";
2808 parser_ctx->user_data = report;
2809 parser_ctx->start = start_report;
2810 parser_ctx->end = end_report;
2811 parser_ctx->cdata = cdata_report;
2812 parser_ctx->done = &report->done;
2814 handler->response_handler = svn_ra_serf__handle_xml_parser;
2815 handler->response_baton = parser_ctx;
2817 report->parser_ctx = parser_ctx;
2819 svn_ra_serf__request_create(handler);
2821 /* Open the first extra connection. */
2822 SVN_ERR(open_connection_if_needed(sess, 0));
2826 /* Note that we may have no active GET or PROPFIND requests, yet the
2827 processing has not been completed. This could be from a delay on the
2828 network or because we've spooled the entire response into our "pending"
2829 content of the XML parser. The DONE flag will get set when all the
2830 XML content has been received *and* parsed. */
2831 while (!report->done
2832 || report->num_active_fetches
2833 || report->num_active_propfinds)
2835 apr_pool_t *iterpool_inner;
2836 svn_ra_serf__list_t *done_list;
2838 apr_status_t status;
2840 /* Note: this throws out the old ITERPOOL_INNER. */
2841 svn_pool_clear(iterpool);
2843 if (sess->cancel_func)
2844 SVN_ERR(sess->cancel_func(sess->cancel_baton));
2846 /* We need to be careful between the outer and inner ITERPOOLs,
2847 and what items are allocated within. */
2848 iterpool_inner = svn_pool_create(iterpool);
2850 status = serf_context_run(sess->context,
2851 SVN_RA_SERF__CONTEXT_RUN_DURATION,
2854 err = sess->pending_error;
2855 sess->pending_error = SVN_NO_ERROR;
2857 if (!err && handler->done && handler->server_error)
2859 err = handler->server_error->error;
2862 /* If the context duration timeout is up, we'll subtract that
2863 duration from the total time alloted for such things. If
2864 there's no time left, we fail with a message indicating that
2865 the connection timed out. */
2866 if (APR_STATUS_IS_TIMEUP(status))
2868 svn_error_clear(err);
2874 if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
2876 waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
2880 return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
2881 _("Connection timed out"));
2887 waittime_left = sess->timeout;
2890 if (status && handler->sline.code != 200)
2892 return svn_error_trace(
2893 svn_error_compose_create(
2894 svn_ra_serf__error_on_status(handler->sline,
2902 return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
2905 /* Open extra connections if we have enough requests to send. */
2906 if (sess->num_conns < sess->max_connections)
2907 SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches +
2908 report->num_active_propfinds));
2910 /* Prune completed file PROPFINDs. */
2911 done_list = report->done_propfinds;
2914 svn_ra_serf__list_t *next_done = done_list->next;
2916 svn_pool_clear(iterpool_inner);
2918 report->num_active_propfinds--;
2920 /* If we have some files that we won't be fetching the content
2921 * for, ensure that we update the file with any altered props.
2923 if (report->file_propchanges_only)
2925 svn_ra_serf__list_t *cur, *prev;
2928 cur = report->file_propchanges_only;
2932 report_info_t *item = cur->data;
2934 if (item->propfind_handler == done_list->data)
2943 /* If we found a match, set the new props and remove this
2944 * propchange from our list.
2948 report_info_t *info = cur->data;
2952 report->file_propchanges_only = cur->next;
2956 prev->next = cur->next;
2959 /* If we've got cached file content for this file,
2960 take care of the locally collected properties and
2961 file content at once. Otherwise, just deal with
2962 the collected properties.
2964 NOTE: These functions below could delete
2965 info->dir->pool (via maybe_close_dir_chain()),
2966 from which is allocated the list item in
2967 report->file_propchanges_only.
2969 if (info->cached_contents)
2971 SVN_ERR(handle_local_content(info, iterpool_inner));
2975 SVN_ERR(handle_propchange_only(info, iterpool_inner));
2980 done_list = next_done;
2982 report->done_propfinds = NULL;
2984 /* Prune completed fetches from our list. */
2985 done_list = report->done_fetches;
2988 report_fetch_t *done_fetch = done_list->data;
2989 svn_ra_serf__list_t *next_done = done_list->next;
2990 report_dir_t *cur_dir;
2992 /* Decrease the refcount in the parent directory of the file
2993 whose fetch has completed. */
2994 cur_dir = done_fetch->info->dir;
2995 cur_dir->ref_count--;
2997 /* Decrement our active fetch count. */
2998 report->num_active_fetches--;
3000 /* See if the parent directory of this fetched item (and
3001 perhaps even parents of that) can be closed now.
3003 NOTE: This could delete cur_dir->pool, from which is
3004 allocated the list item in report->done_fetches.
3006 SVN_ERR(maybe_close_dir_chain(cur_dir));
3008 done_list = next_done;
3010 report->done_fetches = NULL;
3012 /* Prune completed directory PROPFINDs. */
3013 done_list = report->done_dir_propfinds;
3016 svn_ra_serf__list_t *next_done = done_list->next;
3018 report->num_active_propfinds--;
3020 if (report->active_dir_propfinds)
3022 svn_ra_serf__list_t *cur, *prev;
3025 cur = report->active_dir_propfinds;
3029 report_dir_t *item = cur->data;
3031 if (item->propfind_handler == done_list->data)
3039 SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */
3041 /* If we found a match, set the new props and remove this
3042 * propchange from our list.
3046 report_dir_t *cur_dir = cur->data;
3050 report->active_dir_propfinds = cur->next;
3054 prev->next = cur->next;
3057 /* See if this directory (and perhaps even parents of that)
3060 NOTE: This could delete cur_dir->pool, from which is
3061 allocated the list item in report->active_dir_propfinds.
3063 SVN_ERR(maybe_close_dir_chain(cur_dir));
3067 done_list = next_done;
3069 report->done_dir_propfinds = NULL;
3071 /* If the parser is paused, and the number of active requests has
3072 dropped far enough, then resume parsing. */
3073 if (parser_ctx->paused
3074 && (report->num_active_fetches + report->num_active_propfinds
3075 < REQUEST_COUNT_TO_RESUME))
3076 parser_ctx->paused = FALSE;
3078 /* If we have not paused the parser and it looks like data MAY be
3079 present (we can't know for sure because of the private structure),
3080 then go process the pending content. */
3081 if (!parser_ctx->paused && parser_ctx->pending != NULL)
3082 SVN_ERR(svn_ra_serf__process_pending(parser_ctx,
3083 &report->report_received,
3086 /* Debugging purposes only! */
3087 for (i = 0; i < sess->num_conns; i++)
3089 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
3093 /* If we got a complete report, close the edit. Otherwise, abort it. */
3094 if (report->report_completed)
3096 /* Ensure that we opened and closed our root dir and that we closed
3097 * all of our children. */
3098 if (!report->closed_root && report->root_dir != NULL)
3100 SVN_ERR(close_all_dirs(report->root_dir));
3103 err = report->update_editor->close_edit(report->update_baton, iterpool);
3107 /* Tell the editor that something failed */
3108 err = report->update_editor->abort_edit(report->update_baton, iterpool);
3110 err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
3111 _("Missing update-report close tag"));
3114 svn_pool_destroy(iterpool);
3115 return svn_error_trace(err);
3119 static svn_error_t *
3120 abort_report(void *report_baton,
3124 report_context_t *report = report_baton;
3127 /* Should we perform some cleanup here? */
3129 return SVN_NO_ERROR;
3132 static const svn_ra_reporter3_t ra_serf_reporter = {
3141 /** RA function implementations and body */
3143 static svn_error_t *
3144 make_update_reporter(svn_ra_session_t *ra_session,
3145 const svn_ra_reporter3_t **reporter,
3146 void **report_baton,
3147 svn_revnum_t revision,
3148 const char *src_path,
3149 const char *dest_path,
3150 const char *update_target,
3152 svn_boolean_t ignore_ancestry,
3153 svn_boolean_t text_deltas,
3154 svn_boolean_t send_copyfrom_args,
3155 const svn_delta_editor_t *update_editor,
3157 apr_pool_t *result_pool,
3158 apr_pool_t *scratch_pool)
3160 report_context_t *report;
3161 const svn_delta_editor_t *filter_editor;
3163 svn_boolean_t has_target = *update_target != '\0';
3164 svn_boolean_t server_supports_depth;
3165 svn_ra_serf__session_t *sess = ra_session->priv;
3166 svn_stringbuf_t *buf = NULL;
3167 svn_boolean_t use_bulk_updates;
3169 SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
3170 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
3171 /* We can skip the depth filtering when the user requested
3172 depth_files or depth_infinity because the server will
3173 transmit the right stuff anyway. */
3174 if ((depth != svn_depth_files)
3175 && (depth != svn_depth_infinity)
3176 && ! server_supports_depth)
3178 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
3184 update_editor = filter_editor;
3185 update_baton = filter_baton;
3188 report = apr_pcalloc(result_pool, sizeof(*report));
3189 report->pool = result_pool;
3190 report->sess = sess;
3191 report->conn = report->sess->conns[0];
3192 report->target_rev = revision;
3193 report->ignore_ancestry = ignore_ancestry;
3194 report->send_copyfrom_args = send_copyfrom_args;
3195 report->text_deltas = text_deltas;
3196 report->lock_path_tokens = apr_hash_make(report->pool);
3197 report->switched_paths = apr_hash_make(report->pool);
3199 report->source = src_path;
3200 report->destination = dest_path;
3201 report->update_target = update_target;
3203 report->update_editor = update_editor;
3204 report->update_baton = update_baton;
3205 report->done = FALSE;
3207 *reporter = &ra_serf_reporter;
3208 *report_baton = report;
3210 SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL,
3211 svn_io_file_del_on_pool_cleanup,
3212 report->pool, scratch_pool));
3214 if (sess->bulk_updates == svn_tristate_true)
3216 /* User would like to use bulk updates. */
3217 use_bulk_updates = TRUE;
3219 else if (sess->bulk_updates == svn_tristate_false)
3221 /* User doesn't want bulk updates. */
3222 use_bulk_updates = FALSE;
3226 /* User doesn't have any preferences on bulk updates. Decide on server
3227 preferences and capabilities. */
3228 if (sess->server_allows_bulk)
3230 if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
3232 /* Server doesn't want bulk updates */
3233 use_bulk_updates = FALSE;
3235 else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
3237 /* Server prefers bulk updates, and we respect that */
3238 use_bulk_updates = TRUE;
3242 /* Server allows bulk updates, but doesn't dictate its use. Do
3243 whatever is the default. */
3244 use_bulk_updates = FALSE;
3249 /* Pre-1.8 server didn't send the bulk_updates header. Check if server
3250 supports inlining properties in update editor report. */
3251 if (sess->supports_inline_props)
3253 /* Inline props supported: do not use bulk updates. */
3254 use_bulk_updates = FALSE;
3258 /* Inline props are not supported: use bulk updates to avoid
3259 * PROPFINDs for every added node. */
3260 use_bulk_updates = TRUE;
3265 if (use_bulk_updates)
3267 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
3269 "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
3274 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
3276 "xmlns:S", SVN_XML_NAMESPACE,
3278 /* Subversion 1.8+ servers can be told to send properties for newly
3279 added items inline even when doing a skelta response. */
3280 make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
3283 make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
3285 if (SVN_IS_VALID_REVNUM(report->target_rev))
3287 make_simple_xml_tag(&buf, "S:target-revision",
3288 apr_ltoa(scratch_pool, report->target_rev),
3292 if (report->destination && *report->destination)
3294 make_simple_xml_tag(&buf, "S:dst-path", report->destination,
3298 if (report->update_target && *report->update_target)
3300 make_simple_xml_tag(&buf, "S:update-target", report->update_target,
3304 if (report->ignore_ancestry)
3306 make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
3309 if (report->send_copyfrom_args)
3311 make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
3314 /* Old servers know "recursive" but not "depth"; help them DTRT. */
3315 if (depth == svn_depth_files || depth == svn_depth_empty)
3317 make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
3320 /* When in 'send-all' mode, mod_dav_svn will assume that it should
3321 calculate and transmit real text-deltas (instead of empty windows
3322 that merely indicate "text is changed") unless it finds this
3325 NOTE: Do NOT count on servers actually obeying this, as some exist
3326 which obey send-all, but do not check for this directive at all!
3328 NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
3329 override our request and send text-deltas. */
3332 make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
3335 make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
3337 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
3338 NULL, scratch_pool));
3340 return SVN_NO_ERROR;
3344 svn_ra_serf__do_update(svn_ra_session_t *ra_session,
3345 const svn_ra_reporter3_t **reporter,
3346 void **report_baton,
3347 svn_revnum_t revision_to_update_to,
3348 const char *update_target,
3350 svn_boolean_t send_copyfrom_args,
3351 svn_boolean_t ignore_ancestry,
3352 const svn_delta_editor_t *update_editor,
3354 apr_pool_t *result_pool,
3355 apr_pool_t *scratch_pool)
3357 svn_ra_serf__session_t *session = ra_session->priv;
3359 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3360 revision_to_update_to,
3361 session->session_url.path, NULL, update_target,
3362 depth, ignore_ancestry, TRUE /* text_deltas */,
3364 update_editor, update_baton,
3365 result_pool, scratch_pool));
3366 return SVN_NO_ERROR;
3370 svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
3371 const svn_ra_reporter3_t **reporter,
3372 void **report_baton,
3373 svn_revnum_t revision,
3374 const char *diff_target,
3376 svn_boolean_t ignore_ancestry,
3377 svn_boolean_t text_deltas,
3378 const char *versus_url,
3379 const svn_delta_editor_t *diff_editor,
3383 svn_ra_serf__session_t *session = ra_session->priv;
3384 apr_pool_t *scratch_pool = svn_pool_create(pool);
3386 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3388 session->session_url.path, versus_url, diff_target,
3389 depth, ignore_ancestry, text_deltas, FALSE,
3390 diff_editor, diff_baton,
3391 pool, scratch_pool));
3392 svn_pool_destroy(scratch_pool);
3393 return SVN_NO_ERROR;
3397 svn_ra_serf__do_status(svn_ra_session_t *ra_session,
3398 const svn_ra_reporter3_t **reporter,
3399 void **report_baton,
3400 const char *status_target,
3401 svn_revnum_t revision,
3403 const svn_delta_editor_t *status_editor,
3407 svn_ra_serf__session_t *session = ra_session->priv;
3408 apr_pool_t *scratch_pool = svn_pool_create(pool);
3410 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3412 session->session_url.path, NULL, status_target,
3413 depth, FALSE, FALSE, FALSE,
3414 status_editor, status_baton,
3415 pool, scratch_pool));
3416 svn_pool_destroy(scratch_pool);
3417 return SVN_NO_ERROR;
3421 svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
3422 const svn_ra_reporter3_t **reporter,
3423 void **report_baton,
3424 svn_revnum_t revision_to_switch_to,
3425 const char *switch_target,
3427 const char *switch_url,
3428 svn_boolean_t send_copyfrom_args,
3429 svn_boolean_t ignore_ancestry,
3430 const svn_delta_editor_t *switch_editor,
3432 apr_pool_t *result_pool,
3433 apr_pool_t *scratch_pool)
3435 svn_ra_serf__session_t *session = ra_session->priv;
3437 return make_update_reporter(ra_session, reporter, report_baton,
3438 revision_to_switch_to,
3439 session->session_url.path,
3440 switch_url, switch_target,
3443 TRUE /* text_deltas */,
3445 switch_editor, switch_baton,
3446 result_pool, scratch_pool);
3449 /* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
3450 * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
3453 * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
3455 * Performs all temporary allocations in POOL.
3457 static svn_error_t *
3458 try_get_wc_contents(svn_boolean_t *found_p,
3459 svn_ra_serf__session_t *session,
3461 svn_stream_t *dst_stream,
3464 apr_hash_t *svn_props;
3465 const char *sha1_checksum_prop;
3466 svn_checksum_t *checksum;
3467 svn_stream_t *wc_stream;
3470 /* No contents found by default. */
3473 if (!session->wc_callbacks->get_wc_contents)
3475 /* No callback, nothing to do. */
3476 return SVN_NO_ERROR;
3480 svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
3483 /* No properties -- therefore no checksum property -- in response. */
3484 return SVN_NO_ERROR;
3487 sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum");
3488 if (sha1_checksum_prop == NULL)
3490 /* No checksum property in response. */
3491 return SVN_NO_ERROR;
3494 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
3495 sha1_checksum_prop, pool));
3497 err = session->wc_callbacks->get_wc_contents(
3498 session->wc_callback_baton, &wc_stream, checksum, pool);
3502 svn_error_clear(err);
3504 /* Ignore errors for now. */
3505 return SVN_NO_ERROR;
3510 SVN_ERR(svn_stream_copy3(wc_stream,
3511 svn_stream_disown(dst_stream, pool),
3516 return SVN_NO_ERROR;
3520 svn_ra_serf__get_file(svn_ra_session_t *ra_session,
3522 svn_revnum_t revision,
3523 svn_stream_t *stream,
3524 svn_revnum_t *fetched_rev,
3528 svn_ra_serf__session_t *session = ra_session->priv;
3529 svn_ra_serf__connection_t *conn;
3530 const char *fetch_url;
3531 apr_hash_t *fetch_props;
3532 svn_node_kind_t res_kind;
3533 const svn_ra_serf__dav_props_t *which_props;
3535 /* What connection should we go on? */
3536 conn = session->conns[session->cur_conn];
3538 /* Fetch properties. */
3540 fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
3542 /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
3544 * Otherwise, we need to get the baseline version for this particular
3545 * revision and then fetch that file.
3547 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
3549 SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
3551 fetch_url, revision,
3553 revision = SVN_INVALID_REVNUM;
3555 /* REVISION is always SVN_INVALID_REVNUM */
3556 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
3560 which_props = all_props;
3562 else if (stream && session->wc_callbacks->get_wc_contents)
3564 which_props = type_and_checksum_props;
3568 which_props = check_path_props;
3571 SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url,
3576 /* Verify that resource type is not collection. */
3577 SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props));
3578 if (res_kind != svn_node_file)
3580 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
3581 _("Can't get text contents of a directory"));
3584 /* TODO Filter out all of our props into a usable format. */
3587 /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
3588 ### put them into POOL, so we're okay. */
3589 SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props,
3595 svn_boolean_t found;
3596 SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool));
3598 /* No contents found in the WC, let's fetch from server. */
3601 report_fetch_t *stream_ctx;
3602 svn_ra_serf__handler_t *handler;
3604 /* Create the fetch context. */
3605 stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
3606 stream_ctx->target_stream = stream;
3607 stream_ctx->sess = session;
3608 stream_ctx->conn = conn;
3609 stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
3610 stream_ctx->info->name = fetch_url;
3612 handler = apr_pcalloc(pool, sizeof(*handler));
3614 handler->handler_pool = pool;
3615 handler->method = "GET";
3616 handler->path = fetch_url;
3617 handler->conn = conn;
3618 handler->session = session;
3620 handler->custom_accept_encoding = TRUE;
3621 handler->header_delegate = headers_fetch;
3622 handler->header_delegate_baton = stream_ctx;
3624 handler->response_handler = handle_stream;
3625 handler->response_baton = stream_ctx;
3627 handler->response_error = cancel_fetch;
3628 handler->response_error_baton = stream_ctx;
3630 stream_ctx->handler = handler;
3632 svn_ra_serf__request_create(handler);
3634 SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
3638 return SVN_NO_ERROR;