Reorganize the output to "svnserve --help".
[svn.git] / subversion / libsvn_ra_serf / update.c
blob9aef69a8d99b8746ee05a82ed09e8f353f5abe4e
1 /*
2 * update.c : entry point for update RA functions for ra_serf
4 * ====================================================================
5 * Copyright (c) 2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
24 #include <apr_uri.h>
26 #include <expat.h>
28 #include <serf.h>
30 #include "svn_pools.h"
31 #include "svn_ra.h"
32 #include "svn_dav.h"
33 #include "svn_xml.h"
34 #include "../libsvn_ra/ra_loader.h"
35 #include "svn_config.h"
36 #include "svn_delta.h"
37 #include "svn_version.h"
38 #include "svn_path.h"
39 #include "svn_base64.h"
40 #include "svn_private_config.h"
42 #include "ra_serf.h"
46 * This enum represents the current state of our XML parsing for a REPORT.
48 * A little explanation of how the parsing works. Every time we see
49 * an open-directory tag, we enter the OPEN_DIR state. Likewise, for
50 * add-directory, open-file, etc. When we see the closing variant of the
51 * open-directory tag, we'll 'pop' out of that state.
53 * Each state has a pool associated with it that can have temporary
54 * allocations that will live as long as the tag is opened. Once
55 * the tag is 'closed', the pool will be reused.
57 typedef enum {
58 NONE = 0,
59 OPEN_DIR,
60 ADD_DIR,
61 OPEN_FILE,
62 ADD_FILE,
63 PROP,
64 IGNORE_PROP_NAME,
65 NEED_PROP_NAME,
66 } report_state_e;
68 /* Forward-declare our report context. */
69 typedef struct report_context_t report_context_t;
72 * This structure represents the information for a directory.
74 typedef struct report_dir_t
76 /* Our parent directory.
78 * This value is NULL when we are the root.
80 struct report_dir_t *parent_dir;
82 apr_pool_t *pool;
84 /* Pointer back to our original report context. */
85 report_context_t *report_context;
87 /* Our name sans any parents. */
88 const char *base_name;
90 /* the expanded directory name (including all parent names) */
91 const char *name;
93 /* temporary path buffer for this directory. */
94 svn_stringbuf_t *name_buf;
96 /* the canonical url for this directory. */
97 const char *url;
99 /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */
100 svn_revnum_t base_rev;
102 /* The target revision we're retrieving. */
103 svn_revnum_t target_rev;
105 /* controlling dir baton - this is only created in open_dir() */
106 void *dir_baton;
107 apr_pool_t *dir_baton_pool;
109 /* Our master update editor and baton. */
110 const svn_delta_editor_t *update_editor;
111 void *update_baton;
113 /* How many references to this directory do we still have open? */
114 apr_size_t ref_count;
116 /* Namespace list allocated out of this ->pool. */
117 svn_ra_serf__ns_t *ns_list;
119 /* hashtable for all of the properties (shared within a dir) */
120 apr_hash_t *props;
122 /* hashtable for all to-be-removed properties (shared within a dir) */
123 apr_hash_t *removed_props;
125 /* The propfind request for our current directory */
126 svn_ra_serf__propfind_context_t *propfind;
128 /* Has the server told us to fetch the dir props? */
129 svn_boolean_t fetch_props;
131 /* Have we closed the directory tag (meaning no more additions)? */
132 svn_boolean_t tag_closed;
134 /* The children of this directory */
135 struct report_dir_t *children;
137 /* The next sibling of this directory */
138 struct report_dir_t *sibling;
139 } report_dir_t;
142 * This structure represents the information for a file.
144 * A directory may have a report_info_t associated with it as well.
146 * This structure is created as we parse the REPORT response and
147 * once the element is completed, we create a report_fetch_t structure
148 * to give to serf to retrieve this file.
150 typedef struct report_info_t
152 apr_pool_t *pool;
154 /* The enclosing directory.
156 * If this structure refers to a directory, the dir it points to will be
157 * itself.
159 report_dir_t *dir;
161 /* Our name sans any directory info. */
162 const char *base_name;
164 /* the expanded file name (including all parent directory names) */
165 const char *name;
167 /* file name buffer */
168 svn_stringbuf_t *name_buf;
170 /* the canonical url for this file. */
171 const char *url;
173 /* lock token, if we had one to start off with. */
174 const char *lock_token;
176 /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */
177 svn_revnum_t base_rev;
179 /* The target revision we're retrieving. */
180 svn_revnum_t target_rev;
182 /* our delta base, if present (NULL if we're adding the file) */
183 const svn_string_t *delta_base;
185 /* Path of original item if add with history */
186 const char *copyfrom_path;
188 /* Revision of original item if add with history */
189 svn_revnum_t copyfrom_rev;
191 /* The propfind request for our current file (if present) */
192 svn_ra_serf__propfind_context_t *propfind;
194 /* Has the server told us to fetch the file props? */
195 svn_boolean_t fetch_props;
197 /* Has the server told us to go fetch - only valid if we had it already */
198 svn_boolean_t fetch_file;
200 /* The properties for this file */
201 apr_hash_t *props;
203 /* pool passed to update->add_file, etc. */
204 apr_pool_t *editor_pool;
206 /* controlling file_baton and textdelta handler */
207 void *file_baton;
208 svn_txdelta_window_handler_t textdelta;
209 void *textdelta_baton;
211 /* temporary property for this file which is currently being parsed
212 * It will eventually be stored in our parent directory's property hash.
214 const char *prop_ns;
215 const char *prop_name;
216 const char *prop_val;
217 apr_size_t prop_val_len;
218 const char *prop_encoding;
219 } report_info_t;
222 * This structure represents a single request to GET (fetch) a file with
223 * its associated Serf session/connection.
225 typedef struct report_fetch_t {
226 /* Our pool. */
227 apr_pool_t *pool;
229 /* Non-NULL if we received an error during processing. */
230 svn_error_t *err;
232 /* The session we should use to fetch the file. */
233 svn_ra_serf__session_t *sess;
235 /* The connection we should use to fetch file. */
236 svn_ra_serf__connection_t *conn;
238 /* Stores the information for the file we want to fetch. */
239 report_info_t *info;
241 /* Have we read our response headers yet? */
242 svn_boolean_t read_headers;
244 /* This flag is set when our response is aborted before we reach the
245 * end and we decide to requeue this request.
247 svn_boolean_t aborted_read;
248 apr_off_t aborted_read_size;
250 /* This is the amount of data that we have read so far. */
251 apr_off_t read_size;
253 /* If we're receiving an svndiff, this will be non-NULL. */
254 svn_stream_t *delta_stream;
256 /* If we're writing this file to a stream, this will be non-NULL. */
257 svn_stream_t *target_stream;
259 /* Are we done fetching this file? */
260 svn_boolean_t done;
261 svn_ra_serf__list_t **done_list;
262 svn_ra_serf__list_t done_item;
264 } report_fetch_t;
267 * The master structure for a REPORT request and response.
269 struct report_context_t {
270 apr_pool_t *pool;
272 svn_ra_serf__session_t *sess;
273 svn_ra_serf__connection_t *conn;
275 /* Source path and destination path */
276 const char *source;
277 const char *destination;
279 /* Our update target. */
280 const char *update_target;
282 /* What is the target revision that we want for this REPORT? */
283 svn_revnum_t target_rev;
285 /* Have we been asked to ignore ancestry or textdeltas? */
286 svn_boolean_t ignore_ancestry;
287 svn_boolean_t text_deltas;
289 /* Do we want the server to send copyfrom args or not? */
290 svn_boolean_t send_copyfrom_args;
292 /* Path -> lock token mapping. */
293 apr_hash_t *lock_path_tokens;
295 /* Our master update editor and baton. */
296 const svn_delta_editor_t *update_editor;
297 void *update_baton;
299 /* The request body for the REPORT. */
300 serf_bucket_t *buckets;
302 /* root directory object */
303 report_dir_t *root_dir;
305 /* number of pending GET requests */
306 unsigned int active_fetches;
308 /* completed fetches (contains report_fetch_t) */
309 svn_ra_serf__list_t *done_fetches;
311 /* number of pending PROPFIND requests */
312 unsigned int active_propfinds;
314 /* completed PROPFIND requests (contains propfind_context_t) */
315 svn_ra_serf__list_t *done_propfinds;
317 /* list of files that only have prop changes (contains report_info_t) */
318 svn_ra_serf__list_t *file_propchanges_only;
320 /* The path to the REPORT request */
321 const char *path;
323 /* Are we done parsing the REPORT response? */
324 svn_boolean_t done;
329 /** Report state management helper **/
331 static report_info_t *
332 push_state(svn_ra_serf__xml_parser_t *parser,
333 report_context_t *ctx,
334 report_state_e state)
336 report_info_t *info;
337 apr_pool_t *info_parent_pool;
339 svn_ra_serf__xml_push_state(parser, state);
341 info = parser->state->private;
343 /* Our private pool needs to be disjoint from the state pool. */
344 if (!info)
346 info_parent_pool = ctx->pool;
348 else
350 info_parent_pool = info->pool;
353 if (state == OPEN_DIR || state == ADD_DIR)
355 report_info_t *new_info;
357 new_info = apr_palloc(info_parent_pool, sizeof(*new_info));
358 apr_pool_create(&new_info->pool, info_parent_pool);
359 new_info->lock_token = NULL;
361 new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
362 new_info->dir->pool = new_info->pool;
364 /* Create the root property tree. */
365 new_info->dir->props = apr_hash_make(new_info->pool);
366 new_info->props = new_info->dir->props;
367 new_info->dir->removed_props = apr_hash_make(new_info->pool);
369 /* Point to the update_editor */
370 new_info->dir->update_editor = ctx->update_editor;
371 new_info->dir->update_baton = ctx->update_baton;
372 new_info->dir->report_context = ctx;
374 if (info)
376 info->dir->ref_count++;
378 new_info->dir->parent_dir = info->dir;
380 /* Point our ns_list at our parents to try to reuse it. */
381 new_info->dir->ns_list = info->dir->ns_list;
383 /* Add ourselves to our parent's list */
384 new_info->dir->sibling = info->dir->children;
385 info->dir->children = new_info->dir;
387 else
389 /* Allow us to be found later. */
390 ctx->root_dir = new_info->dir;
393 parser->state->private = new_info;
395 else if (state == OPEN_FILE || state == ADD_FILE)
397 report_info_t *new_info;
399 new_info = apr_palloc(info_parent_pool, sizeof(*new_info));
400 apr_pool_create(&new_info->pool, info_parent_pool);
401 new_info->file_baton = NULL;
402 new_info->lock_token = NULL;
403 new_info->fetch_file = FALSE;
405 /* Point at our parent's directory state. */
406 new_info->dir = info->dir;
407 info->dir->ref_count++;
409 new_info->props = apr_hash_make(new_info->pool);
411 parser->state->private = new_info;
414 return parser->state->private;
418 /** Wrappers around our various property walkers **/
420 static svn_error_t *
421 set_file_props(void *baton,
422 const char *ns, apr_ssize_t ns_len,
423 const char *name, apr_ssize_t name_len,
424 const svn_string_t *val,
425 apr_pool_t *pool)
427 report_info_t *info = baton;
428 const svn_delta_editor_t *editor = info->dir->update_editor;
430 return svn_ra_serf__set_baton_props(editor->change_file_prop,
431 info->file_baton,
432 ns, ns_len, name, name_len, val, pool);
435 static svn_error_t *
436 set_dir_props(void *baton,
437 const char *ns, apr_ssize_t ns_len,
438 const char *name, apr_ssize_t name_len,
439 const svn_string_t *val,
440 apr_pool_t *pool)
442 report_dir_t *dir = baton;
443 return svn_ra_serf__set_baton_props(dir->update_editor->change_dir_prop,
444 dir->dir_baton,
445 ns, ns_len, name, name_len, val, pool);
448 static svn_error_t *
449 remove_file_props(void *baton,
450 const char *ns, apr_ssize_t ns_len,
451 const char *name, apr_ssize_t name_len,
452 const svn_string_t *val,
453 apr_pool_t *pool)
455 report_info_t *info = baton;
456 const svn_delta_editor_t *editor = info->dir->update_editor;
458 return svn_ra_serf__set_baton_props(editor->change_file_prop,
459 info->file_baton,
460 ns, ns_len, name, name_len, NULL, pool);
463 static svn_error_t *
464 remove_dir_props(void *baton,
465 const char *ns, apr_ssize_t ns_len,
466 const char *name, apr_ssize_t name_len,
467 const svn_string_t *val,
468 apr_pool_t *pool)
470 report_dir_t *dir = baton;
471 return svn_ra_serf__set_baton_props(dir->update_editor->change_dir_prop,
472 dir->dir_baton,
473 ns, ns_len, name, name_len, NULL, pool);
477 /** Helpers to open and close directories */
479 static svn_error_t*
480 open_dir(report_dir_t *dir)
482 /* if we're already open, return now */
483 if (dir->dir_baton)
485 return SVN_NO_ERROR;
488 if (dir->base_name[0] == '\0')
490 apr_pool_create(&dir->dir_baton_pool, dir->pool);
492 if (dir->report_context->destination &&
493 dir->report_context->sess->wc_callbacks->invalidate_wc_props)
495 SVN_ERR(dir->report_context->sess->wc_callbacks->invalidate_wc_props(
496 dir->report_context->sess->wc_callback_baton,
497 dir->report_context->update_target,
498 SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
501 SVN_ERR(dir->update_editor->open_root(dir->update_baton, dir->base_rev,
502 dir->dir_baton_pool,
503 &dir->dir_baton));
505 else
507 SVN_ERR(open_dir(dir->parent_dir));
509 apr_pool_create(&dir->dir_baton_pool, dir->parent_dir->dir_baton_pool);
511 if (SVN_IS_VALID_REVNUM(dir->base_rev))
513 SVN_ERR(dir->update_editor->open_directory(dir->name,
514 dir->parent_dir->dir_baton,
515 dir->base_rev,
516 dir->dir_baton_pool,
517 &dir->dir_baton));
519 else
521 SVN_ERR(dir->update_editor->add_directory(dir->name,
522 dir->parent_dir->dir_baton,
523 NULL, SVN_INVALID_REVNUM,
524 dir->dir_baton_pool,
525 &dir->dir_baton));
529 return SVN_NO_ERROR;
532 static svn_error_t *
533 close_dir(report_dir_t *dir)
535 report_dir_t *prev, *sibling;
537 if (dir->ref_count)
539 abort();
542 svn_ra_serf__walk_all_props(dir->props, dir->base_name, dir->base_rev,
543 set_dir_props, dir, dir->dir_baton_pool);
545 svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name,
546 dir->base_rev, remove_dir_props, dir,
547 dir->dir_baton_pool);
549 if (dir->fetch_props)
551 svn_ra_serf__walk_all_props(dir->props, dir->url, dir->target_rev,
552 set_dir_props, dir, dir->dir_baton_pool);
555 SVN_ERR(dir->update_editor->close_directory(dir->dir_baton,
556 dir->dir_baton_pool));
558 /* remove us from our parent's children list */
559 if (dir->parent_dir)
561 prev = NULL;
562 sibling = dir->parent_dir->children;
564 while (sibling != dir)
566 prev = sibling;
567 sibling = sibling->sibling;
568 if (!sibling)
569 abort();
572 if (!prev)
574 dir->parent_dir->children = dir->sibling;
576 else
578 prev->sibling = dir->sibling;
582 svn_pool_destroy(dir->dir_baton_pool);
583 svn_pool_destroy(dir->pool);
585 return SVN_NO_ERROR;
588 static svn_error_t *close_all_dirs(report_dir_t *dir)
590 while (dir->children)
592 SVN_ERR(close_all_dirs(dir->children));
593 dir->ref_count--;
596 if (dir->ref_count)
598 abort();
601 SVN_ERR(open_dir(dir));
603 return close_dir(dir);
607 /** Routines called when we are fetching a file */
609 /* This function works around a bug in mod_dav_svn in that it will not
610 * send remove-prop in the update report when a lock property disappears
611 * when send-all is false.
613 * Therefore, we'll try to look at our properties and see if there's
614 * an active lock. If not, then we'll assume there isn't a lock
615 * anymore.
617 static void
618 check_lock(report_info_t *info)
620 const char *lock_val;
622 lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
623 info->target_rev,
624 "DAV:", "lockdiscovery");
626 if (lock_val)
628 char *new_lock;
629 new_lock = apr_pstrdup(info->editor_pool, lock_val);
630 apr_collapse_spaces(new_lock, new_lock);
631 lock_val = new_lock;
634 if (!lock_val || lock_val[0] == '\0')
636 svn_string_t *str;
638 str = svn_string_ncreate("", 1, info->editor_pool);
640 svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name,
641 info->base_rev, "DAV:", "lock-token",
642 str, info->dir->pool);
646 static apr_status_t
647 headers_fetch(serf_bucket_t *headers,
648 void *baton,
649 apr_pool_t *pool)
651 report_fetch_t *fetch_ctx = baton;
653 /* note that we have old VC URL */
654 if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) &&
655 fetch_ctx->info->delta_base)
657 serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
658 fetch_ctx->info->delta_base->data);
659 serf_bucket_headers_setn(headers, "Accept-Encoding",
660 "svndiff1;q=0.9,svndiff;q=0.8");
662 else if (fetch_ctx->conn->using_compression == TRUE)
664 serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
667 return APR_SUCCESS;
670 static apr_status_t
671 cancel_fetch(serf_request_t *request,
672 serf_bucket_t *response,
673 int status_code,
674 void *baton)
676 report_fetch_t *fetch_ctx = baton;
678 /* Uh-oh. Our connection died on us.
680 * The core ra_serf layer will requeue our request - we just need to note
681 * that we got cut off in the middle of our song.
683 if (!response)
685 /* If we already started the fetch and opened the file handle, we need
686 * to hold subsequent read() ops until we get back to where we were
687 * before the close and we can then resume the textdelta() calls.
689 if (fetch_ctx->read_headers == TRUE)
691 if (fetch_ctx->aborted_read == FALSE && fetch_ctx->read_size)
693 fetch_ctx->aborted_read = TRUE;
694 fetch_ctx->aborted_read_size = fetch_ctx->read_size;
696 fetch_ctx->read_size = 0;
699 return APR_SUCCESS;
702 /* We have no idea what went wrong. */
703 abort();
706 static apr_status_t
707 error_fetch(serf_request_t *request,
708 report_fetch_t *fetch_ctx,
709 svn_error_t *err)
711 fetch_ctx->err = err;
713 fetch_ctx->done = TRUE;
715 fetch_ctx->done_item.data = fetch_ctx;
716 fetch_ctx->done_item.next = *fetch_ctx->done_list;
717 *fetch_ctx->done_list = &fetch_ctx->done_item;
719 serf_request_set_handler(request, svn_ra_serf__handle_discard_body, NULL);
721 return APR_SUCCESS;
724 static apr_status_t
725 handle_fetch(serf_request_t *request,
726 serf_bucket_t *response,
727 void *handler_baton,
728 apr_pool_t *pool)
730 const char *data;
731 apr_size_t len;
732 apr_status_t status;
733 report_fetch_t *fetch_ctx = handler_baton;
734 svn_error_t *err;
735 serf_status_line sl;
737 if (fetch_ctx->read_headers == FALSE)
739 serf_bucket_t *hdrs;
740 const char *val;
741 report_info_t *info;
743 hdrs = serf_bucket_response_get_headers(response);
744 val = serf_bucket_headers_get(hdrs, "Content-Type");
745 info = fetch_ctx->info;
747 err = open_dir(info->dir);
748 if (err)
750 return error_fetch(request, fetch_ctx, err);
753 apr_pool_create(&info->editor_pool, info->dir->dir_baton_pool);
755 /* Expand our full name now if we haven't done so yet. */
756 if (!info->name)
758 info->name_buf = svn_stringbuf_dup(info->dir->name_buf,
759 info->editor_pool);
760 svn_path_add_component(info->name_buf, info->base_name);
761 info->name = info->name_buf->data;
764 if (SVN_IS_VALID_REVNUM(info->base_rev))
766 err = info->dir->update_editor->open_file(info->name,
767 info->dir->dir_baton,
768 info->base_rev,
769 info->editor_pool,
770 &info->file_baton);
772 else
774 err = info->dir->update_editor->add_file(info->name,
775 info->dir->dir_baton,
776 info->copyfrom_path,
777 info->copyfrom_rev,
778 info->editor_pool,
779 &info->file_baton);
782 if (err)
784 return error_fetch(request, fetch_ctx, err);
787 err = info->dir->update_editor->apply_textdelta(info->file_baton,
788 NULL,
789 info->editor_pool,
790 &info->textdelta,
791 &info->textdelta_baton);
793 if (err)
795 return error_fetch(request, fetch_ctx, err);
798 if (val && svn_cstring_casecmp(val, "application/vnd.svn-svndiff") == 0)
800 fetch_ctx->delta_stream =
801 svn_txdelta_parse_svndiff(info->textdelta,
802 info->textdelta_baton,
803 TRUE, info->editor_pool);
805 else
807 fetch_ctx->delta_stream = NULL;
810 fetch_ctx->read_headers = TRUE;
813 /* If the error code wasn't 200, something went wrong. Don't use the returned
814 data as its probably an error message. Just bail out instead. */
815 status = serf_bucket_response_status(response, &sl);
816 if (SERF_BUCKET_READ_ERROR(status))
818 return status;
820 if (sl.code != 200)
822 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
823 _("GET request failed: %d %s"),
824 sl.code, sl.reason);
825 return error_fetch(request, fetch_ctx, err);
828 while (1)
830 svn_txdelta_window_t delta_window = { 0 };
831 svn_txdelta_op_t delta_op;
832 svn_string_t window_data;
834 status = serf_bucket_read(response, 8000, &data, &len);
835 if (SERF_BUCKET_READ_ERROR(status))
837 return status;
840 fetch_ctx->read_size += len;
842 if (fetch_ctx->aborted_read == TRUE)
844 /* We haven't caught up to where we were before. */
845 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
847 /* Eek. What did the file shrink or something? */
848 if (APR_STATUS_IS_EOF(status))
850 abort();
853 /* Skip on to the next iteration of this loop. */
854 if (APR_STATUS_IS_EAGAIN(status))
856 return status;
858 continue;
861 /* Woo-hoo. We're back. */
862 fetch_ctx->aborted_read = FALSE;
864 /* Increment data and len by the difference. */
865 data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
866 len = fetch_ctx->read_size - fetch_ctx->aborted_read_size;
869 if (fetch_ctx->delta_stream)
871 err = svn_stream_write(fetch_ctx->delta_stream, data, &len);
872 if (err)
874 return error_fetch(request, fetch_ctx, err);
877 /* otherwise, manually construct the text delta window. */
878 else if (len)
880 window_data.data = data;
881 window_data.len = len;
883 delta_op.action_code = svn_txdelta_new;
884 delta_op.offset = 0;
885 delta_op.length = len;
887 delta_window.tview_len = len;
888 delta_window.num_ops = 1;
889 delta_window.ops = &delta_op;
890 delta_window.new_data = &window_data;
892 /* write to the file located in the info. */
893 err = fetch_ctx->info->textdelta(&delta_window,
894 fetch_ctx->info->textdelta_baton);
895 if (err)
897 return error_fetch(request, fetch_ctx, err);
901 if (APR_STATUS_IS_EOF(status))
903 report_info_t *info = fetch_ctx->info;
905 err = info->textdelta(NULL, info->textdelta_baton);
906 if (err)
908 return error_fetch(request, fetch_ctx, err);
911 if (info->lock_token)
912 check_lock(info);
914 /* set all of the properties we received */
915 svn_ra_serf__walk_all_props(info->props,
916 info->base_name,
917 info->base_rev,
918 set_file_props,
919 info, info->editor_pool);
920 svn_ra_serf__walk_all_props(info->dir->removed_props,
921 info->base_name,
922 info->base_rev,
923 remove_file_props,
924 info, info->editor_pool);
925 if (info->fetch_props)
927 svn_ra_serf__walk_all_props(info->props,
928 info->url,
929 info->target_rev,
930 set_file_props,
931 info, info->editor_pool);
934 err = info->dir->update_editor->close_file(info->file_baton, NULL,
935 info->editor_pool);
937 if (err)
939 return error_fetch(request, fetch_ctx, err);
942 fetch_ctx->done = TRUE;
944 fetch_ctx->done_item.data = fetch_ctx;
945 fetch_ctx->done_item.next = *fetch_ctx->done_list;
946 *fetch_ctx->done_list = &fetch_ctx->done_item;
948 /* We're done with our pools. */
949 svn_pool_destroy(info->editor_pool);
950 svn_pool_destroy(info->pool);
952 return status;
954 if (APR_STATUS_IS_EAGAIN(status))
956 return status;
959 /* not reached */
962 static apr_status_t
963 handle_stream(serf_request_t *request,
964 serf_bucket_t *response,
965 void *handler_baton,
966 apr_pool_t *pool)
968 report_fetch_t *fetch_ctx = handler_baton;
969 serf_status_line sl;
971 serf_bucket_response_status(response, &sl);
973 /* Woo-hoo. Nothing here to see. */
974 if (sl.code == 404)
976 fetch_ctx->done = TRUE;
977 fetch_ctx->err = svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND, NULL,
978 "'%s' path not found",
979 fetch_ctx->info->name);
980 return svn_ra_serf__handle_discard_body(request, response, NULL, pool);
983 while (1)
985 const char *data;
986 apr_size_t len;
987 apr_status_t status;
989 status = serf_bucket_read(response, 8000, &data, &len);
990 if (SERF_BUCKET_READ_ERROR(status))
992 return status;
995 fetch_ctx->read_size += len;
997 if (fetch_ctx->aborted_read == TRUE)
999 /* We haven't caught up to where we were before. */
1000 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1002 /* Eek. What did the file shrink or something? */
1003 if (APR_STATUS_IS_EOF(status))
1005 abort();
1008 /* Skip on to the next iteration of this loop. */
1009 if (APR_STATUS_IS_EAGAIN(status))
1011 return status;
1013 continue;
1016 /* Woo-hoo. We're back. */
1017 fetch_ctx->aborted_read = FALSE;
1019 /* Increment data and len by the difference. */
1020 data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1021 len += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1024 if (len)
1026 apr_size_t written_len;
1028 written_len = len;
1030 svn_stream_write(fetch_ctx->target_stream, data, &written_len);
1033 if (APR_STATUS_IS_EOF(status))
1035 fetch_ctx->done = TRUE;
1038 if (status)
1040 return status;
1043 /* not reached */
1046 static svn_error_t *
1047 handle_propchange_only(report_info_t *info)
1049 /* Ensure our parent is open. */
1050 SVN_ERR(open_dir(info->dir));
1052 apr_pool_create(&info->editor_pool, info->dir->dir_baton_pool);
1054 /* Expand our full name now if we haven't done so yet. */
1055 if (!info->name)
1057 info->name_buf = svn_stringbuf_dup(info->dir->name_buf,
1058 info->editor_pool);
1059 svn_path_add_component(info->name_buf, info->base_name);
1060 info->name = info->name_buf->data;
1063 if (SVN_IS_VALID_REVNUM(info->base_rev))
1065 SVN_ERR(info->dir->update_editor->open_file(info->name,
1066 info->dir->dir_baton,
1067 info->base_rev,
1068 info->editor_pool,
1069 &info->file_baton));
1071 else
1073 SVN_ERR(info->dir->update_editor->add_file(info->name,
1074 info->dir->dir_baton,
1075 info->copyfrom_path,
1076 info->copyfrom_rev,
1077 info->editor_pool,
1078 &info->file_baton));
1081 if (info->fetch_file)
1083 SVN_ERR(info->dir->update_editor->apply_textdelta(info->file_baton,
1084 NULL,
1085 info->editor_pool,
1086 &info->textdelta,
1087 &info->textdelta_baton));
1090 if (info->lock_token)
1091 check_lock(info);
1093 /* set all of the properties we received */
1094 svn_ra_serf__walk_all_props(info->props,
1095 info->base_name, info->base_rev,
1096 set_file_props, info, info->editor_pool);
1097 svn_ra_serf__walk_all_props(info->dir->removed_props,
1098 info->base_name, info->base_rev,
1099 remove_file_props, info, info->editor_pool);
1100 if (info->fetch_props)
1102 svn_ra_serf__walk_all_props(info->props, info->url, info->target_rev,
1103 set_file_props, info, info->editor_pool);
1106 SVN_ERR(info->dir->update_editor->close_file(info->file_baton, NULL,
1107 info->editor_pool));
1109 /* We're done with our pools. */
1110 svn_pool_destroy(info->editor_pool);
1111 svn_pool_destroy(info->pool);
1113 info->dir->ref_count--;
1115 return SVN_NO_ERROR;
1118 static svn_error_t *
1119 fetch_file(report_context_t *ctx, report_info_t *info)
1121 svn_ra_serf__connection_t *conn;
1122 svn_ra_serf__handler_t *handler;
1124 /* What connection should we go on? */
1125 conn = ctx->sess->conns[ctx->sess->cur_conn];
1127 /* go fetch info->name from DAV:checked-in */
1128 info->url =
1129 svn_ra_serf__get_ver_prop(info->props, info->base_name,
1130 info->base_rev, "DAV:", "checked-in");
1132 if (!info->url)
1134 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1135 _("The OPTIONS response did not include the "
1136 "requested checked-in value"));
1139 /* If needed, create the PROPFIND to retrieve the file's properties. */
1140 info->propfind = NULL;
1141 if (info->fetch_props)
1143 svn_ra_serf__deliver_props(&info->propfind, info->props,
1144 ctx->sess, conn,
1145 info->url, info->target_rev, "0", all_props,
1146 FALSE, &ctx->done_propfinds, info->dir->pool);
1147 if (!info->propfind)
1149 abort();
1152 ctx->active_propfinds++;
1155 /* If we've been asked to fetch the file or its an add, do so.
1156 * Otherwise, handle the case where only the properties changed.
1158 if (info->fetch_file && ctx->text_deltas == TRUE)
1160 report_fetch_t *fetch_ctx;
1162 fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
1163 fetch_ctx->pool = info->pool;
1164 fetch_ctx->info = info;
1165 fetch_ctx->done_list = &ctx->done_fetches;
1166 fetch_ctx->sess = ctx->sess;
1167 fetch_ctx->conn = conn;
1169 handler = apr_pcalloc(info->pool, sizeof(*handler));
1171 handler->method = "GET";
1172 handler->path = fetch_ctx->info->url;
1174 handler->conn = conn;
1175 handler->session = ctx->sess;
1177 handler->header_delegate = headers_fetch;
1178 handler->header_delegate_baton = fetch_ctx;
1180 handler->response_handler = handle_fetch;
1181 handler->response_baton = fetch_ctx;
1183 handler->response_error = cancel_fetch;
1184 handler->response_error_baton = fetch_ctx;
1186 svn_ra_serf__request_create(handler);
1188 ctx->active_fetches++;
1190 else if (info->propfind)
1192 svn_ra_serf__list_t *list_item;
1194 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1195 list_item->data = info;
1196 list_item->next = ctx->file_propchanges_only;
1197 ctx->file_propchanges_only = list_item;
1199 else
1201 /* No propfind or GET request. Just handle the prop changes now. */
1202 SVN_ERR(handle_propchange_only(info));
1205 return SVN_NO_ERROR;
1209 /** XML callbacks for our update-report response parsing */
1211 static svn_error_t *
1212 start_report(svn_ra_serf__xml_parser_t *parser,
1213 void *userData,
1214 svn_ra_serf__dav_props_t name,
1215 const char **attrs)
1217 report_context_t *ctx = userData;
1218 report_state_e state;
1220 state = parser->state->current_state;
1222 if (state == NONE && strcmp(name.name, "target-revision") == 0)
1224 const char *rev;
1226 rev = svn_xml_get_attr_value("rev", attrs);
1228 if (!rev)
1230 return svn_error_create
1231 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1232 _("Missing revision attr in target-revision element"));
1235 ctx->update_editor->set_target_revision(ctx->update_baton,
1236 SVN_STR_TO_REV(rev),
1237 ctx->sess->pool);
1239 else if (state == NONE && strcmp(name.name, "open-directory") == 0)
1241 const char *rev;
1242 report_info_t *info;
1244 rev = svn_xml_get_attr_value("rev", attrs);
1246 if (!rev)
1248 return svn_error_create
1249 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1250 _("Missing revision attr in open-directory element"));
1253 info = push_state(parser, ctx, OPEN_DIR);
1255 info->base_rev = apr_atoi64(rev);
1256 info->dir->base_rev = info->base_rev;
1257 info->dir->target_rev = ctx->target_rev;
1258 info->fetch_props = TRUE;
1260 info->dir->base_name = "";
1261 /* Create empty stringbuf with estimated max. path size. */
1262 info->dir->name_buf = svn_stringbuf_create_ensure(256, info->pool);
1263 info->dir->name = info->dir->name_buf->data;
1265 info->base_name = info->dir->base_name;
1266 info->name = info->dir->name;
1267 info->name_buf = info->dir->name_buf;
1269 else if (state == NONE)
1271 /* do nothing as we haven't seen our valid start tag yet. */
1273 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1274 strcmp(name.name, "open-directory") == 0)
1276 const char *rev, *dirname;
1277 report_dir_t *dir;
1278 report_info_t *info;
1280 rev = svn_xml_get_attr_value("rev", attrs);
1282 if (!rev)
1284 return svn_error_create
1285 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1286 _("Missing revision attr in open-directory element"));
1289 dirname = svn_xml_get_attr_value("name", attrs);
1291 if (!dirname)
1293 return svn_error_create
1294 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1295 _("Missing name attr in open-directory element"));
1298 info = push_state(parser, ctx, OPEN_DIR);
1300 dir = info->dir;
1302 info->base_rev = apr_atoi64(rev);
1303 dir->base_rev = info->base_rev;
1304 dir->target_rev = ctx->target_rev;
1306 info->fetch_props = FALSE;
1308 dir->base_name = apr_pstrdup(dir->pool, dirname);
1309 info->base_name = dir->base_name;
1311 /* Expand our name. */
1312 dir->name_buf = svn_stringbuf_dup(dir->parent_dir->name_buf, dir->pool);
1313 svn_path_add_component(dir->name_buf, dir->base_name);
1315 dir->name = dir->name_buf->data;
1316 info->name = dir->name;
1318 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1319 strcmp(name.name, "add-directory") == 0)
1321 const char *dir_name, *cf, *cr;
1322 report_dir_t *dir;
1323 report_info_t *info;
1325 dir_name = svn_xml_get_attr_value("name", attrs);
1326 if (!dir_name)
1328 return svn_error_create
1329 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1330 _("Missing name attr in add-directory element"));
1332 cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1333 cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1335 info = push_state(parser, ctx, ADD_DIR);
1337 dir = info->dir;
1339 dir->base_name = apr_pstrdup(dir->pool, dir_name);
1340 info->base_name = dir->base_name;
1342 /* Expand our name. */
1343 dir->name_buf = svn_stringbuf_dup(dir->parent_dir->name_buf, dir->pool);
1344 svn_path_add_component(dir->name_buf, dir->base_name);
1346 dir->name = dir->name_buf->data;
1347 info->name = dir->name;
1349 info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1350 info->copyfrom_rev = cr ? apr_atoi64(cr) : SVN_INVALID_REVNUM;
1352 /* Mark that we don't have a base. */
1353 info->base_rev = SVN_INVALID_REVNUM;
1354 dir->base_rev = info->base_rev;
1355 dir->target_rev = ctx->target_rev;
1356 dir->fetch_props = TRUE;
1358 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1359 strcmp(name.name, "open-file") == 0)
1361 const char *file_name, *rev;
1362 report_info_t *info;
1364 file_name = svn_xml_get_attr_value("name", attrs);
1366 if (!file_name)
1368 return svn_error_create
1369 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1370 _("Missing name attr in open-file element"));
1373 rev = svn_xml_get_attr_value("rev", attrs);
1375 if (!rev)
1377 return svn_error_create
1378 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1379 _("Missing revision attr in open-file element"));
1382 info = push_state(parser, ctx, OPEN_FILE);
1384 info->base_rev = apr_atoi64(rev);
1385 info->target_rev = ctx->target_rev;
1386 info->fetch_props = FALSE;
1388 info->base_name = apr_pstrdup(info->pool, file_name);
1389 info->name = NULL;
1391 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1392 strcmp(name.name, "add-file") == 0)
1394 const char *file_name, *cf, *cr;
1395 report_info_t *info;
1397 file_name = svn_xml_get_attr_value("name", attrs);
1398 cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1399 cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1401 if (!file_name)
1403 return svn_error_create
1404 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1405 _("Missing name attr in add-file element"));
1408 info = push_state(parser, ctx, ADD_FILE);
1410 info->base_rev = SVN_INVALID_REVNUM;
1411 info->target_rev = ctx->target_rev;
1412 info->fetch_props = TRUE;
1413 info->fetch_file = TRUE;
1415 info->base_name = apr_pstrdup(info->pool, file_name);
1416 info->name = NULL;
1418 info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1419 info->copyfrom_rev = cr ? apr_atoi64(cr) : SVN_INVALID_REVNUM;
1421 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1422 strcmp(name.name, "delete-entry") == 0)
1424 const char *file_name;
1425 svn_stringbuf_t *name_buf;
1426 report_info_t *info;
1427 apr_pool_t *tmppool;
1429 file_name = svn_xml_get_attr_value("name", attrs);
1431 if (!file_name)
1433 return svn_error_create
1434 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1435 _("Missing name attr in delete-entry element"));
1438 info = parser->state->private;
1440 SVN_ERR(open_dir(info->dir));
1442 apr_pool_create(&tmppool, info->dir->dir_baton_pool);
1444 name_buf = svn_stringbuf_dup(info->dir->name_buf, tmppool);
1445 svn_path_add_component(name_buf, file_name);
1447 SVN_ERR(info->dir->update_editor->delete_entry(name_buf->data,
1448 SVN_INVALID_REVNUM,
1449 info->dir->dir_baton,
1450 tmppool));
1452 svn_pool_destroy(tmppool);
1454 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1455 strcmp(name.name, "absent-directory") == 0)
1457 const char *file_name;
1458 report_info_t *info;
1460 file_name = svn_xml_get_attr_value("name", attrs);
1462 if (!file_name)
1464 return svn_error_create
1465 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1466 _("Missing name attr in absent-directory element"));
1469 info = parser->state->private;
1471 SVN_ERR(open_dir(info->dir));
1473 ctx->update_editor->absent_directory(file_name,
1474 info->dir->dir_baton,
1475 info->dir->pool);
1477 else if ((state == OPEN_DIR || state == ADD_DIR) &&
1478 strcmp(name.name, "absent-file") == 0)
1480 const char *file_name;
1481 report_info_t *info;
1483 file_name = svn_xml_get_attr_value("name", attrs);
1485 if (!file_name)
1487 return svn_error_create
1488 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1489 _("Missing name attr in absent-file element"));
1492 info = parser->state->private;
1494 SVN_ERR(open_dir(info->dir));
1496 ctx->update_editor->absent_file(file_name,
1497 info->dir->dir_baton,
1498 info->dir->pool);
1500 else if (state == OPEN_DIR || state == ADD_DIR)
1502 report_info_t *info;
1504 if (strcmp(name.name, "checked-in") == 0)
1506 info = push_state(parser, ctx, IGNORE_PROP_NAME);
1507 info->prop_ns = name.namespace;
1508 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
1509 info->prop_encoding = NULL;
1510 info->prop_val = NULL;
1511 info->prop_val_len = 0;
1513 else if (strcmp(name.name, "set-prop") == 0 ||
1514 strcmp(name.name, "remove-prop") == 0)
1516 const char *full_prop_name;
1517 const char *colon;
1519 info = push_state(parser, ctx, PROP);
1521 full_prop_name = svn_xml_get_attr_value("name", attrs);
1522 if (!full_prop_name)
1524 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1525 _("Missing name attr in %s element"),
1526 name.name);
1529 colon = strchr(full_prop_name, ':');
1531 if (colon)
1532 colon++;
1533 else
1534 colon = full_prop_name;
1536 info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
1537 colon - full_prop_name);
1538 info->prop_name = apr_pstrdup(parser->state->pool, colon);
1539 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
1540 info->prop_val = NULL;
1541 info->prop_val_len = 0;
1543 else if (strcmp(name.name, "prop") == 0)
1545 /* need to fetch it. */
1546 push_state(parser, ctx, NEED_PROP_NAME);
1548 else if (strcmp(name.name, "fetch-props") == 0)
1550 info = parser->state->private;
1552 info->dir->fetch_props = TRUE;
1554 else
1556 abort();
1560 else if (state == OPEN_FILE || state == ADD_FILE)
1562 report_info_t *info;
1564 if (strcmp(name.name, "checked-in") == 0)
1566 info = push_state(parser, ctx, IGNORE_PROP_NAME);
1567 info->prop_ns = name.namespace;
1568 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
1569 info->prop_encoding = NULL;
1570 info->prop_val = NULL;
1571 info->prop_val_len = 0;
1573 else if (strcmp(name.name, "prop") == 0)
1575 /* need to fetch it. */
1576 push_state(parser, ctx, NEED_PROP_NAME);
1578 else if (strcmp(name.name, "fetch-props") == 0)
1580 info = parser->state->private;
1582 info->fetch_props = TRUE;
1584 else if (strcmp(name.name, "fetch-file") == 0)
1586 info = parser->state->private;
1588 info->fetch_file = TRUE;
1590 else if (strcmp(name.name, "set-prop") == 0 ||
1591 strcmp(name.name, "remove-prop") == 0)
1593 const char *full_prop_name;
1594 const char *colon;
1596 info = push_state(parser, ctx, PROP);
1598 full_prop_name = svn_xml_get_attr_value("name", attrs);
1599 if (!full_prop_name)
1601 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1602 _("Missing name attr in %s element"),
1603 name.name);
1605 colon = strchr(full_prop_name, ':');
1607 if (colon)
1608 colon++;
1609 else
1610 colon = full_prop_name;
1612 info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
1613 colon - full_prop_name);
1614 info->prop_name = apr_pstrdup(parser->state->pool, colon);
1615 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
1616 info->prop_val = NULL;
1617 info->prop_val_len = 0;
1619 else
1621 abort();
1624 else if (state == IGNORE_PROP_NAME)
1626 push_state(parser, ctx, PROP);
1628 else if (state == NEED_PROP_NAME)
1630 report_info_t *info;
1632 info = push_state(parser, ctx, PROP);
1634 info->prop_ns = name.namespace;
1635 info->prop_name = apr_pstrdup(parser->state->pool, name.name);
1636 info->prop_val = NULL;
1637 info->prop_val_len = 0;
1640 return SVN_NO_ERROR;
1643 static svn_error_t *
1644 end_report(svn_ra_serf__xml_parser_t *parser,
1645 void *userData,
1646 svn_ra_serf__dav_props_t name)
1648 report_context_t *ctx = userData;
1649 report_state_e state;
1651 state = parser->state->current_state;
1653 if (state == NONE)
1655 /* nothing to close yet. */
1656 return SVN_NO_ERROR;
1659 if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
1660 (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0))))
1662 const char *checked_in_url;
1663 report_info_t *info = parser->state->private;
1665 /* We've now closed this directory; note it. */
1666 info->dir->tag_closed = TRUE;
1668 /* go fetch info->file_name from DAV:checked-in */
1669 checked_in_url =
1670 svn_ra_serf__get_ver_prop(info->dir->props, info->base_name,
1671 info->base_rev, "DAV:", "checked-in");
1673 /* If we were expecting to have the properties and we aren't able to
1674 * get it, bail.
1676 if (!checked_in_url &&
1677 (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
1679 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1680 _("The OPTIONS response did not include the "
1681 "requested checked-in value"));
1684 info->dir->url = checked_in_url;
1686 /* At this point, we should have the checked-in href.
1687 * If needed, create the PROPFIND to retrieve the dir's properties.
1689 if (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props)
1691 /* Unconditionally set fetch_props now. */
1692 info->dir->fetch_props = TRUE;
1694 svn_ra_serf__deliver_props(&info->dir->propfind, info->dir->props,
1695 ctx->sess,
1696 ctx->sess->conns[ctx->sess->cur_conn],
1697 info->dir->url, info->dir->target_rev,
1698 "0", all_props, FALSE,
1699 &ctx->done_propfinds, info->dir->pool);
1701 if (!info->dir->propfind)
1703 abort();
1706 ctx->active_propfinds++;
1708 else
1710 info->dir->propfind = NULL;
1713 svn_ra_serf__xml_pop_state(parser);
1715 else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
1717 report_info_t *info = parser->state->private;
1719 /* Expand our full name now if we haven't done so yet. */
1720 if (!info->name)
1722 info->name_buf = svn_stringbuf_dup(info->dir->name_buf, info->pool);
1723 svn_path_add_component(info->name_buf, info->base_name);
1724 info->name = info->name_buf->data;
1727 info->lock_token = apr_hash_get(ctx->lock_path_tokens, info->name,
1728 APR_HASH_KEY_STRING);
1730 if (info->lock_token && info->fetch_props == FALSE)
1731 info->fetch_props = TRUE;
1733 /* If we have a WC, we can dive all the way into the WC to get the
1734 * previous URL so we can do an differential GET with the base URL.
1736 * If we don't have a WC (as is the case for URL<->URL diff), we can
1737 * manually reconstruct the base URL. This avoids us having to grab
1738 * two full-text for URL<->URL diffs. Instead, we can just grab one
1739 * full-text and a diff from the server against that other file.
1741 if (ctx->sess->wc_callbacks->get_wc_prop)
1743 ctx->sess->wc_callbacks->get_wc_prop(ctx->sess->wc_callback_baton,
1744 info->name,
1745 SVN_RA_SERF__WC_CHECKED_IN_URL,
1746 &info->delta_base,
1747 info->pool);
1749 else
1751 const char *c;
1752 apr_size_t comp_count;
1753 svn_stringbuf_t *path;
1755 c = svn_ra_serf__get_ver_prop(info->props, info->base_name,
1756 info->base_rev, "DAV:", "checked-in");
1758 path = svn_stringbuf_create(c, info->pool);
1760 comp_count = svn_path_component_count(info->name_buf->data);
1762 svn_path_remove_components(path, comp_count);
1764 /* Find out the difference of the destination compared to the repos
1765 * root url. Cut of this difference from path, which will give us our
1766 * version resource root path.
1768 * Example:
1769 * path:
1770 * /repositories/log_tests-17/!svn/ver/4/branches/a
1771 * repos_root:
1772 * http://localhost/repositories/log_tests-17
1773 * destination:
1774 * http://localhost/repositories/log_tests-17/branches/a
1776 * So, find 'branches/a' as the difference. Cut it of path, gives us:
1777 * /repositories/log_tests-17/!svn/ver/4
1779 if (ctx->destination &&
1780 strcmp(ctx->destination, ctx->sess->repos_root_str) != 0)
1782 apr_size_t root_count, src_count;
1784 src_count = svn_path_component_count(ctx->destination);
1785 root_count = svn_path_component_count(ctx->sess->repos_root_str);
1787 svn_path_remove_components(path, src_count - root_count);
1790 /* At this point, we should just have the version number
1791 * remaining. We know our target revision, so we'll replace it
1792 * and recreate what we just chopped off.
1794 svn_path_remove_component(path);
1796 svn_path_add_component(path, apr_ltoa(info->pool, info->base_rev));
1798 /* Similar as above, we now have to add the relative path between
1799 * source and root path.
1801 * Example:
1802 * path:
1803 * /repositories/log_tests-17/!svn/ver/2
1804 * repos_root path:
1805 * /repositories/log_tests-17
1806 * source:
1807 * /repositories/log_tests-17/trunk
1809 * So, find 'trunk' as the difference. Addding it to path, gives us:
1810 * /repositories/log_tests-17/!svn/ver/2/trunk
1812 if (strcmp(ctx->source, ctx->sess->repos_root.path) != 0)
1814 apr_size_t root_len;
1816 root_len = strlen(ctx->sess->repos_root.path) + 1;
1818 svn_path_add_component(path, &ctx->source[root_len]);
1821 /* Re-add the filename. */
1822 svn_path_add_component(path, info->name);
1824 info->delta_base = svn_string_create_from_buf(path, info->pool);
1827 SVN_ERR(fetch_file(ctx, info));
1828 svn_ra_serf__xml_pop_state(parser);
1830 else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
1832 /* We should have everything we need to fetch the file. */
1833 SVN_ERR(fetch_file(ctx, parser->state->private));
1834 svn_ra_serf__xml_pop_state(parser);
1836 else if (state == PROP)
1838 /* We need to move the prop_ns, prop_name, and prop_val into the
1839 * same lifetime as the dir->pool.
1841 svn_ra_serf__ns_t *ns, *ns_name_match;
1842 int found = 0;
1843 report_info_t *info;
1844 report_dir_t *dir;
1845 apr_hash_t *props;
1846 const char *set_val;
1847 svn_string_t *set_val_str;
1848 apr_pool_t *pool;
1850 info = parser->state->private;
1851 dir = info->dir;
1853 /* We're going to be slightly tricky. We don't care what the ->url
1854 * field is here at this point. So, we're going to stick a single
1855 * copy of the property name inside of the ->url field.
1857 ns_name_match = NULL;
1858 for (ns = dir->ns_list; ns; ns = ns->next)
1860 if (strcmp(ns->namespace, info->prop_ns) == 0)
1862 ns_name_match = ns;
1863 if (strcmp(ns->url, info->prop_name) == 0)
1865 found = 1;
1866 break;
1871 if (!found)
1873 ns = apr_palloc(dir->pool, sizeof(*ns));
1874 if (!ns_name_match)
1876 ns->namespace = apr_pstrdup(dir->pool, info->prop_ns);
1878 else
1880 ns->namespace = ns_name_match->namespace;
1882 ns->url = apr_pstrdup(dir->pool, info->prop_name);
1884 ns->next = dir->ns_list;
1885 dir->ns_list = ns;
1888 if (strcmp(name.name, "remove-prop") != 0)
1890 props = info->props;
1891 pool = info->pool;
1893 else
1895 props = dir->removed_props;
1896 pool = dir->pool;
1897 info->prop_val = "";
1898 info->prop_val_len = 1;
1901 if (info->prop_encoding)
1903 if (strcmp(info->prop_encoding, "base64") == 0)
1905 svn_string_t encoded;
1906 const svn_string_t *decoded;
1908 encoded.data = info->prop_val;
1909 encoded.len = info->prop_val_len;
1911 decoded = svn_base64_decode_string(&encoded, parser->state->pool);
1913 info->prop_val = decoded->data;
1914 info->prop_val_len = decoded->len;
1916 else
1918 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
1919 NULL,
1920 _("Got unrecognized encoding '%s'"),
1921 info->prop_encoding);
1926 set_val = apr_pmemdup(pool, info->prop_val, info->prop_val_len);
1927 set_val_str = svn_string_ncreate(set_val, info->prop_val_len, pool);
1929 svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
1930 ns->namespace, ns->url, set_val_str, pool);
1931 svn_ra_serf__xml_pop_state(parser);
1933 else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
1935 svn_ra_serf__xml_pop_state(parser);
1938 return SVN_NO_ERROR;
1941 static svn_error_t *
1942 cdata_report(svn_ra_serf__xml_parser_t *parser,
1943 void *userData,
1944 const char *data,
1945 apr_size_t len)
1947 report_context_t *ctx = userData;
1949 UNUSED_CTX(ctx);
1951 if (parser->state->current_state == PROP)
1953 report_info_t *info = parser->state->private;
1955 svn_ra_serf__expand_string(&info->prop_val, &info->prop_val_len,
1956 data, len, parser->state->pool);
1959 return SVN_NO_ERROR;
1963 /** Editor callbacks given to callers to create request body */
1965 static svn_error_t *
1966 set_path(void *report_baton,
1967 const char *path,
1968 svn_revnum_t revision,
1969 svn_depth_t depth,
1970 svn_boolean_t start_empty,
1971 const char *lock_token,
1972 apr_pool_t *pool)
1974 report_context_t *report = report_baton;
1975 serf_bucket_t *tmp;
1976 svn_stringbuf_t *path_buf;
1978 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<S:entry rev=\"",
1979 sizeof("<S:entry rev=\"")-1,
1980 report->sess->bkt_alloc);
1981 serf_bucket_aggregate_append(report->buckets, tmp);
1983 tmp = SERF_BUCKET_SIMPLE_STRING(apr_ltoa(report->pool, revision),
1984 report->sess->bkt_alloc);
1985 serf_bucket_aggregate_append(report->buckets, tmp);
1987 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", sizeof("\"")-1,
1988 report->sess->bkt_alloc);
1989 serf_bucket_aggregate_append(report->buckets, tmp);
1991 if (lock_token)
1993 apr_hash_set(report->lock_path_tokens,
1994 apr_pstrdup(report->pool, path),
1995 APR_HASH_KEY_STRING,
1996 apr_pstrdup(report->pool, lock_token));
1998 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" lock-token=\"",
1999 sizeof(" lock-token=\"")-1,
2000 report->sess->bkt_alloc);
2001 serf_bucket_aggregate_append(report->buckets, tmp);
2003 tmp = SERF_BUCKET_SIMPLE_STRING(lock_token,
2004 report->sess->bkt_alloc);
2005 serf_bucket_aggregate_append(report->buckets, tmp);
2007 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", sizeof("\"")-1,
2008 report->sess->bkt_alloc);
2009 serf_bucket_aggregate_append(report->buckets, tmp);
2012 /* Depth. */
2013 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" depth=\"",
2014 sizeof(" depth=\"")-1,
2015 report->sess->bkt_alloc);
2016 serf_bucket_aggregate_append(report->buckets, tmp);
2018 tmp = SERF_BUCKET_SIMPLE_STRING(svn_depth_to_word(depth),
2019 report->sess->bkt_alloc);
2020 serf_bucket_aggregate_append(report->buckets, tmp);
2022 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", sizeof("\"")-1,
2023 report->sess->bkt_alloc);
2024 serf_bucket_aggregate_append(report->buckets, tmp);
2026 if (start_empty)
2028 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" start-empty=\"true\"",
2029 sizeof(" start-empty=\"true\"")-1,
2030 report->sess->bkt_alloc);
2031 serf_bucket_aggregate_append(report->buckets, tmp);
2034 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", sizeof(">")-1,
2035 report->sess->bkt_alloc);
2036 serf_bucket_aggregate_append(report->buckets, tmp);
2038 path_buf = NULL;
2039 svn_xml_escape_cdata_cstring(&path_buf, path, report->pool);
2041 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(path_buf->data, path_buf->len,
2042 report->sess->bkt_alloc);
2043 serf_bucket_aggregate_append(report->buckets, tmp);
2045 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</S:entry>",
2046 sizeof("</S:entry>")-1,
2047 report->sess->bkt_alloc);
2049 serf_bucket_aggregate_append(report->buckets, tmp);
2050 return APR_SUCCESS;
2053 static svn_error_t *
2054 delete_path(void *report_baton,
2055 const char *path,
2056 apr_pool_t *pool)
2058 report_context_t *report = report_baton;
2059 serf_bucket_t *tmp;
2061 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<S:missing>",
2062 sizeof("<S:missing>")-1,
2063 report->sess->bkt_alloc);
2064 serf_bucket_aggregate_append(report->buckets, tmp);
2066 tmp = SERF_BUCKET_SIMPLE_STRING(apr_pstrdup(report->pool, path),
2067 report->sess->bkt_alloc);
2068 serf_bucket_aggregate_append(report->buckets, tmp);
2070 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</S:missing>",
2071 sizeof("</S:missing>")-1,
2072 report->sess->bkt_alloc);
2074 serf_bucket_aggregate_append(report->buckets, tmp);
2075 return APR_SUCCESS;
2078 static svn_error_t *
2079 link_path(void *report_baton,
2080 const char *path,
2081 const char *url,
2082 svn_revnum_t revision,
2083 svn_depth_t depth,
2084 svn_boolean_t start_empty,
2085 const char *lock_token,
2086 apr_pool_t *pool)
2088 report_context_t *report = report_baton;
2089 serf_bucket_t *tmp;
2090 const char *link, *vcc_url;
2091 apr_uri_t uri;
2092 apr_status_t status;
2094 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<S:entry rev=\"",
2095 sizeof("<S:entry rev=\"")-1,
2096 report->sess->bkt_alloc);
2097 serf_bucket_aggregate_append(report->buckets, tmp);
2099 tmp = SERF_BUCKET_SIMPLE_STRING(apr_ltoa(report->pool, revision),
2100 report->sess->bkt_alloc);
2101 serf_bucket_aggregate_append(report->buckets, tmp);
2103 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", sizeof("\"")-1,
2104 report->sess->bkt_alloc);
2105 serf_bucket_aggregate_append(report->buckets, tmp);
2107 if (lock_token)
2109 apr_hash_set(report->lock_path_tokens,
2110 apr_pstrdup(report->pool, path),
2111 APR_HASH_KEY_STRING,
2112 apr_pstrdup(report->pool, lock_token));
2114 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" lock-token=\"",
2115 sizeof(" lock-token=\"")-1,
2116 report->sess->bkt_alloc);
2117 serf_bucket_aggregate_append(report->buckets, tmp);
2119 tmp = SERF_BUCKET_SIMPLE_STRING(lock_token,
2120 report->sess->bkt_alloc);
2121 serf_bucket_aggregate_append(report->buckets, tmp);
2123 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", sizeof("\"")-1,
2124 report->sess->bkt_alloc);
2125 serf_bucket_aggregate_append(report->buckets, tmp);
2128 /* Depth. */
2129 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" depth=\"",
2130 sizeof(" depth=\"")-1,
2131 report->sess->bkt_alloc);
2132 serf_bucket_aggregate_append(report->buckets, tmp);
2134 tmp = SERF_BUCKET_SIMPLE_STRING(svn_depth_to_word(depth),
2135 report->sess->bkt_alloc);
2136 serf_bucket_aggregate_append(report->buckets, tmp);
2138 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", sizeof("\"")-1,
2139 report->sess->bkt_alloc);
2140 serf_bucket_aggregate_append(report->buckets, tmp);
2142 if (start_empty)
2144 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" start-empty=\"true\"",
2145 sizeof(" start-empty=\"true\"")-1,
2146 report->sess->bkt_alloc);
2147 serf_bucket_aggregate_append(report->buckets, tmp);
2150 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" linkpath=\"/",
2151 sizeof(" linkpath=\"/")-1,
2152 report->sess->bkt_alloc);
2153 serf_bucket_aggregate_append(report->buckets, tmp);
2155 /* We need to pass in the baseline relative path.
2157 * TODO Confirm that it's on the same server?
2159 status = apr_uri_parse(pool, url, &uri);
2160 if (status)
2162 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2163 _("Unable to parse URL '%s'"), url);
2166 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &link, report->sess,
2167 report->sess->conns[0], uri.path, pool));
2169 tmp = SERF_BUCKET_SIMPLE_STRING(apr_pstrdup(report->pool, link),
2170 report->sess->bkt_alloc);
2171 serf_bucket_aggregate_append(report->buckets, tmp);
2173 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", sizeof("\"")-1,
2174 report->sess->bkt_alloc);
2175 serf_bucket_aggregate_append(report->buckets, tmp);
2177 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", sizeof(">")-1,
2178 report->sess->bkt_alloc);
2179 serf_bucket_aggregate_append(report->buckets, tmp);
2181 tmp = SERF_BUCKET_SIMPLE_STRING(apr_pstrdup(report->pool, path),
2182 report->sess->bkt_alloc);
2183 serf_bucket_aggregate_append(report->buckets, tmp);
2185 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</S:entry>",
2186 sizeof("</S:entry>")-1,
2187 report->sess->bkt_alloc);
2189 serf_bucket_aggregate_append(report->buckets, tmp);
2190 return APR_SUCCESS;
2193 static svn_error_t *
2194 finish_report(void *report_baton,
2195 apr_pool_t *pool)
2197 report_context_t *report = report_baton;
2198 svn_ra_serf__session_t *sess = report->sess;
2199 svn_ra_serf__handler_t *handler;
2200 svn_ra_serf__xml_parser_t *parser_ctx;
2201 svn_ra_serf__list_t *done_list;
2202 serf_bucket_t *tmp;
2203 const char *vcc_url;
2204 apr_hash_t *props;
2205 apr_status_t status;
2206 svn_boolean_t closed_root;
2207 int i;
2209 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</S:update-report>",
2210 sizeof("</S:update-report>")-1,
2211 report->sess->bkt_alloc);
2212 serf_bucket_aggregate_append(report->buckets, tmp);
2214 props = apr_hash_make(pool);
2216 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL, sess, sess->conns[0],
2217 sess->repos_url.path, pool));
2219 if (!vcc_url)
2221 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
2222 _("The OPTIONS response did not include the "
2223 "requested version-controlled-configuration "
2224 "value"));
2227 /* create and deliver request */
2228 report->path = vcc_url;
2230 handler = apr_pcalloc(pool, sizeof(*handler));
2232 handler->method = "REPORT";
2233 handler->path = report->path;
2234 handler->body_buckets = report->buckets;
2235 handler->body_type = "text/xml";
2236 handler->conn = sess->conns[0];
2237 handler->session = sess;
2239 parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
2241 parser_ctx->pool = pool;
2242 parser_ctx->user_data = report;
2243 parser_ctx->start = start_report;
2244 parser_ctx->end = end_report;
2245 parser_ctx->cdata = cdata_report;
2246 parser_ctx->done = &report->done;
2248 handler->response_handler = svn_ra_serf__handle_xml_parser;
2249 handler->response_baton = parser_ctx;
2251 svn_ra_serf__request_create(handler);
2253 for (i = sess->num_conns; i < 4; i++)
2255 sess->conns[i] = apr_palloc(sess->pool, sizeof(*sess->conns[i]));
2256 sess->conns[i]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
2257 NULL, NULL);
2258 sess->conns[i]->address = sess->conns[0]->address;
2259 sess->conns[i]->hostinfo = sess->conns[0]->hostinfo;
2260 sess->conns[i]->using_ssl = sess->conns[0]->using_ssl;
2261 sess->conns[i]->using_compression = sess->conns[0]->using_compression;
2262 sess->conns[i]->proxy_auth_header = sess->conns[0]->proxy_auth_header;
2263 sess->conns[i]->proxy_auth_value = sess->conns[0]->proxy_auth_value;
2264 sess->conns[i]->useragent = sess->conns[0]->useragent;
2265 sess->conns[i]->last_status_code = -1;
2266 sess->conns[i]->ssl_context = NULL;
2267 sess->conns[i]->session = sess;
2268 sess->conns[i]->conn = serf_connection_create(sess->context,
2269 sess->conns[i]->address,
2270 svn_ra_serf__conn_setup,
2271 sess->conns[i],
2272 svn_ra_serf__conn_closed,
2273 sess->conns[i],
2274 sess->pool);
2275 sess->num_conns++;
2277 /* Authentication protocol specific initalization. */
2278 if (sess->auth_protocol)
2279 sess->auth_protocol->init_conn_func(sess, sess->conns[i], pool);
2282 sess->cur_conn = 1;
2283 closed_root = FALSE;
2285 while (!report->done || report->active_fetches || report->active_propfinds)
2287 status = serf_context_run(sess->context, SERF_DURATION_FOREVER, pool);
2288 if (APR_STATUS_IS_TIMEUP(status))
2290 continue;
2292 if (status)
2294 SVN_ERR(sess->pending_error);
2296 return svn_error_wrap_apr(status, _("Error retrieving REPORT (%d)"),
2297 status);
2300 /* Switch our connection. */
2301 if (!report->done)
2302 if (++sess->cur_conn == sess->num_conns)
2303 sess->cur_conn = 1;
2305 /* prune our propfind list if they are done. */
2306 done_list = report->done_propfinds;
2307 while (done_list)
2309 report->active_propfinds--;
2311 /* If we have some files that we won't be fetching the content
2312 * for, ensure that we update the file with any altered props.
2314 if (report->file_propchanges_only)
2316 svn_ra_serf__list_t *cur, *prev;
2318 prev = NULL;
2319 cur = report->file_propchanges_only;
2321 while (cur)
2323 report_info_t *item = cur->data;
2325 if (item->propfind == done_list->data)
2327 break;
2330 prev = cur;
2331 cur = cur->next;
2334 /* If we found a match, set the new props and remove this
2335 * propchange from our list.
2337 if (cur)
2339 SVN_ERR(handle_propchange_only(cur->data));
2341 if (!prev)
2343 report->file_propchanges_only = cur->next;
2345 else
2347 prev->next = cur->next;
2352 done_list = done_list->next;
2354 report->done_propfinds = NULL;
2356 /* prune our fetches list if they are done. */
2357 done_list = report->done_fetches;
2358 while (done_list)
2360 report_fetch_t *done_fetch = done_list->data;
2361 report_dir_t *cur_dir;
2363 if (done_fetch->err)
2365 svn_error_t *err = done_fetch->err;
2366 /* Error found. There might be more, clear those first. */
2367 done_list = done_list->next;
2368 while (done_list)
2370 done_fetch = done_list->data;
2371 if (done_fetch->err)
2372 svn_error_clear(done_fetch->err);
2373 done_list = done_list->next;
2375 return err;
2378 /* decrease our parent's directory refcount. */
2379 cur_dir = done_fetch->info->dir;
2380 cur_dir->ref_count--;
2382 /* Decrement our active fetch count. */
2383 report->active_fetches--;
2385 done_list = done_list->next;
2387 /* If we have a valid directory and
2388 * we have no open items in this dir and
2389 * we've closed the directory tag (no more children can be added)
2390 * and either:
2391 * we know we won't be fetching props or
2392 * we've already completed the propfind
2393 * then, we know it's time for us to close this directory.
2395 while (cur_dir && !cur_dir->ref_count && cur_dir->tag_closed &&
2396 (!cur_dir->fetch_props ||
2397 svn_ra_serf__propfind_is_done(cur_dir->propfind)))
2399 report_dir_t *parent = cur_dir->parent_dir;
2401 SVN_ERR(close_dir(cur_dir));
2402 if (parent)
2404 parent->ref_count--;
2406 else
2408 closed_root = TRUE;
2410 cur_dir = parent;
2413 report->done_fetches = NULL;
2415 /* Debugging purposes only! */
2416 serf_debug__closed_conn(sess->bkt_alloc);
2417 for (i = 0; i < sess->num_conns; i++)
2419 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
2423 /* Ensure that we opened and closed our root dir and that we closed
2424 * all of our children. */
2425 if (closed_root == FALSE)
2427 SVN_ERR(close_all_dirs(report->root_dir));
2430 /* FIXME subpool */
2431 SVN_ERR(report->update_editor->close_edit(report->update_baton, sess->pool));
2433 return SVN_NO_ERROR;
2436 static svn_error_t *
2437 abort_report(void *report_baton,
2438 apr_pool_t *pool)
2440 #if 0
2441 report_context_t *report = report_baton;
2442 #endif
2443 abort();
2446 static const svn_ra_reporter3_t ra_serf_reporter = {
2447 set_path,
2448 delete_path,
2449 link_path,
2450 finish_report,
2451 abort_report
2455 /** RA function implementations and body */
2457 static svn_error_t *
2458 make_update_reporter(svn_ra_session_t *ra_session,
2459 const svn_ra_reporter3_t **reporter,
2460 void **report_baton,
2461 svn_revnum_t revision,
2462 const char *src_path,
2463 const char *dest_path,
2464 const char *update_target,
2465 svn_depth_t depth,
2466 svn_boolean_t ignore_ancestry,
2467 svn_boolean_t text_deltas,
2468 svn_boolean_t send_copyfrom_args,
2469 const svn_delta_editor_t *update_editor,
2470 void *update_baton,
2471 apr_pool_t *pool)
2473 report_context_t *report;
2474 serf_bucket_t *tmp;
2475 svn_stringbuf_t *path_buf;
2476 const svn_delta_editor_t *filter_editor;
2477 void *filter_baton;
2478 svn_boolean_t has_target = *update_target ? TRUE : FALSE;
2479 svn_boolean_t server_supports_depth;
2480 svn_ra_serf__session_t *sess = ra_session->priv;
2482 SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
2483 SVN_RA_CAPABILITY_DEPTH, pool));
2484 /* We can skip the depth filtering when the user requested
2485 depth_files or depth_infinity because the server will
2486 transmit the right stuff anyway. */
2487 if ((depth != svn_depth_files)
2488 && (depth != svn_depth_infinity)
2489 && ! server_supports_depth)
2491 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
2492 &filter_baton,
2493 update_editor,
2494 update_baton,
2495 depth, has_target,
2496 sess->pool));
2497 update_editor = filter_editor;
2498 update_baton = filter_baton;
2501 report = apr_pcalloc(pool, sizeof(*report));
2502 report->pool = pool;
2503 report->sess = sess;
2504 report->conn = report->sess->conns[0];
2505 report->target_rev = revision;
2506 report->ignore_ancestry = ignore_ancestry;
2507 report->send_copyfrom_args = send_copyfrom_args;
2508 report->text_deltas = text_deltas;
2509 report->lock_path_tokens = apr_hash_make(pool);
2511 if (src_path)
2513 path_buf = NULL;
2514 svn_xml_escape_cdata_cstring(&path_buf, src_path, report->pool);
2515 report->source = path_buf->data;
2518 if (dest_path)
2520 path_buf = NULL;
2521 svn_xml_escape_cdata_cstring(&path_buf, dest_path, report->pool);
2522 report->destination = path_buf->data;
2525 if (update_target)
2527 path_buf = NULL;
2528 svn_xml_escape_cdata_cstring(&path_buf, update_target, report->pool);
2529 report->update_target = path_buf->data;
2532 report->update_editor = update_editor;
2533 report->update_baton = update_baton;
2534 report->done = FALSE;
2536 *reporter = &ra_serf_reporter;
2537 *report_baton = report;
2539 report->buckets = serf_bucket_aggregate_create(report->sess->bkt_alloc);
2541 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<S:update-report xmlns:S=\"",
2542 sizeof("<S:update-report xmlns:S=\"")-1,
2543 report->sess->bkt_alloc);
2544 serf_bucket_aggregate_append(report->buckets, tmp);
2546 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(SVN_XML_NAMESPACE,
2547 sizeof(SVN_XML_NAMESPACE)-1,
2548 report->sess->bkt_alloc);
2549 serf_bucket_aggregate_append(report->buckets, tmp);
2551 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\">",
2552 sizeof("\">")-1,
2553 report->sess->bkt_alloc);
2554 serf_bucket_aggregate_append(report->buckets, tmp);
2556 svn_ra_serf__add_tag_buckets(report->buckets,
2557 "S:src-path", report->source,
2558 report->sess->bkt_alloc);
2560 if (SVN_IS_VALID_REVNUM(report->target_rev))
2562 svn_ra_serf__add_tag_buckets(report->buckets,
2563 "S:target-revision",
2564 apr_ltoa(pool, report->target_rev),
2565 report->sess->bkt_alloc);
2568 if (report->destination && *report->destination)
2570 svn_ra_serf__add_tag_buckets(report->buckets,
2571 "S:dst-path",
2572 report->destination,
2573 report->sess->bkt_alloc);
2576 if (report->update_target && *report->update_target)
2578 svn_ra_serf__add_tag_buckets(report->buckets,
2579 "S:update-target", report->update_target,
2580 report->sess->bkt_alloc);
2583 if (report->ignore_ancestry)
2585 svn_ra_serf__add_tag_buckets(report->buckets,
2586 "S:ignore-ancestry", "yes",
2587 report->sess->bkt_alloc);
2590 if (report->send_copyfrom_args)
2592 svn_ra_serf__add_tag_buckets(report->buckets,
2593 "S:send-copyfrom-args", "yes",
2594 report->sess->bkt_alloc);
2597 /* Old servers know "recursive" but not "depth"; help them DTRT. */
2598 if (depth == svn_depth_files || depth == svn_depth_empty)
2600 svn_ra_serf__add_tag_buckets(report->buckets,
2601 "S:recursive", "no",
2602 report->sess->bkt_alloc);
2605 svn_ra_serf__add_tag_buckets(report->buckets,
2606 "S:depth", svn_depth_to_word(depth),
2607 report->sess->bkt_alloc);
2609 return SVN_NO_ERROR;
2612 svn_error_t *
2613 svn_ra_serf__do_update(svn_ra_session_t *ra_session,
2614 const svn_ra_reporter3_t **reporter,
2615 void **report_baton,
2616 svn_revnum_t revision_to_update_to,
2617 const char *update_target,
2618 svn_depth_t depth,
2619 svn_boolean_t send_copyfrom_args,
2620 const svn_delta_editor_t *update_editor,
2621 void *update_baton,
2622 apr_pool_t *pool)
2624 svn_ra_serf__session_t *session = ra_session->priv;
2626 return make_update_reporter(ra_session, reporter, report_baton,
2627 revision_to_update_to,
2628 session->repos_url.path, NULL, update_target,
2629 depth, FALSE, TRUE, send_copyfrom_args,
2630 update_editor, update_baton, pool);
2633 svn_error_t *
2634 svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
2635 const svn_ra_reporter3_t **reporter,
2636 void **report_baton,
2637 svn_revnum_t revision,
2638 const char *diff_target,
2639 svn_depth_t depth,
2640 svn_boolean_t ignore_ancestry,
2641 svn_boolean_t text_deltas,
2642 const char *versus_url,
2643 const svn_delta_editor_t *diff_editor,
2644 void *diff_baton,
2645 apr_pool_t *pool)
2647 svn_ra_serf__session_t *session = ra_session->priv;
2649 return make_update_reporter(ra_session, reporter, report_baton,
2650 revision,
2651 session->repos_url.path, versus_url, diff_target,
2652 depth, ignore_ancestry, text_deltas, FALSE,
2653 diff_editor, diff_baton, pool);
2656 svn_error_t *
2657 svn_ra_serf__do_status(svn_ra_session_t *ra_session,
2658 const svn_ra_reporter3_t **reporter,
2659 void **report_baton,
2660 const char *status_target,
2661 svn_revnum_t revision,
2662 svn_depth_t depth,
2663 const svn_delta_editor_t *status_editor,
2664 void *status_baton,
2665 apr_pool_t *pool)
2667 svn_ra_serf__session_t *session = ra_session->priv;
2669 return make_update_reporter(ra_session, reporter, report_baton,
2670 revision,
2671 session->repos_url.path, NULL, status_target,
2672 depth, FALSE, FALSE, FALSE,
2673 status_editor, status_baton, pool);
2676 svn_error_t *
2677 svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
2678 const svn_ra_reporter3_t **reporter,
2679 void **report_baton,
2680 svn_revnum_t revision_to_switch_to,
2681 const char *switch_target,
2682 svn_depth_t depth,
2683 const char *switch_url,
2684 const svn_delta_editor_t *switch_editor,
2685 void *switch_baton,
2686 apr_pool_t *pool)
2688 svn_ra_serf__session_t *session = ra_session->priv;
2690 return make_update_reporter(ra_session, reporter, report_baton,
2691 revision_to_switch_to,
2692 session->repos_url.path,
2693 switch_url, switch_target,
2694 depth, TRUE, TRUE, FALSE /* TODO(sussman) */,
2695 switch_editor, switch_baton, pool);
2698 svn_error_t *
2699 svn_ra_serf__get_file(svn_ra_session_t *ra_session,
2700 const char *path,
2701 svn_revnum_t revision,
2702 svn_stream_t *stream,
2703 svn_revnum_t *fetched_rev,
2704 apr_hash_t **props,
2705 apr_pool_t *pool)
2707 svn_ra_serf__session_t *session = ra_session->priv;
2708 svn_ra_serf__connection_t *conn;
2709 svn_ra_serf__handler_t *handler;
2710 const char *fetch_url;
2711 apr_hash_t *fetch_props;
2713 /* What connection should we go on? */
2714 conn = session->conns[session->cur_conn];
2716 /* Fetch properties. */
2717 fetch_props = apr_hash_make(pool);
2719 fetch_url = svn_path_url_add_component(session->repos_url.path, path, pool);
2721 /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
2723 * Otherwise, we need to get the baseline version for this particular
2724 * revision and then fetch that file.
2726 if (SVN_IS_VALID_REVNUM(revision))
2728 const char *vcc_url, *rel_path, *baseline_url;
2730 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &rel_path,
2731 session, conn, fetch_url, pool));
2733 SVN_ERR(svn_ra_serf__retrieve_props(fetch_props, session, conn, vcc_url,
2734 revision, "0", baseline_props, pool));
2736 baseline_url = svn_ra_serf__get_ver_prop(fetch_props, vcc_url, revision,
2737 "DAV:", "baseline-collection");
2739 fetch_url = svn_path_url_add_component(baseline_url, rel_path, pool);
2740 revision = SVN_INVALID_REVNUM;
2743 /* TODO Filter out all of our props into a usable format. */
2744 if (props)
2746 *props = apr_hash_make(pool);
2748 SVN_ERR(svn_ra_serf__retrieve_props(fetch_props, session, conn, fetch_url,
2749 revision, "0", all_props, pool));
2751 svn_ra_serf__walk_all_props(fetch_props, fetch_url, revision,
2752 svn_ra_serf__set_flat_props, *props, pool);
2755 if (stream)
2757 report_fetch_t *stream_ctx;
2759 /* Create the fetch context. */
2760 stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
2761 stream_ctx->pool = pool;
2762 stream_ctx->target_stream = stream;
2763 stream_ctx->sess = session;
2764 stream_ctx->conn = conn;
2765 stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
2766 stream_ctx->info->name = fetch_url;
2768 handler = apr_pcalloc(pool, sizeof(*handler));
2769 handler->method = "GET";
2770 handler->path = fetch_url;
2771 handler->conn = conn;
2772 handler->session = session;
2774 handler->response_handler = handle_stream;
2775 handler->response_baton = stream_ctx;
2777 handler->response_error = cancel_fetch;
2778 handler->response_error_baton = stream_ctx;
2780 svn_ra_serf__request_create(handler);
2782 SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
2783 SVN_ERR(stream_ctx->err);
2786 return SVN_NO_ERROR;