summaryrefslogtreecommitdiffstats
path: root/contrib/subversion/subversion/libsvn_ra_serf/update.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/subversion/subversion/libsvn_ra_serf/update.c')
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/update.c4038
1 files changed, 1730 insertions, 2308 deletions
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/update.c b/contrib/subversion/subversion/libsvn_ra_serf/update.c
index 88488ff..8313af0 100644
--- a/contrib/subversion/subversion/libsvn_ra_serf/update.c
+++ b/contrib/subversion/subversion/libsvn_ra_serf/update.c
@@ -49,6 +49,7 @@
#include "ra_serf.h"
#include "../libsvn_ra/ra_loader.h"
+
/*
* This enum represents the current state of our XML parsing for a REPORT.
@@ -63,23 +64,180 @@
* the tag is 'closed', the pool will be reused.
*/
typedef enum report_state_e {
- NONE = 0,
- INITIAL = 0,
- UPDATE_REPORT,
- TARGET_REVISION,
- OPEN_DIR,
- ADD_DIR,
- ABSENT_DIR,
- OPEN_FILE,
- ADD_FILE,
- ABSENT_FILE,
- PROP,
- IGNORE_PROP_NAME,
- NEED_PROP_NAME,
- TXDELTA
+ INITIAL = XML_STATE_INITIAL /* = 0 */,
+ UPDATE_REPORT,
+ TARGET_REVISION,
+
+ OPEN_DIR,
+ ADD_DIR,
+
+ OPEN_FILE,
+ ADD_FILE,
+
+ DELETE_ENTRY,
+ ABSENT_DIR,
+ ABSENT_FILE,
+
+ SET_PROP,
+ REMOVE_PROP,
+
+ PROP,
+
+ FETCH_FILE,
+ FETCH_PROPS,
+ TXDELTA,
+
+ CHECKED_IN,
+ CHECKED_IN_HREF,
+
+ MD5_CHECKSUM,
+
+ VERSION_NAME,
+ CREATIONDATE,
+ CREATOR_DISPLAYNAME
} report_state_e;
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+#define V_ SVN_DAV_PROP_NS_DAV
+static const svn_ra_serf__xml_transition_t update_ttable[] = {
+ { INITIAL, S_, "update-report", UPDATE_REPORT,
+ FALSE, { "?inline-props", "?send-all", NULL }, TRUE },
+
+ { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
+ FALSE, { "rev", NULL }, TRUE },
+
+ { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "add-directory", ADD_DIR,
+ FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/
+ NULL }, TRUE },
+
+ { ADD_DIR, S_, "add-directory", ADD_DIR,
+ FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/
+ NULL }, TRUE },
+
+ { OPEN_DIR, S_, "open-file", OPEN_FILE,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "open-file", OPEN_FILE,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "add-file", ADD_FILE,
+ FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
+ "?sha1-checksum", NULL }, TRUE },
+
+ { ADD_DIR, S_, "add-file", ADD_FILE,
+ FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
+ "?sha1-checksum", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "delete-entry", DELETE_ENTRY,
+ FALSE, { "?rev", "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "delete-entry", DELETE_ENTRY,
+ FALSE, { "?rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
+ FALSE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
+ FALSE, { "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
+ FALSE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "absent-file", ABSENT_FILE,
+ FALSE, { "name", NULL }, TRUE },
+
+
+ { OPEN_DIR, D_, "checked-in", CHECKED_IN,
+ FALSE, { NULL }, FALSE },
+
+ { ADD_DIR, D_, "checked-in", CHECKED_IN,
+ FALSE, { NULL }, FALSE },
+
+ { OPEN_FILE, D_, "checked-in", CHECKED_IN,
+ FALSE, { NULL }, FALSE },
+
+ { ADD_FILE, D_, "checked-in", CHECKED_IN,
+ FALSE, { NULL }, FALSE },
+
+
+ { OPEN_DIR, S_, "set-prop", SET_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { ADD_DIR, S_, "set-prop", SET_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { OPEN_FILE, S_, "set-prop", SET_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { ADD_FILE, S_, "set-prop", SET_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+
+ { OPEN_DIR, S_, "remove-prop", REMOVE_PROP,
+ TRUE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "remove-prop", REMOVE_PROP,
+ TRUE, { "name", NULL }, TRUE },
+
+ { OPEN_FILE, S_, "remove-prop", REMOVE_PROP,
+ TRUE, { "name", NULL }, TRUE },
+
+ { ADD_FILE, S_, "remove-prop", REMOVE_PROP,
+ TRUE, { "name", NULL }, TRUE },
+
+ { OPEN_FILE, S_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+ { OPEN_DIR, S_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+ { ADD_FILE, S_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+ { ADD_DIR, S_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+
+ { OPEN_FILE, S_, "txdelta", TXDELTA,
+ FALSE, { "?base-checksum" }, TRUE },
+
+ { ADD_FILE, S_, "txdelta", TXDELTA,
+ FALSE, { "?base-checksum" }, TRUE },
+
+ { OPEN_FILE, S_, "fetch-file", FETCH_FILE,
+ FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE},
+
+ { ADD_FILE, S_, "fetch-file", FETCH_FILE,
+ FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE },
+
+ { CHECKED_IN, D_, "href", CHECKED_IN_HREF,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, V_, "md5-checksum", MD5_CHECKSUM,
+ TRUE, { NULL }, TRUE },
+
+ /* These are only reported for <= 1.6.x mod_dav_svn */
+ { OPEN_DIR, S_, "fetch-props", FETCH_PROPS,
+ FALSE, { NULL }, FALSE },
+ { OPEN_FILE, S_, "fetch-props", FETCH_PROPS,
+ FALSE, { NULL }, FALSE },
+
+ { PROP, D_, "version-name", VERSION_NAME,
+ TRUE, { NULL }, TRUE },
+ { PROP, D_, "creationdate", CREATIONDATE,
+ TRUE, { NULL }, TRUE },
+ { PROP, D_, "creator-displayname", CREATOR_DISPLAYNAME,
+ TRUE, { NULL }, TRUE },
+ { 0 }
+};
+
/* While we process the REPORT response, we will queue up GET and PROPFIND
requests. For a very large checkout, it is very easy to queue requests
faster than they are resolved. Thus, we need to pause the XML processing
@@ -97,175 +255,120 @@ typedef enum report_state_e {
#define REQUEST_COUNT_TO_PAUSE 50
#define REQUEST_COUNT_TO_RESUME 40
+#define SPILLBUF_BLOCKSIZE 4096
+#define SPILLBUF_MAXBUFFSIZE 131072
+
+#define PARSE_CHUNK_SIZE 8000 /* Copied from xml.c ### Needs tuning */
/* Forward-declare our report context. */
typedef struct report_context_t report_context_t;
-
+typedef struct body_create_baton_t body_create_baton_t;
/*
* This structure represents the information for a directory.
*/
-typedef struct report_dir_t
+typedef struct dir_baton_t
{
- /* Our parent directory.
- *
- * This value is NULL when we are the root.
- */
- struct report_dir_t *parent_dir;
+ struct dir_baton_t *parent_dir; /* NULL when root */
- apr_pool_t *pool;
+ apr_pool_t *pool; /* Subpool for this directory */
/* Pointer back to our original report context. */
- report_context_t *report_context;
-
- /* Our name sans any parents. */
- const char *base_name;
+ report_context_t *ctx;
- /* the expanded directory name (including all parent names) */
- const char *name;
+ const char *relpath; /* session relative path */
+ const char *base_name; /* Name of item "" for root */
/* the canonical url for this directory after updating. (received) */
const char *url;
- /* The original repos_relpath of this url (from the working copy)
- or NULL if the repos_relpath can be calculated from the edit root. */
+ /* The original repos_relpath of this url (via the reporter)
+ directly, or via an ancestor. */
const char *repos_relpath;
- /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */
- svn_revnum_t base_rev;
+ svn_revnum_t base_rev; /* base revision or NULL for Add */
+
+ const char *copyfrom_path; /* NULL for open */
+ svn_revnum_t copyfrom_rev; /* SVN_INVALID_REVNUM for open */
/* controlling dir baton - this is only created in ensure_dir_opened() */
+ svn_boolean_t dir_opened;
void *dir_baton;
- apr_pool_t *dir_baton_pool;
/* How many references to this directory do we still have open? */
apr_size_t ref_count;
- /* Namespace list allocated out of this ->pool. */
- svn_ra_serf__ns_t *ns_list;
-
- /* hashtable for all of the properties (shared within a dir) */
- apr_hash_t *props;
-
- /* hashtable for all to-be-removed properties (shared within a dir) */
- apr_hash_t *removed_props;
-
- /* The propfind request for our current directory */
+ svn_boolean_t fetch_props; /* Use PROPFIND request? */
svn_ra_serf__handler_t *propfind_handler;
+ apr_hash_t *remove_props;
- /* Has the server told us to fetch the dir props? */
- svn_boolean_t fetch_props;
-
- /* Have we closed the directory tag (meaning no more additions)? */
- svn_boolean_t tag_closed;
-
- /* The children of this directory */
- struct report_dir_t *children;
-
- /* The next sibling of this directory */
- struct report_dir_t *sibling;
-} report_dir_t;
+} dir_baton_t;
/*
- * This structure represents the information for a file.
- *
- * A directory may have a report_info_t associated with it as well.
- *
- * This structure is created as we parse the REPORT response and
- * once the element is completed, we create a report_fetch_t structure
- * to give to serf to retrieve this file.
- */
-typedef struct report_info_t
+* This structure represents the information for a file.
+*
+* This structure is created as we parse the REPORT response and
+* once the element is completed, we may create a fetch_ctx_t structure
+* to give to serf to retrieve this file.
+*/
+typedef struct file_baton_t
{
- apr_pool_t *pool;
-
- /* The enclosing directory.
- *
- * If this structure refers to a directory, the dir it points to will be
- * itself.
- */
- report_dir_t *dir;
+ dir_baton_t *parent_dir; /* The parent */
+ apr_pool_t *pool; /* Subpool for this file*/
- /* Our name sans any directory info. */
+ const char *relpath; /* session relative path */
const char *base_name;
- /* the expanded file name (including all parent directory names) */
- const char *name;
-
- /* the canonical url for this file. */
+ /* the canonical url for this directory after updating. (received) */
const char *url;
+ /* The original repos_relpath of this url as reported. */
+ const char *repos_relpath;
+
/* lock token, if we had one to start off with. */
const char *lock_token;
- /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */
- svn_revnum_t base_rev;
-
- /* our delta base, if present (NULL if we're adding the file) */
- const char *delta_base;
+ svn_revnum_t base_rev; /* SVN_INVALID_REVNUM for Add */
- /* Path of original item if add with history */
- const char *copyfrom_path;
+ const char *copyfrom_path; /* NULL for open */
+ svn_revnum_t copyfrom_rev; /* SVN_INVALID_REVNUM for open */
- /* Revision of original item if add with history */
- svn_revnum_t copyfrom_rev;
+ /* controlling dir baton - this is only created in ensure_file_opened() */
+ svn_boolean_t file_opened;
+ void *file_baton;
- /* The propfind request for our current file (if present) */
+ svn_boolean_t fetch_props; /* Use PROPFIND request? */
svn_ra_serf__handler_t *propfind_handler;
-
- /* Has the server told us to fetch the file props? */
- svn_boolean_t fetch_props;
+ svn_boolean_t found_lock_prop;
+ apr_hash_t *remove_props;
/* Has the server told us to go fetch - only valid if we had it already */
svn_boolean_t fetch_file;
- /* The properties for this file */
- apr_hash_t *props;
+ /* controlling file_baton and textdelta handler */
+ svn_txdelta_window_handler_t txdelta;
+ void *txdelta_baton;
- /* pool passed to update->add_file, etc. */
- apr_pool_t *editor_pool;
+ svn_checksum_t *base_md5_checksum;
+ svn_checksum_t *final_md5_checksum;
+ svn_checksum_t *final_sha1_checksum;
- /* controlling file_baton and textdelta handler */
- void *file_baton;
- const char *base_checksum;
- const char *final_sha1_checksum;
- svn_txdelta_window_handler_t textdelta;
- void *textdelta_baton;
- svn_stream_t *svndiff_decoder;
- svn_stream_t *base64_decoder;
-
- /* Checksum for close_file */
- const char *final_checksum;
-
- /* Stream containing file contents already cached in the working
- copy (which may be used to avoid a GET request for the same). */
- svn_stream_t *cached_contents;
-
- /* temporary property for this file which is currently being parsed
- * It will eventually be stored in our parent directory's property hash.
- */
- const char *prop_ns;
- const char *prop_name;
- svn_stringbuf_t *prop_value;
- const char *prop_encoding;
-} report_info_t;
+ svn_stream_t *txdelta_stream; /* Stream that feeds windows when
+ written to within txdelta*/
+} file_baton_t;
/*
* This structure represents a single request to GET (fetch) a file with
* its associated Serf session/connection.
*/
-typedef struct report_fetch_t {
+typedef struct fetch_ctx_t {
/* The handler representing this particular fetch. */
svn_ra_serf__handler_t *handler;
- /* The session we should use to fetch the file. */
- svn_ra_serf__session_t *sess;
-
- /* The connection we should use to fetch file. */
- svn_ra_serf__connection_t *conn;
+ svn_boolean_t using_compression;
/* Stores the information for the file we want to fetch. */
- report_info_t *info;
+ file_baton_t *file;
/* Have we read our response headers yet? */
svn_boolean_t read_headers;
@@ -279,22 +382,13 @@ typedef struct report_fetch_t {
/* This is the amount of data that we have read so far. */
apr_off_t read_size;
- /* If we're receiving an svndiff, this will be non-NULL. */
- svn_stream_t *delta_stream;
-
/* If we're writing this file to a stream, this will be non-NULL. */
- svn_stream_t *target_stream;
-
- /* Are we done fetching this file? */
- svn_boolean_t done;
-
- /* Discard the rest of the content? */
- svn_boolean_t discard;
+ svn_stream_t *result_stream;
- svn_ra_serf__list_t **done_list;
- svn_ra_serf__list_t done_item;
+ /* The base-rev header */
+ const char *delta_base;
-} report_fetch_t;
+} fetch_ctx_t;
/*
* The master structure for a REPORT request and response.
@@ -303,7 +397,6 @@ struct report_context_t {
apr_pool_t *pool;
svn_ra_serf__session_t *sess;
- svn_ra_serf__connection_t *conn;
/* Source path and destination path */
const char *source;
@@ -315,6 +408,10 @@ struct report_context_t {
/* What is the target revision that we want for this REPORT? */
svn_revnum_t target_rev;
+ /* Where are we (used while parsing) */
+ dir_baton_t *cur_dir;
+ file_baton_t *cur_file;
+
/* Have we been asked to ignore ancestry or textdeltas? */
svn_boolean_t ignore_ancestry;
svn_boolean_t text_deltas;
@@ -332,170 +429,305 @@ struct report_context_t {
/* Path -> const char *repos_relpath mapping */
apr_hash_t *switched_paths;
- /* Boolean indicating whether "" is switched.
- (This indicates that the we are updating a single file) */
- svn_boolean_t root_is_switched;
-
/* Our master update editor and baton. */
- const svn_delta_editor_t *update_editor;
- void *update_baton;
+ const svn_delta_editor_t *editor;
+ void *editor_baton;
/* The file holding request body for the REPORT.
*
* ### todo: It will be better for performance to store small
* request bodies (like 4k) in memory and bigger bodies on disk.
*/
- apr_file_t *body_file;
-
- /* root directory object */
- report_dir_t *root_dir;
+ svn_stream_t *body_template;
+ body_create_baton_t *body;
/* number of pending GET requests */
unsigned int num_active_fetches;
- /* completed fetches (contains report_fetch_t) */
- svn_ra_serf__list_t *done_fetches;
-
/* number of pending PROPFIND requests */
unsigned int num_active_propfinds;
- /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */
- svn_ra_serf__list_t *done_propfinds;
- svn_ra_serf__list_t *done_dir_propfinds;
-
- /* list of outstanding prop changes (contains report_dir_t) */
- svn_ra_serf__list_t *active_dir_propfinds;
-
- /* list of files that only have prop changes (contains report_info_t) */
- svn_ra_serf__list_t *file_propchanges_only;
-
- /* The path to the REPORT request */
- const char *path;
-
/* Are we done parsing the REPORT response? */
svn_boolean_t done;
/* Did we receive all data from the network? */
svn_boolean_t report_received;
- /* Did we get a complete (non-truncated) report? */
- svn_boolean_t report_completed;
-
- /* The XML parser context for the REPORT response. */
- svn_ra_serf__xml_parser_t *parser_ctx;
-
/* Did we close the root directory? */
svn_boolean_t closed_root;
};
+/* Baton for collecting REPORT body. Depending on the size this
+ work is backed by a memory buffer (via serf buckets) or by
+ a file */
+struct body_create_baton_t
+{
+ apr_pool_t *result_pool;
+ apr_size_t total_bytes;
-#ifdef NOT_USED_YET
+ apr_pool_t *scratch_pool;
-#define D_ "DAV:"
-#define S_ SVN_XML_NAMESPACE
-static const svn_ra_serf__xml_transition_t update_ttable[] = {
- { INITIAL, S_, "update-report", UPDATE_REPORT,
- FALSE, { NULL }, FALSE },
+ serf_bucket_alloc_t *alloc;
+ serf_bucket_t *collect_bucket;
- { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
- FALSE, { "rev", NULL }, TRUE },
+ const void *all_data;
+ apr_file_t *file;
+};
- { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
- FALSE, { "rev", NULL }, TRUE },
- { OPEN_DIR, S_, "open-directory", OPEN_DIR,
- FALSE, { "rev", "name", NULL }, TRUE },
+#define MAX_BODY_IN_RAM (256*1024)
- { OPEN_DIR, S_, "add-directory", ADD_DIR,
- FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+/* Fold all previously collected data in a single buffer allocated in
+ RESULT_POOL and clear all intermediate state */
+static const char *
+body_allocate_all(body_create_baton_t *body,
+ apr_pool_t *result_pool)
+{
+ char *buffer = apr_pcalloc(result_pool, body->total_bytes);
+ const char *data;
+ apr_size_t sz;
+ apr_status_t s;
+ apr_size_t remaining = body->total_bytes;
+ char *next = buffer;
- { ADD_DIR, S_, "add-directory", ADD_DIR,
- FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+ while (!(s = serf_bucket_read(body->collect_bucket, remaining, &data, &sz)))
+ {
+ memcpy(next, data, sz);
+ remaining -= sz;
+ next += sz;
- { OPEN_DIR, S_, "open-file", OPEN_FILE,
- FALSE, { "rev", "name", NULL }, TRUE },
+ if (! remaining)
+ break;
+ }
- { OPEN_DIR, S_, "add-file", ADD_FILE,
- FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+ if (!SERF_BUCKET_READ_ERROR(s))
+ {
+ memcpy(next, data, sz);
+ }
- { ADD_DIR, S_, "add-file", ADD_FILE,
- FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+ serf_bucket_destroy(body->collect_bucket);
+ body->collect_bucket = NULL;
- { OPEN_DIR, S_, "delete-entry", OPEN_FILE,
- FALSE, { "?rev", "name", NULL }, TRUE },
+ return (s != APR_EOF) ? NULL : buffer;
+}
- { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
- FALSE, { "name", NULL }, TRUE },
+/* Noop function. Make serf take care of freeing in error situations */
+static void serf_free_no_error(void *unfreed_baton, void *block) {}
- { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
- FALSE, { "name", NULL }, TRUE },
+/* Stream write function for body creation */
+static svn_error_t *
+body_write_fn(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ body_create_baton_t *bcb = baton;
- { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
- FALSE, { "name", NULL }, TRUE },
+ if (!bcb->scratch_pool)
+ bcb->scratch_pool = svn_pool_create(bcb->result_pool);
- { ADD_DIR, S_, "absent-file", ABSENT_FILE,
- FALSE, { "name", NULL }, TRUE },
+ if (bcb->file)
+ {
+ SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL,
+ bcb->scratch_pool));
+ svn_pool_clear(bcb->scratch_pool);
- { 0 }
-};
+ bcb->total_bytes += *len;
+ }
+ else if (*len + bcb->total_bytes > MAX_BODY_IN_RAM)
+ {
+ SVN_ERR(svn_io_open_unique_file3(&bcb->file, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ bcb->result_pool, bcb->scratch_pool));
+ if (bcb->total_bytes)
+ {
+ const char *all = body_allocate_all(bcb, bcb->scratch_pool);
+ SVN_ERR(svn_io_file_write_full(bcb->file, all, bcb->total_bytes,
+ NULL, bcb->scratch_pool));
+ }
-/* Conforms to svn_ra_serf__xml_opened_t */
-static svn_error_t *
-update_opened(svn_ra_serf__xml_estate_t *xes,
- void *baton,
- int entered_state,
- const svn_ra_serf__dav_props_t *tag,
- apr_pool_t *scratch_pool)
-{
- report_context_t *ctx = baton;
+ SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL,
+ bcb->scratch_pool));
+ bcb->total_bytes += *len;
+ }
+ else
+ {
+ if (!bcb->alloc)
+ bcb->alloc = serf_bucket_allocator_create(bcb->scratch_pool,
+ serf_free_no_error, NULL);
+
+ if (!bcb->collect_bucket)
+ bcb->collect_bucket = serf_bucket_aggregate_create(bcb->alloc);
+
+ serf_bucket_aggregate_append(bcb->collect_bucket,
+ serf_bucket_simple_copy_create(data, *len,
+ bcb->alloc));
+
+ bcb->total_bytes += *len;
+ }
return SVN_NO_ERROR;
}
+/* Stream close function for collecting body */
+static svn_error_t *
+body_done_fn(void *baton)
+{
+ body_create_baton_t *bcb = baton;
+ if (bcb->file)
+ {
+ /* We need to flush the file, make it unbuffered (so that it can be
+ * zero-copied via mmap), and reset the position before attempting
+ * to deliver the file.
+ *
+ * N.B. If we have APR 1.3+, we can unbuffer the file to let us use
+ * mmap and zero-copy the PUT body. However, on older APR versions,
+ * we can't check the buffer status; but serf will fall through and
+ * create a file bucket for us on the buffered handle.
+ */
+ SVN_ERR(svn_io_file_flush(bcb->file, bcb->scratch_pool));
+ apr_file_buffer_set(bcb->file, NULL, 0);
+ }
+ else if (bcb->collect_bucket)
+ bcb->all_data = body_allocate_all(bcb, bcb->result_pool);
+
+ if (bcb->scratch_pool)
+ svn_pool_destroy(bcb->scratch_pool);
+
+ return SVN_NO_ERROR;
+}
-/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
-update_closed(svn_ra_serf__xml_estate_t *xes,
- void *baton,
- int leaving_state,
- const svn_string_t *cdata,
- apr_hash_t *attrs,
- apr_pool_t *scratch_pool)
+create_dir_baton(dir_baton_t **new_dir,
+ report_context_t *ctx,
+ const char *name,
+ apr_pool_t *scratch_pool)
{
- report_context_t *ctx = baton;
+ dir_baton_t *parent = ctx->cur_dir;
+ apr_pool_t *dir_pool;
+ dir_baton_t *dir;
- if (leaving_state == TARGET_REVISION)
+ if (parent)
+ dir_pool = svn_pool_create(parent->pool);
+ else
+ dir_pool = svn_pool_create(ctx->pool);
+
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+ dir->pool = dir_pool;
+ dir->ctx = ctx;
+
+ if (parent)
{
- const char *rev = svn_hash_gets(attrs, "rev");
+ dir->parent_dir = parent;
+ parent->ref_count++;
+ }
- SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
- SVN_STR_TO_REV(rev),
- ctx->sess->pool));
+ dir->relpath = parent ? svn_relpath_join(parent->relpath, name, dir_pool)
+ : apr_pstrdup(dir_pool, name);
+ dir->base_name = svn_relpath_basename(dir->relpath, NULL);
+
+ dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->relpath);
+ if (!dir->repos_relpath)
+ {
+ if (parent)
+ dir->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
+ dir_pool);
+ else
+ dir->repos_relpath = svn_uri_skip_ancestor(ctx->sess->repos_root_str,
+ ctx->sess->session_url_str,
+ dir_pool);
}
+ dir->base_rev = SVN_INVALID_REVNUM;
+ dir->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ dir->ref_count = 1;
+
+ ctx->cur_dir = dir;
+
+ *new_dir = dir;
return SVN_NO_ERROR;
}
-
-/* Conforms to svn_ra_serf__xml_cdata_t */
static svn_error_t *
-update_cdata(svn_ra_serf__xml_estate_t *xes,
- void *baton,
- int current_state,
- const char *data,
- apr_size_t len,
- apr_pool_t *scratch_pool)
+create_file_baton(file_baton_t **new_file,
+ report_context_t *ctx,
+ const char *name,
+ apr_pool_t *scratch_pool)
{
- report_context_t *ctx = baton;
+ dir_baton_t *parent = ctx->cur_dir;
+ apr_pool_t *file_pool;
+ file_baton_t *file;
+
+ file_pool = svn_pool_create(parent->pool);
+
+ file = apr_pcalloc(file_pool, sizeof(*file));
+ file->pool = file_pool;
+
+ file->parent_dir = parent;
+ parent->ref_count++;
+
+ file->relpath = svn_relpath_join(parent->relpath, name, file_pool);
+ file->base_name = svn_relpath_basename(file->relpath, NULL);
+
+ file->repos_relpath = svn_hash_gets(ctx->switched_paths, file->relpath);
+ if (!file->repos_relpath)
+ file->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
+ file_pool);
+
+ /* Sane defaults */
+ file->base_rev = SVN_INVALID_REVNUM;
+ file->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ *new_file = file;
+
+ ctx->cur_file = file;
return SVN_NO_ERROR;
}
-#endif /* NOT_USED_YET */
+/** Minimum nr. of outstanding requests needed before a new connection is
+ * opened. */
+#define REQS_PER_CONN 8
+/** This function creates a new connection for this serf session, but only
+ * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
+ * only one main connection open.
+ */
+static svn_error_t *
+open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
+{
+ /* For each REQS_PER_CONN outstanding requests open a new connection, with
+ * a minimum of 1 extra connection. */
+ if (sess->num_conns == 1 ||
+ ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
+ {
+ int cur = sess->num_conns;
+ apr_status_t status;
+
+ sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
+ sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
+ NULL, NULL);
+ sess->conns[cur]->last_status_code = -1;
+ sess->conns[cur]->session = sess;
+ status = serf_connection_create2(&sess->conns[cur]->conn,
+ sess->context,
+ sess->session_url,
+ svn_ra_serf__conn_setup,
+ sess->conns[cur],
+ svn_ra_serf__conn_closed,
+ sess->conns[cur],
+ sess->pool);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ sess->num_conns++;
+ }
+
+ return SVN_NO_ERROR;
+}
/* Returns best connection for fetching files/properties. */
static svn_ra_serf__connection_t *
@@ -520,391 +752,199 @@ get_best_connection(report_context_t *ctx)
if (ctx->report_received && (ctx->sess->max_connections > 2))
first_conn = 0;
- /* Currently, we just cycle connections. In the future we could
- store the number of pending requests on each connection, or
- perform other heuristics, to achieve better connection usage.
- (As an optimization, if there's only one available auxiliary
- connection to use, don't bother doing all the cur_conn math --
- just return that one connection.) */
+ /* If there's only one available auxiliary connection to use, don't bother
+ doing all the cur_conn math -- just return that one connection. */
if (ctx->sess->num_conns - first_conn == 1)
{
conn = ctx->sess->conns[first_conn];
}
else
{
+#if SERF_VERSION_AT_LEAST(1, 4, 0)
+ /* Often one connection is slower than others, e.g. because the server
+ process/thread has to do more work for the particular set of requests.
+ In the worst case, when REQUEST_COUNT_TO_RESUME requests are queued
+ on such a slow connection, ra_serf will completely stop sending
+ requests.
+
+ The method used here selects the connection with the least amount of
+ pending requests, thereby giving more work to lightly loaded server
+ processes.
+ */
+ int i, best_conn = first_conn;
+ unsigned int min = INT_MAX;
+ for (i = first_conn; i < ctx->sess->num_conns; i++)
+ {
+ serf_connection_t *sc = ctx->sess->conns[i]->conn;
+ unsigned int pending = serf_connection_pending_requests(sc);
+ if (pending < min)
+ {
+ min = pending;
+ best_conn = i;
+ }
+ }
+ conn = ctx->sess->conns[best_conn];
+#else
+ /* We don't know how many requests are pending per connection, so just
+ cycle them. */
conn = ctx->sess->conns[ctx->sess->cur_conn];
ctx->sess->cur_conn++;
if (ctx->sess->cur_conn >= ctx->sess->num_conns)
ctx->sess->cur_conn = first_conn;
+#endif
}
return conn;
}
-
-
-/** Report state management helper **/
-
-static report_info_t *
-push_state(svn_ra_serf__xml_parser_t *parser,
- report_context_t *ctx,
- report_state_e state)
-{
- report_info_t *info;
- apr_pool_t *info_parent_pool;
-
- svn_ra_serf__xml_push_state(parser, state);
-
- info = parser->state->private;
-
- /* Our private pool needs to be disjoint from the state pool. */
- if (!info)
- {
- info_parent_pool = ctx->pool;
- }
- else
- {
- info_parent_pool = info->pool;
- }
-
- if (state == OPEN_DIR || state == ADD_DIR)
- {
- report_info_t *new_info;
-
- new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
- new_info->pool = svn_pool_create(info_parent_pool);
- new_info->lock_token = NULL;
- new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
-
- new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
- new_info->dir->pool = new_info->pool;
-
- /* Create the root property tree. */
- new_info->dir->props = apr_hash_make(new_info->pool);
- new_info->props = new_info->dir->props;
- new_info->dir->removed_props = apr_hash_make(new_info->pool);
-
- new_info->dir->report_context = ctx;
-
- if (info)
- {
- info->dir->ref_count++;
-
- new_info->dir->parent_dir = info->dir;
-
- /* Point our ns_list at our parents to try to reuse it. */
- new_info->dir->ns_list = info->dir->ns_list;
-
- /* Add ourselves to our parent's list */
- new_info->dir->sibling = info->dir->children;
- info->dir->children = new_info->dir;
- }
- else
- {
- /* Allow us to be found later. */
- ctx->root_dir = new_info->dir;
- }
-
- parser->state->private = new_info;
- }
- else if (state == OPEN_FILE || state == ADD_FILE)
- {
- report_info_t *new_info;
-
- new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
- new_info->pool = svn_pool_create(info_parent_pool);
- new_info->file_baton = NULL;
- new_info->lock_token = NULL;
- new_info->fetch_file = FALSE;
- new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
-
- /* Point at our parent's directory state. */
- new_info->dir = info->dir;
- info->dir->ref_count++;
-
- new_info->props = apr_hash_make(new_info->pool);
-
- parser->state->private = new_info;
- }
-
- return parser->state->private;
-}
-
-
-/** Wrappers around our various property walkers **/
-
-static svn_error_t *
-set_file_props(void *baton,
- const char *ns,
- const char *name,
- const svn_string_t *val,
- apr_pool_t *scratch_pool)
-{
- report_info_t *info = baton;
- const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
- const char *prop_name;
-
- prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
- if (prop_name != NULL)
- return svn_error_trace(editor->change_file_prop(info->file_baton,
- prop_name,
- val,
- scratch_pool));
- return SVN_NO_ERROR;
-}
-
-
-static svn_error_t *
-set_dir_props(void *baton,
- const char *ns,
- const char *name,
- const svn_string_t *val,
- apr_pool_t *scratch_pool)
-{
- report_dir_t *dir = baton;
- const svn_delta_editor_t *editor = dir->report_context->update_editor;
- const char *prop_name;
-
- prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
- if (prop_name != NULL)
- return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
- prop_name,
- val,
- scratch_pool));
- return SVN_NO_ERROR;
-}
-
-
-static svn_error_t *
-remove_file_props(void *baton,
- const char *ns,
- const char *name,
- const svn_string_t *val,
- apr_pool_t *scratch_pool)
-{
- report_info_t *info = baton;
- const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
- const char *prop_name;
-
- prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
- if (prop_name != NULL)
- return svn_error_trace(editor->change_file_prop(info->file_baton,
- prop_name,
- NULL,
- scratch_pool));
- return SVN_NO_ERROR;
-}
-
-
-static svn_error_t *
-remove_dir_props(void *baton,
- const char *ns,
- const char *name,
- const svn_string_t *val,
- apr_pool_t *scratch_pool)
-{
- report_dir_t *dir = baton;
- const svn_delta_editor_t *editor = dir->report_context->update_editor;
- const char *prop_name;
-
- prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
- if (prop_name != NULL)
- return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
- prop_name,
- NULL,
- scratch_pool));
- return SVN_NO_ERROR;
-}
-
/** Helpers to open and close directories */
static svn_error_t*
-ensure_dir_opened(report_dir_t *dir)
+ensure_dir_opened(dir_baton_t *dir,
+ apr_pool_t *scratch_pool)
{
- report_context_t *ctx = dir->report_context;
+ report_context_t *ctx = dir->ctx;
- /* if we're already open, return now */
- if (dir->dir_baton)
- {
- return SVN_NO_ERROR;
- }
+ if (dir->dir_opened)
+ return SVN_NO_ERROR;
if (dir->base_name[0] == '\0')
{
- dir->dir_baton_pool = svn_pool_create(dir->pool);
-
if (ctx->destination
&& ctx->sess->wc_callbacks->invalidate_wc_props)
{
SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
ctx->sess->wc_callback_baton,
ctx->update_target,
- SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
+ SVN_RA_SERF__WC_CHECKED_IN_URL, scratch_pool));
}
- SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev,
- dir->dir_baton_pool,
- &dir->dir_baton));
+ SVN_ERR(ctx->editor->open_root(ctx->editor_baton, dir->base_rev,
+ dir->pool,
+ &dir->dir_baton));
}
else
{
- SVN_ERR(ensure_dir_opened(dir->parent_dir));
-
- dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool);
+ SVN_ERR(ensure_dir_opened(dir->parent_dir, scratch_pool));
if (SVN_IS_VALID_REVNUM(dir->base_rev))
{
- SVN_ERR(ctx->update_editor->open_directory(dir->name,
- dir->parent_dir->dir_baton,
- dir->base_rev,
- dir->dir_baton_pool,
- &dir->dir_baton));
+ SVN_ERR(ctx->editor->open_directory(dir->relpath,
+ dir->parent_dir->dir_baton,
+ dir->base_rev,
+ dir->pool,
+ &dir->dir_baton));
}
else
{
- SVN_ERR(ctx->update_editor->add_directory(dir->name,
- dir->parent_dir->dir_baton,
- NULL, SVN_INVALID_REVNUM,
- dir->dir_baton_pool,
- &dir->dir_baton));
+ SVN_ERR(ctx->editor->add_directory(dir->relpath,
+ dir->parent_dir->dir_baton,
+ dir->copyfrom_path,
+ dir->copyfrom_rev,
+ dir->pool,
+ &dir->dir_baton));
}
}
+ dir->dir_opened = TRUE;
+
return SVN_NO_ERROR;
}
static svn_error_t *
-close_dir(report_dir_t *dir)
+maybe_close_dir(dir_baton_t *dir)
{
- report_dir_t *prev;
- report_dir_t *sibling;
-
- /* ### is there a better pool... this is tossed at end-of-func */
- apr_pool_t *scratch_pool = dir->dir_baton_pool;
+ apr_pool_t *scratch_pool = dir->pool;
+ dir_baton_t *parent = dir->parent_dir;
+ report_context_t *ctx = dir->ctx;
- SVN_ERR_ASSERT(! dir->ref_count);
-
- SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name,
- dir->base_rev,
- set_dir_props, dir,
- scratch_pool));
-
- SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name,
- dir->base_rev, remove_dir_props, dir,
- scratch_pool));
-
- if (dir->fetch_props)
+ if (--dir->ref_count)
{
- SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url,
- dir->report_context->target_rev,
- set_dir_props, dir,
- scratch_pool));
+ return SVN_NO_ERROR;
}
- SVN_ERR(dir->report_context->update_editor->close_directory(
- dir->dir_baton, scratch_pool));
+ SVN_ERR(ensure_dir_opened(dir, dir->pool));
- /* remove us from our parent's children list */
- if (dir->parent_dir)
+ if (dir->remove_props)
{
- prev = NULL;
- sibling = dir->parent_dir->children;
+ apr_hash_index_t *hi;
- while (sibling != dir)
+ for (hi = apr_hash_first(scratch_pool, dir->remove_props);
+ hi;
+ hi = apr_hash_next(hi))
{
- prev = sibling;
- sibling = sibling->sibling;
- if (!sibling)
- SVN_ERR_MALFUNCTION();
- }
-
- if (!prev)
- {
- dir->parent_dir->children = dir->sibling;
- }
- else
- {
- prev->sibling = dir->sibling;
+ SVN_ERR(ctx->editor->change_file_prop(dir->dir_baton,
+ apr_hash_this_key(hi),
+ NULL /* value */,
+ scratch_pool));
}
}
- svn_pool_destroy(dir->dir_baton_pool);
- svn_pool_destroy(dir->pool);
+ SVN_ERR(dir->ctx->editor->close_directory(dir->dir_baton, scratch_pool));
- return SVN_NO_ERROR;
-}
+ svn_pool_destroy(dir->pool /* scratch_pool */);
-static svn_error_t *close_all_dirs(report_dir_t *dir)
-{
- while (dir->children)
- {
- SVN_ERR(close_all_dirs(dir->children));
- dir->ref_count--;
- }
-
- SVN_ERR_ASSERT(! dir->ref_count);
-
- SVN_ERR(ensure_dir_opened(dir));
-
- return close_dir(dir);
+ if (parent)
+ return svn_error_trace(maybe_close_dir(parent));
+ else
+ return SVN_NO_ERROR;
}
-
-/** Routines called when we are fetching a file */
-
-/* This function works around a bug in some older versions of
- * mod_dav_svn in that it will not send remove-prop in the update
- * report when a lock property disappears when send-all is false.
- *
- * Therefore, we'll try to look at our properties and see if there's
- * an active lock. If not, then we'll assume there isn't a lock
- * anymore.
- */
-static void
-check_lock(report_info_t *info)
+static svn_error_t *
+ensure_file_opened(file_baton_t *file,
+ apr_pool_t *scratch_pool)
{
- const char *lock_val;
+ const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
- lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
- info->dir->report_context->target_rev,
- "DAV:", "lockdiscovery");
+ if (file->file_opened)
+ return SVN_NO_ERROR;
- if (lock_val)
+ /* Ensure our parent is open. */
+ SVN_ERR(ensure_dir_opened(file->parent_dir, scratch_pool));
+
+ /* Open (or add) the file. */
+ if (SVN_IS_VALID_REVNUM(file->base_rev))
{
- char *new_lock;
- new_lock = apr_pstrdup(info->editor_pool, lock_val);
- apr_collapse_spaces(new_lock, new_lock);
- lock_val = new_lock;
+ SVN_ERR(editor->open_file(file->relpath,
+ file->parent_dir->dir_baton,
+ file->base_rev,
+ file->pool,
+ &file->file_baton));
}
-
- if (!lock_val || lock_val[0] == '\0')
+ else
{
- svn_string_t *str;
+ SVN_ERR(editor->add_file(file->relpath,
+ file->parent_dir->dir_baton,
+ file->copyfrom_path,
+ file->copyfrom_rev,
+ file->pool,
+ &file->file_baton));
+ }
- str = svn_string_ncreate("", 1, info->editor_pool);
+ file->file_opened = TRUE;
- svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name,
- info->base_rev, "DAV:", "lock-token",
- str, info->dir->pool);
- }
+ return SVN_NO_ERROR;
}
+
+/** Routines called when we are fetching a file */
+
static svn_error_t *
headers_fetch(serf_bucket_t *headers,
void *baton,
- apr_pool_t *pool)
+ apr_pool_t *pool /* request pool */,
+ apr_pool_t *scratch_pool)
{
- report_fetch_t *fetch_ctx = baton;
+ fetch_ctx_t *fetch_ctx = baton;
/* note that we have old VC URL */
- if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) &&
- fetch_ctx->info->delta_base)
+ if (fetch_ctx->delta_base)
{
serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
- fetch_ctx->info->delta_base);
+ fetch_ctx->delta_base);
serf_bucket_headers_setn(headers, "Accept-Encoding",
"svndiff1;q=0.9,svndiff;q=0.8");
}
- else if (fetch_ctx->sess->using_compression)
+ else if (fetch_ctx->using_compression)
{
serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
}
@@ -918,7 +958,7 @@ cancel_fetch(serf_request_t *request,
int status_code,
void *baton)
{
- report_fetch_t *fetch_ctx = baton;
+ fetch_ctx_t *fetch_ctx = baton;
/* Uh-oh. Our connection died on us.
*
@@ -948,30 +988,6 @@ cancel_fetch(serf_request_t *request,
SVN_ERR_MALFUNCTION();
}
-static svn_error_t *
-error_fetch(serf_request_t *request,
- report_fetch_t *fetch_ctx,
- svn_error_t *err)
-{
- fetch_ctx->done = TRUE;
-
- fetch_ctx->done_item.data = fetch_ctx;
- fetch_ctx->done_item.next = *fetch_ctx->done_list;
- *fetch_ctx->done_list = &fetch_ctx->done_item;
-
- /* Discard the rest of this request
- (This makes sure it doesn't error when the request is aborted later) */
- serf_request_set_handler(request,
- svn_ra_serf__response_discard_handler, NULL);
-
- /* Some errors would be handled by serf; make sure they really make
- the update fail by wrapping it in a different error. */
- if (!SERF_BUCKET_READ_ERROR(err->apr_err))
- return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
-
- return err;
-}
-
/* Wield the editor referenced by INFO to open (or add) the file
file also associated with INFO, setting properties on the file and
calling the editor's apply_textdelta() function on it if necessary
@@ -980,95 +996,92 @@ error_fetch(serf_request_t *request,
Callers will probably want to also see the function that serves
the opposite purpose of this one, close_updated_file(). */
static svn_error_t *
-open_updated_file(report_info_t *info,
- svn_boolean_t force_apply_textdelta,
+open_file_txdelta(file_baton_t *file,
apr_pool_t *scratch_pool)
{
- report_context_t *ctx = info->dir->report_context;
- const svn_delta_editor_t *update_editor = ctx->update_editor;
+ const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
- /* Ensure our parent is open. */
- SVN_ERR(ensure_dir_opened(info->dir));
- info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
+ SVN_ERR_ASSERT(file->txdelta == NULL);
- /* Expand our full name now if we haven't done so yet. */
- if (!info->name)
- {
- info->name = svn_relpath_join(info->dir->name, info->base_name,
- info->editor_pool);
- }
-
- /* Open (or add) the file. */
- if (SVN_IS_VALID_REVNUM(info->base_rev))
- {
- SVN_ERR(update_editor->open_file(info->name,
- info->dir->dir_baton,
- info->base_rev,
- info->editor_pool,
- &info->file_baton));
- }
- else
- {
- SVN_ERR(update_editor->add_file(info->name,
- info->dir->dir_baton,
- info->copyfrom_path,
- info->copyfrom_rev,
- info->editor_pool,
- &info->file_baton));
- }
-
- /* Check for lock information. */
- if (info->lock_token)
- check_lock(info);
+ SVN_ERR(ensure_file_opened(file, scratch_pool));
/* Get (maybe) a textdelta window handler for transmitting file
content changes. */
- if (info->fetch_file || force_apply_textdelta)
- {
- SVN_ERR(update_editor->apply_textdelta(info->file_baton,
- info->base_checksum,
- info->editor_pool,
- &info->textdelta,
- &info->textdelta_baton));
- }
+ SVN_ERR(editor->apply_textdelta(file->file_baton,
+ svn_checksum_to_cstring(
+ file->base_md5_checksum,
+ scratch_pool),
+ file->pool,
+ &file->txdelta,
+ &file->txdelta_baton));
return SVN_NO_ERROR;
}
-/* Close the file associated with INFO->file_baton, and cleanup other
- bits of that structure managed by open_updated_file(). */
+/* Close the file, handling loose ends and cleanup */
static svn_error_t *
-close_updated_file(report_info_t *info,
- apr_pool_t *scratch_pool)
+close_file(file_baton_t *file,
+ apr_pool_t *scratch_pool)
{
- report_context_t *ctx = info->dir->report_context;
+ dir_baton_t *parent_dir = file->parent_dir;
+ report_context_t *ctx = parent_dir->ctx;
+
+ SVN_ERR(ensure_file_opened(file, scratch_pool));
/* Set all of the properties we received */
- SVN_ERR(svn_ra_serf__walk_all_props(info->props,
- info->base_name,
- info->base_rev,
- set_file_props, info,
- scratch_pool));
- SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
- info->base_name,
- info->base_rev,
- remove_file_props, info,
- scratch_pool));
- if (info->fetch_props)
+ if (file->remove_props)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, file->remove_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
+ apr_hash_this_key(hi),
+ NULL /* value */,
+ scratch_pool));
+ }
+ }
+
+ /* Check for lock information. */
+
+ /* This works around a bug in some older versions of mod_dav_svn in that it
+ * will not send remove-prop in the update report when a lock property
+ * disappears when send-all is false.
+
+ ### Given that we only fetch props on additions, is this really necessary?
+ Or is it covering up old local copy bugs where we copied locks to other
+ paths? */
+ if (!ctx->add_props_included
+ && file->lock_token && !file->found_lock_prop
+ && SVN_IS_VALID_REVNUM(file->base_rev) /* file_is_added */)
+ {
+ SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
+ SVN_PROP_ENTRY_LOCK_TOKEN,
+ NULL,
+ scratch_pool));
+ }
+
+ if (file->url)
{
- SVN_ERR(svn_ra_serf__walk_all_props(info->props,
- info->url,
- ctx->target_rev,
- set_file_props, info,
- scratch_pool));
+ SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ svn_string_create(file->url,
+ scratch_pool),
+ scratch_pool));
}
/* Close the file via the editor. */
- SVN_ERR(info->dir->report_context->update_editor->close_file(
- info->file_baton, info->final_checksum, scratch_pool));
+ SVN_ERR(ctx->editor->close_file(file->file_baton,
+ svn_checksum_to_cstring(
+ file->final_md5_checksum,
+ scratch_pool),
+ scratch_pool));
+
+ svn_pool_destroy(file->pool);
- /* We're done with our editor pool. */
- svn_pool_destroy(info->editor_pool);
+ SVN_ERR(maybe_close_dir(parent_dir)); /* Remove reference */
return SVN_NO_ERROR;
}
@@ -1083,8 +1096,8 @@ handle_fetch(serf_request_t *request,
const char *data;
apr_size_t len;
apr_status_t status;
- report_fetch_t *fetch_ctx = handler_baton;
- svn_error_t *err;
+ fetch_ctx_t *fetch_ctx = handler_baton;
+ file_baton_t *file = fetch_ctx->file;
/* ### new field. make sure we didn't miss some initialization. */
SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
@@ -1093,50 +1106,53 @@ handle_fetch(serf_request_t *request,
{
serf_bucket_t *hdrs;
const char *val;
- report_info_t *info;
+
+ /* If the error code wasn't 200, something went wrong. Don't use the
+ * returned data as its probably an error message. Just bail out instead.
+ */
+ if (fetch_ctx->handler->sline.code != 200)
+ {
+ fetch_ctx->handler->discard_body = TRUE;
+ return SVN_NO_ERROR; /* Will return an error in the DONE handler */
+ }
hdrs = serf_bucket_response_get_headers(response);
val = serf_bucket_headers_get(hdrs, "Content-Type");
- info = fetch_ctx->info;
if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
{
- fetch_ctx->delta_stream =
- svn_txdelta_parse_svndiff(info->textdelta,
- info->textdelta_baton,
- TRUE, info->editor_pool);
+ fetch_ctx->result_stream =
+ svn_txdelta_parse_svndiff(file->txdelta,
+ file->txdelta_baton,
+ TRUE, file->pool);
/* Validate the delta base claimed by the server matches
what we asked for! */
val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
- if (val && (strcmp(val, info->delta_base) != 0))
+ if (val && fetch_ctx->delta_base == NULL)
+ {
+ /* We recieved response with delta base header while we didn't
+ requested it -- report it as error. */
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request returned unexpected "
+ "delta base: %s"), val);
+ }
+ else if (val && (strcmp(val, fetch_ctx->delta_base) != 0))
{
- err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
- _("GET request returned unexpected "
- "delta base: %s"), val);
- return error_fetch(request, fetch_ctx, err);
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request returned unexpected "
+ "delta base: %s"), val);
}
}
else
{
- fetch_ctx->delta_stream = NULL;
+ fetch_ctx->result_stream = NULL;
}
fetch_ctx->read_headers = TRUE;
}
- /* If the error code wasn't 200, something went wrong. Don't use the returned
- data as its probably an error message. Just bail out instead. */
- if (fetch_ctx->handler->sline.code != 200)
- {
- err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
- _("GET request failed: %d %s"),
- fetch_ctx->handler->sline.code,
- fetch_ctx->handler->sline.reason);
- return error_fetch(request, fetch_ctx, err);
- }
-
- while (1)
+ while (TRUE)
{
svn_txdelta_window_t delta_window = { 0 };
svn_txdelta_op_t delta_op;
@@ -1163,10 +1179,9 @@ handle_fetch(serf_request_t *request,
}
/* Skip on to the next iteration of this loop. */
- if (APR_STATUS_IS_EAGAIN(status))
- {
- return svn_ra_serf__wrap_err(status, NULL);
- }
+ if (status /* includes EAGAIN */)
+ return svn_ra_serf__wrap_err(status, NULL);
+
continue;
}
@@ -1176,17 +1191,12 @@ handle_fetch(serf_request_t *request,
/* Update data and len to just provide the new data. */
skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
data += skip;
- len -= skip;
+ len -= (apr_size_t)skip;
}
- if (fetch_ctx->delta_stream)
- {
- err = svn_stream_write(fetch_ctx->delta_stream, data, &len);
- if (err)
- {
- return error_fetch(request, fetch_ctx, err);
- }
- }
+ if (fetch_ctx->result_stream)
+ SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &len));
+
/* otherwise, manually construct the text delta window. */
else if (len)
{
@@ -1203,378 +1213,271 @@ handle_fetch(serf_request_t *request,
delta_window.new_data = &window_data;
/* write to the file located in the info. */
- err = fetch_ctx->info->textdelta(&delta_window,
- fetch_ctx->info->textdelta_baton);
- if (err)
- {
- return error_fetch(request, fetch_ctx, err);
- }
+ SVN_ERR(file->txdelta(&delta_window, file->txdelta_baton));
}
if (APR_STATUS_IS_EOF(status))
{
- report_info_t *info = fetch_ctx->info;
-
- if (fetch_ctx->delta_stream)
- err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream));
+ if (fetch_ctx->result_stream)
+ SVN_ERR(svn_stream_close(fetch_ctx->result_stream));
else
- err = svn_error_trace(info->textdelta(NULL,
- info->textdelta_baton));
- if (err)
- {
- return error_fetch(request, fetch_ctx, err);
- }
-
- err = close_updated_file(info, info->pool);
- if (err)
- {
- return svn_error_trace(error_fetch(request, fetch_ctx, err));
- }
-
- fetch_ctx->done = TRUE;
-
- fetch_ctx->done_item.data = fetch_ctx;
- fetch_ctx->done_item.next = *fetch_ctx->done_list;
- *fetch_ctx->done_list = &fetch_ctx->done_item;
-
- /* We're done with our pool. */
- svn_pool_destroy(info->pool);
-
- if (status)
- return svn_ra_serf__wrap_err(status, NULL);
- }
- if (APR_STATUS_IS_EAGAIN(status))
- {
- return svn_ra_serf__wrap_err(status, NULL);
+ SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
}
+
+ /* Report EOF, EEAGAIN and other special errors to serf */
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
}
- /* not reached */
}
-/* Implements svn_ra_serf__response_handler_t */
-static svn_error_t *
-handle_stream(serf_request_t *request,
- serf_bucket_t *response,
- void *handler_baton,
- apr_pool_t *pool)
-{
- report_fetch_t *fetch_ctx = handler_baton;
- svn_error_t *err;
- apr_status_t status;
-
- /* ### new field. make sure we didn't miss some initialization. */
- SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
+/* --------------------------------------------------------- */
- err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline,
- fetch_ctx->info->name,
- fetch_ctx->handler->location);
- if (err)
- {
- fetch_ctx->handler->done = TRUE;
+/** Wrappers around our various property walkers **/
- err = svn_error_compose_create(
- err,
- svn_ra_serf__handle_discard_body(request, response, NULL, pool));
+/* Implements svn_ra_serf__prop_func */
+static svn_error_t *
+set_file_props(void *baton,
+ const char *path,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ file_baton_t *file = baton;
+ report_context_t *ctx = file->parent_dir->ctx;
+ const char *prop_name;
- return svn_error_trace(err);
- }
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
- while (1)
+ if (!prop_name)
{
- const char *data;
- apr_size_t len;
-
- status = serf_bucket_read(response, 8000, &data, &len);
- if (SERF_BUCKET_READ_ERROR(status))
- {
- return svn_ra_serf__wrap_err(status, NULL);
- }
-
- fetch_ctx->read_size += len;
-
- if (fetch_ctx->aborted_read)
+ /* This works around a bug in some older versions of
+ * mod_dav_svn in that it will not send remove-prop in the update
+ * report when a lock property disappears when send-all is false.
+ *
+ * Therefore, we'll try to look at our properties and see if there's
+ * an active lock. If not, then we'll assume there isn't a lock
+ * anymore.
+ */
+ /* assert(!ctx->add_props_included); // Or we wouldn't be here */
+ if (file->lock_token
+ && !file->found_lock_prop
+ && val
+ && strcmp(ns, "DAV:") == 0
+ && strcmp(name, "lockdiscovery") == 0)
{
- /* We haven't caught up to where we were before. */
- if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
- {
- /* Eek. What did the file shrink or something? */
- if (APR_STATUS_IS_EOF(status))
- {
- SVN_ERR_MALFUNCTION();
- }
-
- /* Skip on to the next iteration of this loop. */
- if (APR_STATUS_IS_EAGAIN(status))
- {
- return svn_ra_serf__wrap_err(status, NULL);
- }
- continue;
- }
+ char *new_lock;
+ new_lock = apr_pstrdup(scratch_pool, val->data);
+ apr_collapse_spaces(new_lock, new_lock);
- /* Woo-hoo. We're back. */
- fetch_ctx->aborted_read = FALSE;
-
- /* Increment data and len by the difference. */
- data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
- len += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
+ if (new_lock[0] != '\0')
+ file->found_lock_prop = TRUE;
}
- if (len)
- {
- apr_size_t written_len;
-
- written_len = len;
+ return SVN_NO_ERROR;
+ }
- SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data,
- &written_len));
- }
+ SVN_ERR(ensure_file_opened(file, scratch_pool));
- if (APR_STATUS_IS_EOF(status))
- {
- fetch_ctx->done = TRUE;
- }
+ SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
+ prop_name, val,
+ scratch_pool));
- if (status)
- {
- return svn_ra_serf__wrap_err(status, NULL);
- }
- }
- /* not reached */
+ return SVN_NO_ERROR;
}
-/* Close the directory represented by DIR -- and any suitable parents
- thereof -- if we are able to do so. This is the case whenever:
-
- - there are no remaining open items within the directory, and
- - the directory's XML close tag has been processed (so we know
- there are no more children to worry about in the future), and
- - either:
- - we aren't fetching properties for this directory, or
- - we've already finished fetching those properties.
-*/
+/* Implements svn_ra_serf__response_done_delegate_t */
static svn_error_t *
-maybe_close_dir_chain(report_dir_t *dir)
+file_props_done(serf_request_t *request,
+ void *baton,
+ apr_pool_t *scratch_pool)
{
- report_dir_t *cur_dir = dir;
+ file_baton_t *file = baton;
+ svn_ra_serf__handler_t *handler = file->propfind_handler;
- SVN_ERR(ensure_dir_opened(cur_dir));
+ if (handler->server_error)
+ return svn_error_trace(svn_ra_serf__server_error_create(handler,
+ scratch_pool));
- while (cur_dir
- && !cur_dir->ref_count
- && cur_dir->tag_closed
- && (!cur_dir->fetch_props || cur_dir->propfind_handler->done))
- {
- report_dir_t *parent = cur_dir->parent_dir;
- report_context_t *report_context = cur_dir->report_context;
- svn_boolean_t propfind_in_done_list = FALSE;
- svn_ra_serf__list_t *done_list;
-
- /* Make sure there are no references to this dir in the
- active_dir_propfinds list. If there are, don't close the
- directory -- which would delete the pool from which the
- relevant active_dir_propfinds list item is allocated -- and
- of course don't crawl upward to check the parents for
- a closure opportunity, either. */
- done_list = report_context->active_dir_propfinds;
- while (done_list)
- {
- if (done_list->data == cur_dir)
- {
- propfind_in_done_list = TRUE;
- break;
- }
- done_list = done_list->next;
- }
- if (propfind_in_done_list)
- break;
+ if (handler->sline.code != 207)
+ return svn_error_trace(svn_ra_serf__unexpected_status(handler));
- SVN_ERR(close_dir(cur_dir));
- if (parent)
- {
- parent->ref_count--;
- }
- else
- {
- report_context->closed_root = TRUE;
- }
- cur_dir = parent;
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Open the file associated with INFO for editing, pass along any
- propchanges we've recorded for it, and then close the file. */
-static svn_error_t *
-handle_propchange_only(report_info_t *info,
- apr_pool_t *scratch_pool)
-{
- SVN_ERR(open_updated_file(info, FALSE, scratch_pool));
- SVN_ERR(close_updated_file(info, scratch_pool));
+ file->parent_dir->ctx->num_active_propfinds--;
- /* We're done with our pool. */
- svn_pool_destroy(info->pool);
+ file->fetch_props = FALSE;
- info->dir->ref_count--;
+ if (file->fetch_file)
+ return SVN_NO_ERROR; /* Still processing file request */
- /* See if the parent directory of this file (and perhaps even
- parents of that) can be closed now. */
- SVN_ERR(maybe_close_dir_chain(info->dir));
+ /* Closing the file will automatically deliver the propfind props.
+ *
+ * Note that closing the directory may dispose the pool containing the
+ * handler, which is only a valid operation in this callback, as only
+ * after this callback our serf plumbing assumes the request is done. */
- return SVN_NO_ERROR;
+ return svn_error_trace(close_file(file, scratch_pool));
}
-/* "Fetch" a file whose contents were made available via the
- get_wc_contents() callback (as opposed to requiring a GET to the
- server), and feed the information through the associated update
- editor. In editor-speak, this will add/open the file, transmit any
- property changes, handle the contents, and then close the file. */
static svn_error_t *
-handle_local_content(report_info_t *info,
- apr_pool_t *scratch_pool)
+file_fetch_done(serf_request_t *request,
+ void *baton,
+ apr_pool_t *scratch_pool)
{
- SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta,
- info->textdelta_baton, NULL, scratch_pool));
- SVN_ERR(svn_stream_close(info->cached_contents));
- info->cached_contents = NULL;
- SVN_ERR(close_updated_file(info, scratch_pool));
+ fetch_ctx_t *fetch_ctx = baton;
+ file_baton_t *file = fetch_ctx->file;
+ svn_ra_serf__handler_t *handler = fetch_ctx->handler;
- /* We're done with our pool. */
- svn_pool_destroy(info->pool);
+ if (handler->server_error)
+ return svn_error_trace(svn_ra_serf__server_error_create(handler,
+ scratch_pool));
- info->dir->ref_count--;
+ if (handler->sline.code != 200)
+ return svn_error_trace(svn_ra_serf__unexpected_status(handler));
- /* See if the parent directory of this fetched item (and
- perhaps even parents of that) can be closed now. */
- SVN_ERR(maybe_close_dir_chain(info->dir));
+ file->parent_dir->ctx->num_active_fetches--;
- return SVN_NO_ERROR;
-}
+ file->fetch_file = FALSE;
-/* --------------------------------------------------------- */
+ if (file->fetch_props)
+ return SVN_NO_ERROR; /* Still processing PROPFIND request */
+ /* Closing the file will automatically deliver the propfind props.
+ *
+ * Note that closing the directory may dispose the pool containing the
+ * handler, fetch_ctx, etc. which is only a valid operation in this
+ * callback, as only after this callback our serf plumbing assumes the
+ * request is done. */
+ return svn_error_trace(close_file(file, scratch_pool));
+}
+
+/* Initiates additional requests needed for a file when not in "send-all" mode.
+ */
static svn_error_t *
-fetch_file(report_context_t *ctx, report_info_t *info)
+fetch_for_file(file_baton_t *file,
+ apr_pool_t *scratch_pool)
{
+ report_context_t *ctx = file->parent_dir->ctx;
svn_ra_serf__connection_t *conn;
svn_ra_serf__handler_t *handler;
+ /* Open extra connections if we have enough requests to send. */
+ if (ctx->sess->num_conns < ctx->sess->max_connections)
+ SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
+ ctx->num_active_propfinds));
+
/* What connection should we go on? */
conn = get_best_connection(ctx);
- /* If needed, create the PROPFIND to retrieve the file's properties. */
- info->propfind_handler = NULL;
- if (info->fetch_props)
- {
- SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props,
- ctx->sess, conn, info->url,
- ctx->target_rev, "0", all_props,
- &ctx->done_propfinds,
- info->dir->pool));
- SVN_ERR_ASSERT(info->propfind_handler);
-
- /* Create a serf request for the PROPFIND. */
- svn_ra_serf__request_create(info->propfind_handler);
-
- ctx->num_active_propfinds++;
- }
+ /* Note that we (still) use conn for both requests.. Should we send
+ them out on different connections? */
- /* If we've been asked to fetch the file or it's an add, do so.
- * Otherwise, handle the case where only the properties changed.
- */
- if (info->fetch_file && ctx->text_deltas)
+ if (file->fetch_file)
{
- svn_stream_t *contents = NULL;
+ SVN_ERR(open_file_txdelta(file, scratch_pool));
- /* Open the file for editing. */
- SVN_ERR(open_updated_file(info, FALSE, info->pool));
-
- if (info->textdelta == svn_delta_noop_window_handler)
+ if (!ctx->text_deltas
+ || file->txdelta == svn_delta_noop_window_handler)
{
- /* There is nobody looking for an actual stream.
-
- Just report an empty stream instead of fetching
- to be ingored data */
- info->cached_contents = svn_stream_empty(info->pool);
+ SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
+ file->fetch_file = FALSE;
}
- else if (ctx->sess->wc_callbacks->get_wc_contents
- && info->final_sha1_checksum)
- {
- svn_error_t *err = NULL;
- svn_checksum_t *checksum = NULL;
-
- /* Parse the optional SHA1 checksum (1.7+) */
- err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
- info->final_sha1_checksum,
- info->pool);
- /* Okay so far? Let's try to get a stream on some readily
- available matching content. */
- if (!err && checksum)
- {
- err = ctx->sess->wc_callbacks->get_wc_contents(
- ctx->sess->wc_callback_baton, &contents,
- checksum, info->pool);
+ if (file->fetch_file
+ && file->final_sha1_checksum
+ && ctx->sess->wc_callbacks->get_wc_contents)
+ {
+ svn_error_t *err;
+ svn_stream_t *cached_contents = NULL;
- if (! err)
- info->cached_contents = contents;
- }
+ err = ctx->sess->wc_callbacks->get_wc_contents(
+ ctx->sess->wc_callback_baton,
+ &cached_contents,
+ file->final_sha1_checksum,
+ scratch_pool);
- if (err)
+ if (err || !cached_contents)
+ svn_error_clear(err); /* ### Can we return some/most errors? */
+ else
{
- /* Meh. Maybe we'll care one day why we're in an
- errorful state, but this codepath is optional. */
- svn_error_clear(err);
+ /* ### For debugging purposes we could validate the md5 here,
+ but our implementations in libsvn_client already do that
+ for us... */
+ SVN_ERR(svn_txdelta_send_stream(cached_contents,
+ file->txdelta,
+ file->txdelta_baton,
+ NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(cached_contents));
+ file->fetch_file = FALSE;
}
}
- /* If the working copy can provide cached contents for this
- file, we don't have to fetch them from the server. */
- if (info->cached_contents)
+ if (file->fetch_file)
{
- /* If we'll be doing a PROPFIND for this file... */
- if (info->propfind_handler)
+ fetch_ctx_t *fetch_ctx;
+
+ /* Let's fetch the file with a GET request... */
+ SVN_ERR_ASSERT(file->url && file->repos_relpath);
+
+ /* Otherwise, we use a GET request for the file's contents. */
+
+ fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx));
+ fetch_ctx->file = file;
+ fetch_ctx->using_compression = ctx->sess->using_compression;
+
+ /* Can we somehow get away with just obtaining a DIFF? */
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
{
- /* ... then we'll just leave ourselves a little "todo"
- about that fact (and we'll deal with the file content
- stuff later, after we've handled that PROPFIND
- response. */
- svn_ra_serf__list_t *list_item;
-
- list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
- list_item->data = info;
- list_item->next = ctx->file_propchanges_only;
- ctx->file_propchanges_only = list_item;
+ /* If this file is switched vs the editor root we should provide
+ its real url instead of the one calculated from the session root.
+ */
+ if (SVN_IS_VALID_REVNUM(file->base_rev))
+ {
+ fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
+ ctx->sess->rev_root_stub,
+ file->base_rev,
+ svn_path_uri_encode(
+ file->repos_relpath,
+ scratch_pool));
+ }
+ else if (file->copyfrom_path)
+ {
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(file->copyfrom_rev));
+
+ fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
+ ctx->sess->rev_root_stub,
+ file->copyfrom_rev,
+ svn_path_uri_encode(
+ file->copyfrom_path+1,
+ scratch_pool));
+ }
}
- else
+ else if (ctx->sess->wc_callbacks->get_wc_prop)
{
- /* Otherwise, if we've no PROPFIND to do, we might as
- well take care of those locally accessible file
- contents now. */
- SVN_ERR(handle_local_content(info, info->pool));
+ /* If we have a WC, we might be able to dive all the way into the WC
+ * to get the previous URL so we can do a differential GET with the
+ * base URL.
+ */
+ const svn_string_t *value = NULL;
+ SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
+ ctx->sess->wc_callback_baton,
+ file->relpath,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ &value, scratch_pool));
+
+ fetch_ctx->delta_base = value
+ ? apr_pstrdup(file->pool, value->data)
+ : NULL;
}
- }
- else
- {
- /* Otherwise, we use a GET request for the file's contents. */
- report_fetch_t *fetch_ctx;
- fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
- fetch_ctx->info = info;
- fetch_ctx->done_list = &ctx->done_fetches;
- fetch_ctx->sess = ctx->sess;
- fetch_ctx->conn = conn;
+ handler = svn_ra_serf__create_handler(ctx->sess, file->pool);
- handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
-
- handler->handler_pool = info->dir->pool;
handler->method = "GET";
- handler->path = fetch_ctx->info->url;
+ handler->path = file->url;
- handler->conn = conn;
- handler->session = ctx->sess;
+ handler->conn = conn; /* Explicit scheduling */
handler->custom_accept_encoding = TRUE;
+ handler->no_dav_headers = TRUE;
handler->header_delegate = headers_fetch;
handler->header_delegate_baton = fetch_ctx;
@@ -1584,6 +1487,9 @@ fetch_file(report_context_t *ctx, report_info_t *info)
handler->response_error = cancel_fetch;
handler->response_error_baton = fetch_ctx;
+ handler->done_delegate = file_fetch_done;
+ handler->done_delegate_baton = fetch_ctx;
+
fetch_ctx->handler = handler;
svn_ra_serf__request_create(handler);
@@ -1591,944 +1497,691 @@ fetch_file(report_context_t *ctx, report_info_t *info)
ctx->num_active_fetches++;
}
}
- else if (info->propfind_handler)
- {
- svn_ra_serf__list_t *list_item;
- list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
- list_item->data = info;
- list_item->next = ctx->file_propchanges_only;
- ctx->file_propchanges_only = list_item;
- }
- else
+ /* If needed, create the PROPFIND to retrieve the file's properties. */
+ if (file->fetch_props)
{
- /* No propfind or GET request. Just handle the prop changes now. */
- SVN_ERR(handle_propchange_only(info, info->pool));
+ SVN_ERR(svn_ra_serf__create_propfind_handler(&file->propfind_handler,
+ ctx->sess, file->url,
+ ctx->target_rev, "0",
+ all_props,
+ set_file_props, file,
+ file->pool));
+ file->propfind_handler->conn = conn; /* Explicit scheduling */
+
+ file->propfind_handler->done_delegate = file_props_done;
+ file->propfind_handler->done_delegate_baton = file;
+
+ /* Create a serf request for the PROPFIND. */
+ svn_ra_serf__request_create(file->propfind_handler);
+
+ ctx->num_active_propfinds++;
}
- if (ctx->num_active_fetches + ctx->num_active_propfinds
- > REQUEST_COUNT_TO_PAUSE)
- ctx->parser_ctx->paused = TRUE;
+ if (file->fetch_props || file->fetch_file)
+ return SVN_NO_ERROR;
- return SVN_NO_ERROR;
-}
-
-/** XML callbacks for our update-report response parsing */
+ /* Somehow we are done; probably via the local cache.
+ Close the file and release memory, etc. */
+ return svn_error_trace(close_file(file, scratch_pool));
+}
+
+/* Implements svn_ra_serf__prop_func */
static svn_error_t *
-start_report(svn_ra_serf__xml_parser_t *parser,
- svn_ra_serf__dav_props_t name,
- const char **attrs,
+set_dir_prop(void *baton,
+ const char *path,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
apr_pool_t *scratch_pool)
{
- report_context_t *ctx = parser->user_data;
- report_state_e state;
+ dir_baton_t *dir = baton;
+ report_context_t *ctx = dir->ctx;
+ const char *prop_name;
- state = parser->state->current_state;
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name == NULL)
+ return SVN_NO_ERROR;
- if (state == NONE && strcmp(name.name, "update-report") == 0)
- {
- const char *val;
+ SVN_ERR(ensure_dir_opened(dir, scratch_pool));
- val = svn_xml_get_attr_value("inline-props", attrs);
- if (val && (strcmp(val, "true") == 0))
- ctx->add_props_included = TRUE;
+ SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
+ prop_name, val,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
- val = svn_xml_get_attr_value("send-all", attrs);
- if (val && (strcmp(val, "true") == 0))
- {
- ctx->send_all_mode = TRUE;
+/* Implements svn_ra_serf__response_done_delegate_t */
+static svn_error_t *
+dir_props_done(serf_request_t *request,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ dir_baton_t *dir = baton;
+ svn_ra_serf__handler_t *handler = dir->propfind_handler;
- /* All properties are included in send-all mode. */
- ctx->add_props_included = TRUE;
- }
- }
- else if (state == NONE && strcmp(name.name, "target-revision") == 0)
- {
- const char *rev;
+ if (handler->server_error)
+ return svn_ra_serf__server_error_create(handler, scratch_pool);
- rev = svn_xml_get_attr_value("rev", attrs);
+ if (handler->sline.code != 207)
+ return svn_error_trace(svn_ra_serf__unexpected_status(handler));
- if (!rev)
- {
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing revision attr in target-revision element"));
- }
+ dir->ctx->num_active_propfinds--;
- SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
- SVN_STR_TO_REV(rev),
- ctx->sess->pool));
- }
- else if (state == NONE && strcmp(name.name, "open-directory") == 0)
- {
- const char *rev;
- report_info_t *info;
+ /* Closing the directory will automatically deliver the propfind props.
+ *
+ * Note that closing the directory may dispose the pool containing the
+ * handler, which is only a valid operation in this callback, as after
+ * this callback serf assumes the request is done. */
- rev = svn_xml_get_attr_value("rev", attrs);
+ return svn_error_trace(maybe_close_dir(dir));
+}
- if (!rev)
- {
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing revision attr in open-directory element"));
- }
+/* Initiates additional requests needed for a directory when not in "send-all"
+ * mode */
+static svn_error_t *
+fetch_for_dir(dir_baton_t *dir,
+ apr_pool_t *scratch)
+{
+ report_context_t *ctx = dir->ctx;
+ svn_ra_serf__connection_t *conn;
- info = push_state(parser, ctx, OPEN_DIR);
+ /* Open extra connections if we have enough requests to send. */
+ if (ctx->sess->num_conns < ctx->sess->max_connections)
+ SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
+ ctx->num_active_propfinds));
- info->base_rev = SVN_STR_TO_REV(rev);
- info->dir->base_rev = info->base_rev;
- info->fetch_props = TRUE;
+ /* What connection should we go on? */
+ conn = get_best_connection(ctx);
- info->dir->base_name = "";
- info->dir->name = "";
+ /* If needed, create the PROPFIND to retrieve the file's properties. */
+ if (dir->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__create_propfind_handler(&dir->propfind_handler,
+ ctx->sess, dir->url,
+ ctx->target_rev, "0",
+ all_props,
+ set_dir_prop, dir,
+ dir->pool));
- info->base_name = info->dir->base_name;
- info->name = info->dir->name;
+ dir->propfind_handler->conn = conn;
+ dir->propfind_handler->done_delegate = dir_props_done;
+ dir->propfind_handler->done_delegate_baton = dir;
- info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, "");
+ /* Create a serf request for the PROPFIND. */
+ svn_ra_serf__request_create(dir->propfind_handler);
- if (!info->dir->repos_relpath)
- SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath,
- ctx->sess->session_url.path,
- ctx->sess, ctx->conn,
- info->dir->pool));
- }
- else if (state == NONE)
- {
- /* do nothing as we haven't seen our valid start tag yet. */
+ ctx->num_active_propfinds++;
}
- else if ((state == OPEN_DIR || state == ADD_DIR) &&
- strcmp(name.name, "open-directory") == 0)
- {
- const char *rev, *dirname;
- report_dir_t *dir;
- report_info_t *info;
+ else
+ SVN_ERR_MALFUNCTION();
- rev = svn_xml_get_attr_value("rev", attrs);
+ return SVN_NO_ERROR;
+}
- if (!rev)
- {
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing revision attr in open-directory element"));
- }
+
+/** XML callbacks for our update-report response parsing */
- dirname = svn_xml_get_attr_value("name", attrs);
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+update_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
+ apr_hash_t *attrs;
- if (!dirname)
+ switch (entered_state)
+ {
+ case UPDATE_REPORT:
{
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in open-directory element"));
- }
-
- info = push_state(parser, ctx, OPEN_DIR);
-
- dir = info->dir;
+ const char *val;
- info->base_rev = SVN_STR_TO_REV(rev);
- dir->base_rev = info->base_rev;
+ attrs = svn_ra_serf__xml_gather_since(xes, UPDATE_REPORT);
+ val = svn_hash_gets(attrs, "inline-props");
- info->fetch_props = FALSE;
+ if (val && (strcmp(val, "true") == 0))
+ ctx->add_props_included = TRUE;
- dir->base_name = apr_pstrdup(dir->pool, dirname);
- info->base_name = dir->base_name;
+ val = svn_hash_gets(attrs, "send-all");
- /* Expand our name. */
- dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
- dir->pool);
- info->name = dir->name;
-
- dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name);
+ if (val && (strcmp(val, "true") == 0))
+ {
+ ctx->send_all_mode = TRUE;
- if (!dir->repos_relpath)
- dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
- dir->base_name, dir->pool);
- }
- else if ((state == OPEN_DIR || state == ADD_DIR) &&
- strcmp(name.name, "add-directory") == 0)
- {
- const char *dir_name, *cf, *cr;
- report_dir_t *dir;
- report_info_t *info;
+ /* All properties are included in send-all mode. */
+ ctx->add_props_included = TRUE;
+ }
+ }
+ break;
- dir_name = svn_xml_get_attr_value("name", attrs);
- if (!dir_name)
+ case OPEN_DIR:
+ case ADD_DIR:
{
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in add-directory element"));
- }
- cf = svn_xml_get_attr_value("copyfrom-path", attrs);
- cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
+ dir_baton_t *dir;
+ const char *name;
+ attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
- info = push_state(parser, ctx, ADD_DIR);
+ name = svn_hash_gets(attrs, "name");
+ if (!name)
+ name = "";
- dir = info->dir;
+ SVN_ERR(create_dir_baton(&dir, ctx, name, scratch_pool));
- dir->base_name = apr_pstrdup(dir->pool, dir_name);
- info->base_name = dir->base_name;
+ if (entered_state == OPEN_DIR)
+ {
+ apr_int64_t base_rev;
- /* Expand our name. */
- dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
- dir->pool);
- info->name = dir->name;
+ SVN_ERR(svn_cstring_atoi64(&base_rev,
+ svn_hash_gets(attrs, "rev")));
+ dir->base_rev = (svn_revnum_t)base_rev;
+ }
+ else
+ {
+ dir->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
- info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
- info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
+ if (dir->copyfrom_path)
+ {
+ apr_int64_t copyfrom_rev;
+ const char *copyfrom_rev_str;
+ dir->copyfrom_path = svn_fspath__canonicalize(
+ dir->copyfrom_path,
+ dir->pool);
- /* Mark that we don't have a base. */
- info->base_rev = SVN_INVALID_REVNUM;
- dir->base_rev = info->base_rev;
+ copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
- /* If the server isn't included properties for added items,
- we'll need to fetch them ourselves. */
- if (! ctx->add_props_included)
- dir->fetch_props = TRUE;
+ if (!copyfrom_rev_str)
+ return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
+ NULL,
+ _("Missing '%s' attribute"),
+ "copyfrom-rev");
- dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
- dir->base_name, dir->pool);
- }
- else if ((state == OPEN_DIR || state == ADD_DIR) &&
- strcmp(name.name, "open-file") == 0)
- {
- const char *file_name, *rev;
- report_info_t *info;
+ SVN_ERR(svn_cstring_atoi64(&copyfrom_rev, copyfrom_rev_str));
- file_name = svn_xml_get_attr_value("name", attrs);
+ dir->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
+ }
- if (!file_name)
- {
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in open-file element"));
+ if (! ctx->add_props_included)
+ dir->fetch_props = TRUE;
+ }
}
-
- rev = svn_xml_get_attr_value("rev", attrs);
-
- if (!rev)
+ break;
+ case OPEN_FILE:
+ case ADD_FILE:
{
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing revision attr in open-file element"));
- }
+ file_baton_t *file;
- info = push_state(parser, ctx, OPEN_FILE);
+ attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
- info->base_rev = SVN_STR_TO_REV(rev);
- info->fetch_props = FALSE;
+ SVN_ERR(create_file_baton(&file, ctx, svn_hash_gets(attrs, "name"),
+ scratch_pool));
- info->base_name = apr_pstrdup(info->pool, file_name);
- info->name = NULL;
- }
- else if ((state == OPEN_DIR || state == ADD_DIR) &&
- strcmp(name.name, "add-file") == 0)
- {
- const char *file_name, *cf, *cr;
- report_info_t *info;
-
- file_name = svn_xml_get_attr_value("name", attrs);
- cf = svn_xml_get_attr_value("copyfrom-path", attrs);
- cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
+ if (entered_state == OPEN_FILE)
+ {
+ apr_int64_t base_rev;
- if (!file_name)
- {
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in add-file element"));
- }
+ SVN_ERR(svn_cstring_atoi64(&base_rev,
+ svn_hash_gets(attrs, "rev")));
+ file->base_rev = (svn_revnum_t)base_rev;
+ }
+ else
+ {
+ const char *sha1_checksum;
+ file->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
- info = push_state(parser, ctx, ADD_FILE);
+ if (file->copyfrom_path)
+ {
+ apr_int64_t copyfrom_rev;
+ const char *copyfrom_rev_str;
- info->base_rev = SVN_INVALID_REVNUM;
+ file->copyfrom_path = svn_fspath__canonicalize(
+ file->copyfrom_path,
+ file->pool);
- /* If the server isn't in "send-all" mode, we should expect to
- fetch contents for added files. */
- if (! ctx->send_all_mode)
- info->fetch_file = TRUE;
+ copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
- /* If the server isn't included properties for added items,
- we'll need to fetch them ourselves. */
- if (! ctx->add_props_included)
- info->fetch_props = TRUE;
+ if (!copyfrom_rev_str)
+ return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
+ NULL,
+ _("Missing '%s' attribute"),
+ "copyfrom-rev");
- info->base_name = apr_pstrdup(info->pool, file_name);
- info->name = NULL;
+ SVN_ERR(svn_cstring_atoi64(&copyfrom_rev, copyfrom_rev_str));
- info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
- info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
+ file->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
+ }
- info->final_sha1_checksum =
- svn_xml_get_attr_value("sha1-checksum", attrs);
- if (info->final_sha1_checksum)
- info->final_sha1_checksum = apr_pstrdup(info->pool,
- info->final_sha1_checksum);
- }
- else if ((state == OPEN_DIR || state == ADD_DIR) &&
- strcmp(name.name, "delete-entry") == 0)
- {
- const char *file_name;
- const char *rev_str;
- report_info_t *info;
- apr_pool_t *tmppool;
- const char *full_path;
- svn_revnum_t delete_rev = SVN_INVALID_REVNUM;
+ sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
+ if (sha1_checksum)
+ {
+ SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
+ svn_checksum_sha1,
+ sha1_checksum,
+ file->pool));
+ }
- file_name = svn_xml_get_attr_value("name", attrs);
+ /* If the server isn't in "send-all" mode, we should expect to
+ fetch contents for added files. */
+ if (! ctx->send_all_mode)
+ file->fetch_file = TRUE;
- if (!file_name)
- {
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in delete-entry element"));
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ file->fetch_props = TRUE;
+ }
}
+ break;
- rev_str = svn_xml_get_attr_value("rev", attrs);
- if (rev_str) /* Not available on older repositories! */
- delete_rev = SVN_STR_TO_REV(rev_str);
+ case TXDELTA:
+ {
+ file_baton_t *file = ctx->cur_file;
+ const char *base_checksum;
- info = parser->state->private;
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
+ addition to <fetch-file>s and such) when *not* in
+ "send-all" mode. As a client, we're smart enough to know
+ that's wrong, so we'll just ignore these tags. */
+ if (! ctx->send_all_mode)
+ break;
- SVN_ERR(ensure_dir_opened(info->dir));
+ file->fetch_file = FALSE;
- tmppool = svn_pool_create(info->dir->dir_baton_pool);
+ attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
+ base_checksum = svn_hash_gets(attrs, "base-checksum");
- full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
+ if (base_checksum)
+ SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
+ svn_checksum_md5, base_checksum,
+ file->pool));
- SVN_ERR(ctx->update_editor->delete_entry(full_path,
- delete_rev,
- info->dir->dir_baton,
- tmppool));
+ SVN_ERR(open_file_txdelta(ctx->cur_file, scratch_pool));
- svn_pool_destroy(tmppool);
- }
- else if ((state == OPEN_DIR || state == ADD_DIR) &&
- strcmp(name.name, "absent-directory") == 0)
- {
- const char *file_name;
- report_info_t *info;
+ if (ctx->cur_file->txdelta != svn_delta_noop_window_handler)
+ {
+ svn_stream_t *decoder;
- file_name = svn_xml_get_attr_value("name", attrs);
+ decoder = svn_txdelta_parse_svndiff(file->txdelta,
+ file->txdelta_baton,
+ TRUE /* error early close*/,
+ file->pool);
- if (!file_name)
- {
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in absent-directory element"));
+ file->txdelta_stream = svn_base64_decode(decoder, file->pool);
+ }
}
+ break;
- info = parser->state->private;
+ case FETCH_PROPS:
+ {
+ /* Subversion <= 1.6 servers will return a fetch-props element on
+ open-file and open-dir when non entry props were changed in
+ !send-all mode. In turn we fetch the full set of properties
+ and send all of those as *changes* to the editor. So these
+ editors have to be aware that they receive-non property changes.
+ (In case of incomplete directories they have to be aware anyway)
- SVN_ERR(ensure_dir_opened(info->dir));
+ In r1063337 this behavior was changed in mod_dav_svn to always
+ send property changes inline in these cases. (See issue #3657)
- SVN_ERR(ctx->update_editor->absent_directory(
- svn_relpath_join(info->name, file_name,
- info->dir->pool),
- info->dir->dir_baton,
- info->dir->pool));
+ Note that before that change the property changes to the last_*
+ entry props were already inlined via specific xml elements. */
+ if (ctx->cur_file)
+ ctx->cur_file->fetch_props = TRUE;
+ else if (ctx->cur_dir)
+ ctx->cur_dir->fetch_props = TRUE;
+ }
+ break;
}
- else if ((state == OPEN_DIR || state == ADD_DIR) &&
- strcmp(name.name, "absent-file") == 0)
- {
- const char *file_name;
- report_info_t *info;
- file_name = svn_xml_get_attr_value("name", attrs);
+ return SVN_NO_ERROR;
+}
- if (!file_name)
- {
- return svn_error_create(
- SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in absent-file element"));
- }
- info = parser->state->private;
- SVN_ERR(ensure_dir_opened(info->dir));
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+update_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
- SVN_ERR(ctx->update_editor->absent_file(
- svn_relpath_join(info->name, file_name,
- info->dir->pool),
- info->dir->dir_baton,
- info->dir->pool));
- }
- else if (state == OPEN_DIR || state == ADD_DIR)
+ switch (leaving_state)
{
- report_info_t *info;
-
- if (strcmp(name.name, "checked-in") == 0)
- {
- info = push_state(parser, ctx, IGNORE_PROP_NAME);
- info->prop_ns = name.namespace;
- info->prop_name = apr_pstrdup(parser->state->pool, name.name);
- info->prop_encoding = NULL;
- svn_stringbuf_setempty(info->prop_value);
- }
- else if (strcmp(name.name, "set-prop") == 0 ||
- strcmp(name.name, "remove-prop") == 0)
+ case UPDATE_REPORT:
+ ctx->done = TRUE;
+ break;
+ case TARGET_REVISION:
{
- const char *full_prop_name;
- const char *colon;
-
- info = push_state(parser, ctx, PROP);
-
- full_prop_name = svn_xml_get_attr_value("name", attrs);
- if (!full_prop_name)
- {
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in %s element"),
- name.name);
- }
-
- colon = strchr(full_prop_name, ':');
-
- if (colon)
- colon++;
- else
- colon = full_prop_name;
+ const char *revstr = svn_hash_gets(attrs, "rev");
+ apr_int64_t rev;
- info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
- colon - full_prop_name);
- info->prop_name = apr_pstrdup(parser->state->pool, colon);
- info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
- svn_stringbuf_setempty(info->prop_value);
- }
- else if (strcmp(name.name, "prop") == 0)
- {
- /* need to fetch it. */
- push_state(parser, ctx, NEED_PROP_NAME);
- }
- else if (strcmp(name.name, "fetch-props") == 0)
- {
- info = parser->state->private;
+ SVN_ERR(svn_cstring_atoi64(&rev, revstr));
- info->dir->fetch_props = TRUE;
- }
- else
- {
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Unknown tag '%s' while at state %d"),
- name.name, state);
+ SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
+ (svn_revnum_t)rev,
+ scratch_pool));
}
+ break;
- }
- else if (state == OPEN_FILE || state == ADD_FILE)
- {
- report_info_t *info;
+ case CHECKED_IN_HREF:
+ if (ctx->cur_file)
+ ctx->cur_file->url = apr_pstrdup(ctx->cur_file->pool, cdata->data);
+ else
+ ctx->cur_dir->url = apr_pstrdup(ctx->cur_dir->pool, cdata->data);
+ break;
- if (strcmp(name.name, "checked-in") == 0)
- {
- info = push_state(parser, ctx, IGNORE_PROP_NAME);
- info->prop_ns = name.namespace;
- info->prop_name = apr_pstrdup(parser->state->pool, name.name);
- info->prop_encoding = NULL;
- svn_stringbuf_setempty(info->prop_value);
- }
- else if (strcmp(name.name, "prop") == 0)
- {
- /* need to fetch it. */
- push_state(parser, ctx, NEED_PROP_NAME);
- }
- else if (strcmp(name.name, "fetch-props") == 0)
+ case SET_PROP:
+ case REMOVE_PROP:
{
- info = parser->state->private;
+ const char *name = svn_hash_gets(attrs, "name");
+ const char *encoding;
+ const svn_string_t *value;
- info->fetch_props = TRUE;
- }
- else if (strcmp(name.name, "fetch-file") == 0)
- {
- info = parser->state->private;
- info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs);
+ if (leaving_state == REMOVE_PROP)
+ value = NULL;
+ else if ((encoding = svn_hash_gets(attrs, "encoding")))
+ {
+ if (strcmp(encoding, "base64") != 0)
+ return svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL,
+ _("Got unrecognized encoding '%s'"),
+ encoding);
- if (info->base_checksum)
- info->base_checksum = apr_pstrdup(info->pool, info->base_checksum);
+ value = svn_base64_decode_string(cdata, scratch_pool);
+ }
+ else
+ value = cdata;
- info->final_sha1_checksum =
- svn_xml_get_attr_value("sha1-checksum", attrs);
- if (info->final_sha1_checksum)
- info->final_sha1_checksum = apr_pstrdup(info->pool,
- info->final_sha1_checksum);
+ if (ctx->cur_file)
+ {
+ file_baton_t *file = ctx->cur_file;
- info->fetch_file = TRUE;
- }
- else if (strcmp(name.name, "set-prop") == 0 ||
- strcmp(name.name, "remove-prop") == 0)
- {
- const char *full_prop_name;
- const char *colon;
+ if (value
+ || ctx->add_props_included
+ || SVN_IS_VALID_REVNUM(file->base_rev))
+ {
+ SVN_ERR(ensure_file_opened(file, scratch_pool));
- info = push_state(parser, ctx, PROP);
+ SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
+ name,
+ value,
+ scratch_pool));
+ }
+ else
+ {
+ if (!file->remove_props)
+ file->remove_props = apr_hash_make(file->pool);
- full_prop_name = svn_xml_get_attr_value("name", attrs);
- if (!full_prop_name)
- {
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Missing name attr in %s element"),
- name.name);
+ svn_hash_sets(file->remove_props,
+ apr_pstrdup(file->pool, name),
+ "");
+ }
}
- colon = strchr(full_prop_name, ':');
-
- if (colon)
- colon++;
else
- colon = full_prop_name;
-
- info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
- colon - full_prop_name);
- info->prop_name = apr_pstrdup(parser->state->pool, colon);
- info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
- svn_stringbuf_setempty(info->prop_value);
- }
- else if (strcmp(name.name, "txdelta") == 0)
- {
- /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
- addition to <fetch-file>s and such) when *not* in
- "send-all" mode. As a client, we're smart enough to know
- that's wrong, so we'll just ignore these tags. */
- if (ctx->send_all_mode)
{
- const svn_delta_editor_t *update_editor = ctx->update_editor;
-
- info = push_state(parser, ctx, TXDELTA);
+ dir_baton_t *dir = ctx->cur_dir;
- if (! info->file_baton)
+ if (value
+ || ctx->add_props_included
+ || SVN_IS_VALID_REVNUM(dir->base_rev))
{
- SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ SVN_ERR(ensure_dir_opened(dir, scratch_pool));
+
+ SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
+ name,
+ value,
+ scratch_pool));
}
+ else
+ {
+ if (!dir->remove_props)
+ dir->remove_props = apr_hash_make(dir->pool);
- info->base_checksum = svn_xml_get_attr_value("base-checksum",
- attrs);
- SVN_ERR(update_editor->apply_textdelta(info->file_baton,
- info->base_checksum,
- info->editor_pool,
- &info->textdelta,
- &info->textdelta_baton));
- info->svndiff_decoder = svn_txdelta_parse_svndiff(
- info->textdelta,
- info->textdelta_baton,
- TRUE, info->pool);
- info->base64_decoder = svn_base64_decode(info->svndiff_decoder,
- info->pool);
+ svn_hash_sets(dir->remove_props,
+ apr_pstrdup(dir->pool, name),
+ "");
+ }
}
}
- else
- {
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("Unknown tag '%s' while at state %d"),
- name.name, state);
- }
- }
- else if (state == IGNORE_PROP_NAME)
- {
- report_info_t *info = push_state(parser, ctx, PROP);
- info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
- }
- else if (state == NEED_PROP_NAME)
- {
- report_info_t *info;
-
- info = push_state(parser, ctx, PROP);
-
- info->prop_ns = name.namespace;
- info->prop_name = apr_pstrdup(parser->state->pool, name.name);
- info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
- svn_stringbuf_setempty(info->prop_value);
- }
-
- return SVN_NO_ERROR;
-}
+ break;
-static svn_error_t *
-end_report(svn_ra_serf__xml_parser_t *parser,
- svn_ra_serf__dav_props_t name,
- apr_pool_t *scratch_pool)
-{
- report_context_t *ctx = parser->user_data;
- report_state_e state;
+ case OPEN_DIR:
+ case ADD_DIR:
+ {
+ dir_baton_t *dir = ctx->cur_dir;
+ ctx->cur_dir = ctx->cur_dir->parent_dir;
- state = parser->state->current_state;
+ if (dir->fetch_props && ! dir->url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT response did not "
+ "include the requested checked-in "
+ "value"));
+ }
- if (state == NONE)
- {
- if (strcmp(name.name, "update-report") == 0)
- {
- ctx->report_completed = TRUE;
- }
- else
- {
- /* nothing to close yet. */
- return SVN_NO_ERROR;
+ if (!dir->fetch_props)
+ {
+ SVN_ERR(maybe_close_dir(dir));
+ break; /* dir potentially no longer valid */
+ }
+ else
+ {
+ /* Otherwise, if the server is *not* in "send-all" mode, we
+ are at a point where we can queue up the PROPFIND request */
+ SVN_ERR(fetch_for_dir(dir, scratch_pool));
+ }
}
- }
+ break;
- if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
- (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0))))
- {
- const char *checked_in_url;
- report_info_t *info = parser->state->private;
+ case OPEN_FILE:
+ case ADD_FILE:
+ {
+ file_baton_t *file = ctx->cur_file;
- /* We've now closed this directory; note it. */
- info->dir->tag_closed = TRUE;
+ ctx->cur_file = NULL;
+ /* go fetch info->name from DAV:checked-in */
- /* go fetch info->file_name from DAV:checked-in */
- checked_in_url =
- svn_ra_serf__get_ver_prop(info->dir->props, info->base_name,
- info->base_rev, "DAV:", "checked-in");
+ if ((file->fetch_file || file->fetch_props) && ! file->url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT response did not "
+ "include the requested checked-in "
+ "value"));
+ }
- /* If we were expecting to have the properties and we aren't able to
- * get it, bail.
- */
- if (!checked_in_url &&
- (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
- {
- return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("The REPORT or PROPFIND response did not "
- "include the requested checked-in value"));
+ /* If the server is in "send-all" mode or didn't get further work,
+ we can now close the file */
+ if (! file->fetch_file && ! file->fetch_props)
+ {
+ SVN_ERR(close_file(file, scratch_pool));
+ break; /* file is no longer valid */
+ }
+ else
+ {
+ /* Otherwise, if the server is *not* in "send-all" mode, we
+ should be at a point where we can queue up any auxiliary
+ content-fetching requests. */
+ SVN_ERR(fetch_for_file(file, scratch_pool));
+ }
}
+ break;
- info->dir->url = checked_in_url;
+ case MD5_CHECKSUM:
+ SVN_ERR(svn_checksum_parse_hex(&ctx->cur_file->final_md5_checksum,
+ svn_checksum_md5,
+ cdata->data,
+ ctx->cur_file->pool));
+ break;
- /* At this point, we should have the checked-in href.
- * If needed, create the PROPFIND to retrieve the dir's properties.
- */
- if (info->dir->fetch_props)
- {
- svn_ra_serf__list_t *list_item;
-
- SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler,
- info->dir->props, ctx->sess,
- get_best_connection(ctx),
- info->dir->url,
- ctx->target_rev, "0",
- all_props,
- &ctx->done_dir_propfinds,
- info->dir->pool));
- SVN_ERR_ASSERT(info->dir->propfind_handler);
-
- /* Create a serf request for the PROPFIND. */
- svn_ra_serf__request_create(info->dir->propfind_handler);
-
- ctx->num_active_propfinds++;
-
- list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
- list_item->data = info->dir;
- list_item->next = ctx->active_dir_propfinds;
- ctx->active_dir_propfinds = list_item;
-
- if (ctx->num_active_fetches + ctx->num_active_propfinds
- > REQUEST_COUNT_TO_PAUSE)
- ctx->parser_ctx->paused = TRUE;
- }
- else
+ case FETCH_FILE:
{
- info->dir->propfind_handler = NULL;
- }
+ file_baton_t *file = ctx->cur_file;
+ const char *base_checksum = svn_hash_gets(attrs, "base-checksum");
+ const char *sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
- /* See if this directory (and perhaps even parents of that) can
- be closed now. This is likely to be the case only if we
- didn't need to contact the server for supplemental
- information required to handle any of this directory's
- children. */
- SVN_ERR(maybe_close_dir_chain(info->dir));
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
- {
- report_info_t *info = parser->state->private;
+ if (base_checksum)
+ SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
+ svn_checksum_md5, base_checksum,
+ file->pool));
- /* Expand our full name now if we haven't done so yet. */
- if (!info->name)
- {
- info->name = svn_relpath_join(info->dir->name, info->base_name,
- info->pool);
- }
+ /* Property is duplicated between add-file and fetch-file */
+ if (sha1_checksum && !file->final_sha1_checksum)
+ SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
+ svn_checksum_sha1,
+ sha1_checksum,
+ file->pool));
- if (info->lock_token && !info->fetch_props)
- info->fetch_props = TRUE;
+ /* Some 0.3x mod_dav_svn wrote both txdelta and fetch-file
+ elements in send-all mode. (See neon for history) */
+ if (! ctx->send_all_mode)
+ file->fetch_file = TRUE;
+ }
+ break;
- /* If possible, we'd like to fetch only a delta against a
- * version of the file we already have in our working copy,
- * rather than fetching a fulltext.
- *
- * In HTTP v2, we can simply construct the URL we need given the
- * repos_relpath and base revision number.
- */
- if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
+ case DELETE_ENTRY:
{
- const char *repos_relpath;
+ const char *name = svn_hash_gets(attrs, "name");
+ const char *revstr;
+ apr_int64_t delete_rev;
- /* If this file is switched vs the editor root we should provide
- its real url instead of the one calculated from the session root.
- */
- repos_relpath = svn_hash_gets(ctx->switched_paths, info->name);
+ SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
- if (!repos_relpath)
- {
- if (ctx->root_is_switched)
- {
- /* We are updating a direct target (most likely a file)
- that is switched vs its parent url */
- SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool)
- == '\0');
+ revstr = svn_hash_gets(attrs, "rev");
- repos_relpath = svn_hash_gets(ctx->switched_paths, "");
- }
- else
- repos_relpath = svn_relpath_join(info->dir->repos_relpath,
- info->base_name, info->pool);
- }
-
- info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s",
- ctx->sess->rev_root_stub,
- info->base_rev,
- svn_path_uri_encode(repos_relpath,
- info->pool));
- }
- else if (ctx->sess->wc_callbacks->get_wc_prop)
- {
- /* If we have a WC, we might be able to dive all the way into the WC
- * to get the previous URL so we can do a differential GET with the
- * base URL.
- */
- const svn_string_t *value = NULL;
- SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
- ctx->sess->wc_callback_baton, info->name,
- SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool));
+ if (revstr)
+ SVN_ERR(svn_cstring_atoi64(&delete_rev, revstr));
+ else
+ delete_rev = SVN_INVALID_REVNUM;
- info->delta_base = value ? value->data : NULL;
+ SVN_ERR(ctx->editor->delete_entry(
+ svn_relpath_join(ctx->cur_dir->relpath,
+ name,
+ scratch_pool),
+ (svn_revnum_t)delete_rev,
+ ctx->cur_dir->dir_baton,
+ scratch_pool));
}
+ break;
- /* go fetch info->name from DAV:checked-in */
- info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
- info->base_rev, "DAV:", "checked-in");
- if (!info->url)
+ case ABSENT_DIR:
{
- return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("The REPORT or PROPFIND response did not "
- "include the requested checked-in value"));
- }
+ const char *name = svn_hash_gets(attrs, "name");
- /* If the server is in "send-all" mode, we might have opened the
- file when we started seeing content for it. If we didn't get
- any content for it, we still need to open the file. But in
- any case, we can then immediately close it. */
- if (ctx->send_all_mode)
- {
- if (! info->file_baton)
- {
- SVN_ERR(open_updated_file(info, FALSE, info->pool));
- }
- SVN_ERR(close_updated_file(info, info->pool));
- info->dir->ref_count--;
- }
- /* Otherwise, if the server is *not* in "send-all" mode, we
- should be at a point where we can queue up any auxiliary
- content-fetching requests. */
- else
- {
- SVN_ERR(fetch_file(ctx, info));
- }
-
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
- {
- report_info_t *info = parser->state->private;
-
- /* go fetch info->name from DAV:checked-in */
- info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
- info->base_rev, "DAV:", "checked-in");
- if (!info->url)
- {
- return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
- _("The REPORT or PROPFIND response did not "
- "include the requested checked-in value"));
- }
+ SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
- /* If the server is in "send-all" mode, we might have opened the
- file when we started seeing content for it. If we didn't get
- any content for it, we still need to open the file. But in
- any case, we can then immediately close it. */
- if (ctx->send_all_mode)
- {
- if (! info->file_baton)
- {
- SVN_ERR(open_updated_file(info, FALSE, info->pool));
- }
- SVN_ERR(close_updated_file(info, info->pool));
- info->dir->ref_count--;
+ SVN_ERR(ctx->editor->absent_directory(
+ svn_relpath_join(ctx->cur_dir->relpath,
+ name, scratch_pool),
+ ctx->cur_dir->dir_baton,
+ scratch_pool));
}
- /* Otherwise, if the server is *not* in "send-all" mode, we
- should be at a point where we can queue up any auxiliary
- content-fetching requests. */
- else
+ break;
+ case ABSENT_FILE:
{
- SVN_ERR(fetch_file(ctx, info));
- }
+ const char *name = svn_hash_gets(attrs, "name");
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0)
- {
- report_info_t *info = parser->state->private;
+ SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
- /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
- <fetch-file>s and such) when *not* in "send-all" mode. As a
- client, we're smart enough to know that's wrong, so when not
- in "receiving-all" mode, we'll ignore these tags. */
- if (ctx->send_all_mode)
- {
- SVN_ERR(svn_stream_close(info->base64_decoder));
+ SVN_ERR(ctx->editor->absent_file(
+ svn_relpath_join(ctx->cur_dir->relpath,
+ name, scratch_pool),
+ ctx->cur_dir->dir_baton,
+ scratch_pool));
}
+ break;
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == PROP)
- {
- /* We need to move the prop_ns, prop_name, and prop_value into the
- * same lifetime as the dir->pool.
- */
- svn_ra_serf__ns_t *ns, *ns_name_match;
- svn_boolean_t found = FALSE;
- report_info_t *info;
- report_dir_t *dir;
- apr_hash_t *props;
- const svn_string_t *set_val_str;
- apr_pool_t *pool;
-
- info = parser->state->private;
- dir = info->dir;
-
- /* We're going to be slightly tricky. We don't care what the ->url
- * field is here at this point. So, we're going to stick a single
- * copy of the property name inside of the ->url field.
- */
- ns_name_match = NULL;
- for (ns = dir->ns_list; ns; ns = ns->next)
+ case TXDELTA:
{
- if (strcmp(ns->namespace, info->prop_ns) == 0)
- {
- ns_name_match = ns;
- if (strcmp(ns->url, info->prop_name) == 0)
- {
- found = TRUE;
- break;
- }
- }
- }
+ file_baton_t *file = ctx->cur_file;
- if (!found)
- {
- ns = apr_palloc(dir->pool, sizeof(*ns));
- if (!ns_name_match)
+ if (file->txdelta_stream)
{
- ns->namespace = apr_pstrdup(dir->pool, info->prop_ns);
+ SVN_ERR(svn_stream_close(file->txdelta_stream));
+ file->txdelta_stream = NULL;
}
- else
- {
- ns->namespace = ns_name_match->namespace;
- }
- ns->url = apr_pstrdup(dir->pool, info->prop_name);
-
- ns->next = dir->ns_list;
- dir->ns_list = ns;
}
+ break;
- if (strcmp(name.name, "remove-prop") != 0)
+ case VERSION_NAME:
+ case CREATIONDATE:
+ case CREATOR_DISPLAYNAME:
{
- props = info->props;
- pool = info->pool;
- }
- else
- {
- props = dir->removed_props;
- pool = dir->pool;
- svn_stringbuf_setempty(info->prop_value);
- }
+ /* Subversion <= 1.6 servers would return a fetch-props element on
+ open-file and open-dir when non entry props were changed in
+ !send-all mode. In turn we fetch the full set of properties and
+ send those as *changes* to the editor. So these editors have to
+ be aware that they receive non property changes.
+ (In case of incomplete directories they have to be aware anyway)
- if (info->prop_encoding)
- {
- if (strcmp(info->prop_encoding, "base64") == 0)
- {
- svn_string_t tmp;
+ In that case the last_* entry props are posted as 3 specific xml
+ elements, which we handle here.
- /* Don't use morph_info_string cuz we need prop_value to
- remain usable. */
- tmp.data = info->prop_value->data;
- tmp.len = info->prop_value->len;
+ In r1063337 this behavior was changed in mod_dav_svn to always
+ send property changes inline in these cases. (See issue #3657)
+ */
- set_val_str = svn_base64_decode_string(&tmp, pool);
- }
+ const char *propname;
+
+ if (ctx->cur_file)
+ SVN_ERR(ensure_file_opened(ctx->cur_file, scratch_pool));
+ else if (ctx->cur_dir)
+ SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
else
+ break;
+
+ switch (leaving_state)
{
- return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
- NULL,
- _("Got unrecognized encoding '%s'"),
- info->prop_encoding);
+ case VERSION_NAME:
+ propname = SVN_PROP_ENTRY_COMMITTED_REV;
+ break;
+ case CREATIONDATE:
+ propname = SVN_PROP_ENTRY_COMMITTED_DATE;
+ break;
+ case CREATOR_DISPLAYNAME:
+ propname = SVN_PROP_ENTRY_LAST_AUTHOR;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION(); /* Impossible to reach */
}
- }
- else
- {
- set_val_str = svn_string_create_from_buf(info->prop_value, pool);
- }
-
- svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
- ns->namespace, ns->url, set_val_str, pool);
- /* Advance handling: if we spotted the md5-checksum property on
- the wire, remember it's value. */
- if (strcmp(ns->url, "md5-checksum") == 0
- && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0)
- info->final_checksum = apr_pstrdup(info->pool, set_val_str->data);
-
- svn_ra_serf__xml_pop_state(parser);
- }
- else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
- {
- svn_ra_serf__xml_pop_state(parser);
+ if (ctx->cur_file)
+ SVN_ERR(ctx->editor->change_file_prop(ctx->cur_file->file_baton,
+ propname, cdata,
+ scratch_pool));
+ else
+ SVN_ERR(ctx->editor->change_dir_prop(ctx->cur_dir->dir_baton,
+ propname, cdata,
+ scratch_pool));
+ }
+ break;
}
return SVN_NO_ERROR;
}
+
+/* Conforms to svn_ra_serf__xml_cdata_t */
static svn_error_t *
-cdata_report(svn_ra_serf__xml_parser_t *parser,
+update_cdata(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
const char *data,
apr_size_t len,
apr_pool_t *scratch_pool)
{
- report_context_t *ctx = parser->user_data;
-
- UNUSED_CTX(ctx);
-
- if (parser->state->current_state == PROP)
- {
- report_info_t *info = parser->state->private;
+ report_context_t *ctx = baton;
- svn_stringbuf_appendbytes(info->prop_value, data, len);
- }
- else if (parser->state->current_state == TXDELTA)
+ if (current_state == TXDELTA && ctx->cur_file
+ && ctx->cur_file->txdelta_stream)
{
- /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
- <fetch-file>s and such) when *not* in "send-all" mode. As a
- client, we're smart enough to know that's wrong, so when not
- in "receiving-all" mode, we'll ignore these tags. */
- if (ctx->send_all_mode)
- {
- apr_size_t nlen = len;
- report_info_t *info = parser->state->private;
-
- SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen));
- if (nlen != len)
- {
- /* Short write without associated error? "Can't happen." */
- return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
- _("Error writing to '%s': unexpected EOF"),
- info->name);
- }
- }
+ SVN_ERR(svn_stream_write(ctx->cur_file->txdelta_stream, data, &len));
}
return SVN_NO_ERROR;
@@ -2544,7 +2197,8 @@ make_simple_xml_tag(svn_stringbuf_t **buf_p,
const char *cdata,
apr_pool_t *pool)
{
- svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL);
+ svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname,
+ SVN_VA_NULL);
svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
svn_xml_make_close_tag(buf_p, pool, tagname);
}
@@ -2566,12 +2220,11 @@ set_path(void *report_baton,
"lock-token", lock_token,
"depth", svn_depth_to_word(depth),
"start-empty", start_empty ? "true" : NULL,
- NULL);
+ SVN_VA_NULL);
svn_xml_escape_cdata_cstring(&buf, path, pool);
svn_xml_make_close_tag(&buf, pool, "S:entry");
- SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
- NULL, pool));
+ SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
return SVN_NO_ERROR;
}
@@ -2586,8 +2239,7 @@ delete_path(void *report_baton,
make_simple_xml_tag(&buf, "S:missing", path, pool);
- SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
- NULL, pool));
+ SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
return SVN_NO_ERROR;
}
@@ -2619,12 +2271,10 @@ link_path(void *report_baton,
_("Unable to parse URL '%s'"), url);
}
- SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess,
- NULL, pool));
- SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess,
- NULL, pool));
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, pool));
+ SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, pool));
- link = apr_pstrcat(pool, "/", link, (char *)NULL);
+ link = apr_pstrcat(pool, "/", link, SVN_VA_NULL);
svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
"rev", apr_ltoa(pool, revision),
@@ -2632,80 +2282,55 @@ link_path(void *report_baton,
"depth", svn_depth_to_word(depth),
"linkpath", link,
"start-empty", start_empty ? "true" : NULL,
- NULL);
+ SVN_VA_NULL);
svn_xml_escape_cdata_cstring(&buf, path, pool);
svn_xml_make_close_tag(&buf, pool, "S:entry");
- SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
- NULL, pool));
+ SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
/* Store the switch roots to allow generating repos_relpaths from just
the working copy paths. (Needed for HTTPv2) */
path = apr_pstrdup(report->pool, path);
- svn_hash_sets(report->switched_paths,
- path, apr_pstrdup(report->pool, link + 1));
+ link = apr_pstrdup(report->pool, link + 1);
+ svn_hash_sets(report->switched_paths, path, link);
- if (!*path)
- report->root_is_switched = TRUE;
-
- return APR_SUCCESS;
-}
-
-/** Minimum nr. of outstanding requests needed before a new connection is
- * opened. */
-#define REQS_PER_CONN 8
-
-/** This function creates a new connection for this serf session, but only
- * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
- * only one main connection open.
- */
-static svn_error_t *
-open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
-{
- /* For each REQS_PER_CONN outstanding requests open a new connection, with
- * a minimum of 1 extra connection. */
- if (sess->num_conns == 1 ||
- ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
+ if (!path[0] && report->update_target[0])
{
- int cur = sess->num_conns;
- apr_status_t status;
-
- sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
- sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
- NULL, NULL);
- sess->conns[cur]->last_status_code = -1;
- sess->conns[cur]->session = sess;
- status = serf_connection_create2(&sess->conns[cur]->conn,
- sess->context,
- sess->session_url,
- svn_ra_serf__conn_setup,
- sess->conns[cur],
- svn_ra_serf__conn_closed,
- sess->conns[cur],
- sess->pool);
- if (status)
- return svn_ra_serf__wrap_err(status, NULL);
-
- sess->num_conns++;
+ /* The update root is switched. Make sure we store it the way
+ we expect it to find */
+ svn_hash_sets(report->switched_paths, report->update_target, link);
}
- return SVN_NO_ERROR;
+ return APR_SUCCESS;
}
-/* Serf callback to create update request body bucket. */
+/* Serf callback to create update request body bucket.
+ Implements svn_ra_serf__request_body_delegate_t */
static svn_error_t *
create_update_report_body(serf_bucket_t **body_bkt,
void *baton,
serf_bucket_alloc_t *alloc,
- apr_pool_t *pool)
+ apr_pool_t *pool /* request pool */,
+ apr_pool_t *scratch_pool)
{
report_context_t *report = baton;
- apr_off_t offset;
+ body_create_baton_t *body = report->body;
- offset = 0;
- apr_file_seek(report->body_file, APR_SET, &offset);
+ if (body->file)
+ {
+ apr_off_t offset;
+
+ offset = 0;
+ SVN_ERR(svn_io_file_seek(body->file, APR_SET, &offset, pool));
- *body_bkt = serf_bucket_file_create(report->body_file, alloc);
+ *body_bkt = serf_bucket_file_create(report->body->file, alloc);
+ }
+ else
+ {
+ *body_bkt = serf_bucket_simple_create(body->all_data,
+ body->total_bytes,
+ NULL, NULL, alloc);
+ }
return SVN_NO_ERROR;
}
@@ -2714,7 +2339,8 @@ create_update_report_body(serf_bucket_t **body_bkt,
static svn_error_t *
setup_update_report_headers(serf_bucket_t *headers,
void *baton,
- apr_pool_t *pool)
+ apr_pool_t *pool /* request pool */,
+ apr_pool_t *scratch_pool)
{
report_context_t *report = baton;
@@ -2732,368 +2358,347 @@ setup_update_report_headers(serf_bucket_t *headers,
return SVN_NO_ERROR;
}
+/* Baton for update_delay_handler */
+typedef struct update_delay_baton_t
+{
+ report_context_t *report;
+ svn_spillbuf_t *spillbuf;
+ svn_ra_serf__response_handler_t inner_handler;
+ void *inner_handler_baton;
+} update_delay_baton_t;
+
+/* Helper for update_delay_handler() and process_pending() to
+ call UDB->INNER_HANDLER with buffer pointed by DATA. */
static svn_error_t *
-finish_report(void *report_baton,
- apr_pool_t *pool)
+process_buffer(update_delay_baton_t *udb,
+ serf_request_t *request,
+ const void *data,
+ apr_size_t len,
+ svn_boolean_t at_eof,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
{
- report_context_t *report = report_baton;
- svn_ra_serf__session_t *sess = report->sess;
- svn_ra_serf__handler_t *handler;
- svn_ra_serf__xml_parser_t *parser_ctx;
- const char *report_target;
- svn_stringbuf_t *buf = NULL;
- apr_pool_t *iterpool = svn_pool_create(pool);
+ serf_bucket_t *tmp_bucket;
svn_error_t *err;
- apr_interval_time_t waittime_left = sess->timeout;
- svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
- SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
- NULL, iterpool));
+ /* ### This code (and the eagain bucket code) can probably be
+ ### simplified by using a bit of aggregate bucket magic.
+ ### See mail from Ivan to dev@s.a.o. */
+ if (at_eof)
+ {
+ tmp_bucket = serf_bucket_simple_create(data, len, NULL, NULL,
+ alloc);
+ }
+ else
+ {
+ tmp_bucket = svn_ra_serf__create_bucket_with_eagain(data, len,
+ alloc);
+ }
- /* We need to flush the file, make it unbuffered (so that it can be
- * zero-copied via mmap), and reset the position before attempting to
- * deliver the file.
- *
- * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
- * and zero-copy the PUT body. However, on older APR versions, we can't
- * check the buffer status; but serf will fall through and create a file
- * bucket for us on the buffered svndiff handle.
- */
- apr_file_flush(report->body_file);
-#if APR_VERSION_AT_LEAST(1, 3, 0)
- apr_file_buffer_set(report->body_file, NULL, 0);
-#endif
+ /* If not at EOF create a bucket that finishes with EAGAIN, otherwise
+ use a standard bucket with default EOF handling */
+ err = udb->inner_handler(request, tmp_bucket,
+ udb->inner_handler_baton, pool);
- SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool));
+ /* And free the bucket explicitly to avoid growing request allocator
+ storage (in a loop) */
+ serf_bucket_destroy(tmp_bucket);
- /* create and deliver request */
- report->path = report_target;
+ return svn_error_trace(err);
+}
- handler = apr_pcalloc(pool, sizeof(*handler));
- handler->handler_pool = pool;
- handler->method = "REPORT";
- handler->path = report->path;
- handler->body_delegate = create_update_report_body;
- handler->body_delegate_baton = report;
- handler->body_type = "text/xml";
- handler->custom_accept_encoding = TRUE;
- handler->header_delegate = setup_update_report_headers;
- handler->header_delegate_baton = report;
- handler->conn = sess->conns[0];
- handler->session = sess;
+/* Delaying wrapping reponse handler, to avoid creating too many
+ requests to deliver efficiently */
+static svn_error_t *
+update_delay_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *scratch_pool)
+{
+ update_delay_baton_t *udb = handler_baton;
+ apr_status_t status;
+ apr_pool_t *iterpool = NULL;
- parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+ if (! udb->spillbuf)
+ {
+ if (udb->report->send_all_mode)
+ {
+ /* Easy out... We only have one request, so avoid everything and just
+ call the inner handler.
- parser_ctx->pool = pool;
- parser_ctx->response_type = "update-report";
- parser_ctx->user_data = report;
- parser_ctx->start = start_report;
- parser_ctx->end = end_report;
- parser_ctx->cdata = cdata_report;
- parser_ctx->done = &report->done;
+ We will always get in the loop (below) on the first chunk, as only
+ the server can get us in true send-all mode */
- handler->response_handler = svn_ra_serf__handle_xml_parser;
- handler->response_baton = parser_ctx;
+ return svn_error_trace(udb->inner_handler(request, response,
+ udb->inner_handler_baton,
+ scratch_pool));
+ }
- report->parser_ctx = parser_ctx;
+ while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
+ < REQUEST_COUNT_TO_RESUME)
+ {
+ const char *data;
+ apr_size_t len;
+ svn_boolean_t at_eof = FALSE;
+ svn_error_t *err;
- svn_ra_serf__request_create(handler);
+ status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return svn_ra_serf__wrap_err(status, NULL);
+ else if (APR_STATUS_IS_EOF(status))
+ udb->report->report_received = at_eof = TRUE;
- /* Open the first extra connection. */
- SVN_ERR(open_connection_if_needed(sess, 0));
+ if (!iterpool)
+ iterpool = svn_pool_create(scratch_pool);
+ else
+ svn_pool_clear(iterpool);
- sess->cur_conn = 1;
+ if (len == 0 && !at_eof)
+ return svn_ra_serf__wrap_err(status, NULL);
- /* Note that we may have no active GET or PROPFIND requests, yet the
- processing has not been completed. This could be from a delay on the
- network or because we've spooled the entire response into our "pending"
- content of the XML parser. The DONE flag will get set when all the
- XML content has been received *and* parsed. */
- while (!report->done
- || report->num_active_fetches
- || report->num_active_propfinds)
+ err = process_buffer(udb, request, data, len, at_eof,
+ serf_request_get_alloc(request),
+ iterpool);
+
+ if (err && SERF_BUCKET_READ_ERROR(err->apr_err))
+ return svn_error_trace(err);
+ else if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
+ {
+ svn_error_clear(err); /* Throttling is working ok */
+ }
+ else if (err && (APR_STATUS_IS_EOF(err->apr_err)))
+ {
+ svn_pool_destroy(iterpool);
+ return svn_error_trace(err); /* No buffering was necessary */
+ }
+ else
+ {
+ /* SERF_ERROR_WAIT_CONN should be impossible? */
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Let's start using the spill infrastructure */
+ udb->spillbuf = svn_spillbuf__create(SPILLBUF_BLOCKSIZE,
+ SPILLBUF_MAXBUFFSIZE,
+ udb->report->pool);
+ }
+
+ /* Read everything we can to a spillbuffer */
+ do
{
- apr_pool_t *iterpool_inner;
- svn_ra_serf__list_t *done_list;
- int i;
- apr_status_t status;
+ const char *data;
+ apr_size_t len;
- /* Note: this throws out the old ITERPOOL_INNER. */
- svn_pool_clear(iterpool);
+ /* ### What blocksize should we pass? */
+ status = serf_bucket_read(response, 8*PARSE_CHUNK_SIZE, &data, &len);
- if (sess->cancel_func)
- SVN_ERR(sess->cancel_func(sess->cancel_baton));
+ if (!SERF_BUCKET_READ_ERROR(status))
+ SVN_ERR(svn_spillbuf__write(udb->spillbuf, data, len, scratch_pool));
+ }
+ while (status == APR_SUCCESS);
- /* We need to be careful between the outer and inner ITERPOOLs,
- and what items are allocated within. */
- iterpool_inner = svn_pool_create(iterpool);
+ if (APR_STATUS_IS_EOF(status))
+ udb->report->report_received = TRUE;
- status = serf_context_run(sess->context,
- SVN_RA_SERF__CONTEXT_RUN_DURATION,
- iterpool_inner);
+ /* We handle feeding the data from the main context loop, which will be right
+ after processing the pending data */
- err = sess->pending_error;
- sess->pending_error = SVN_NO_ERROR;
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+ else
+ return SVN_NO_ERROR;
+}
+
+/* Process pending data from the update report, if any */
+static svn_error_t *
+process_pending(update_delay_baton_t *udb,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = NULL;
+ serf_bucket_alloc_t *alloc = NULL;
+
+ while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
+ < REQUEST_COUNT_TO_RESUME)
+ {
+ const char *data;
+ apr_size_t len;
+ svn_boolean_t at_eof;
+ svn_error_t *err;
- if (!err && handler->done && handler->server_error)
+ if (!iterpool)
{
- err = handler->server_error->error;
+ iterpool = svn_pool_create(scratch_pool);
+ alloc = serf_bucket_allocator_create(scratch_pool, NULL, NULL);
}
+ else
+ svn_pool_clear(iterpool);
- /* If the context duration timeout is up, we'll subtract that
- duration from the total time alloted for such things. If
- there's no time left, we fail with a message indicating that
- the connection timed out. */
- if (APR_STATUS_IS_TIMEUP(status))
- {
- svn_error_clear(err);
- err = SVN_NO_ERROR;
- status = 0;
+ SVN_ERR(svn_spillbuf__read(&data, &len, udb->spillbuf, iterpool));
- if (sess->timeout)
- {
- if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
- {
- waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
- }
- else
- {
- return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
- _("Connection timed out"));
- }
- }
- }
+ if (data == NULL && !udb->report->report_received)
+ break;
+ else if (data == NULL)
+ at_eof = TRUE;
else
- {
- waittime_left = sess->timeout;
- }
+ at_eof = FALSE;
+
+ err = process_buffer(udb, NULL /* allowed? */, data, len,
+ at_eof, alloc, iterpool);
- if (status && handler->sline.code != 200)
+ if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
{
- return svn_error_trace(
- svn_error_compose_create(
- svn_ra_serf__error_on_status(handler->sline,
- handler->path,
- handler->location),
- err));
+ svn_error_clear(err); /* Throttling is working */
}
- SVN_ERR(err);
- if (status)
+ else if (err && APR_STATUS_IS_EOF(err->apr_err))
{
- return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
+ svn_error_clear(err);
+
+ svn_pool_destroy(iterpool);
+ udb->spillbuf = NULL;
+ return SVN_NO_ERROR;
}
+ else if (err)
+ return svn_error_trace(err);
+ }
- /* Open extra connections if we have enough requests to send. */
- if (sess->num_conns < sess->max_connections)
- SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches +
- report->num_active_propfinds));
+ if (iterpool)
+ svn_pool_destroy(iterpool);
- /* Prune completed file PROPFINDs. */
- done_list = report->done_propfinds;
- while (done_list)
- {
- svn_ra_serf__list_t *next_done = done_list->next;
+ return SVN_NO_ERROR;
+}
- svn_pool_clear(iterpool_inner);
+/* Process the 'update' editor report */
+static svn_error_t *
+process_editor_report(report_context_t *ctx,
+ svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *sess = ctx->sess;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_interval_time_t waittime_left = sess->timeout;
+ update_delay_baton_t *ud;
- report->num_active_propfinds--;
+ /* Now wrap the response handler with delay support to avoid sending
+ out too many requests at once */
+ ud = apr_pcalloc(scratch_pool, sizeof(*ud));
+ ud->report = ctx;
- /* If we have some files that we won't be fetching the content
- * for, ensure that we update the file with any altered props.
- */
- if (report->file_propchanges_only)
- {
- svn_ra_serf__list_t *cur, *prev;
+ ud->inner_handler = handler->response_handler;
+ ud->inner_handler_baton = handler->response_baton;
- prev = NULL;
- cur = report->file_propchanges_only;
+ handler->response_handler = update_delay_handler;
+ handler->response_baton = ud;
- while (cur)
- {
- report_info_t *item = cur->data;
+ /* Open the first extra connection. */
+ SVN_ERR(open_connection_if_needed(sess, 0));
- if (item->propfind_handler == done_list->data)
- {
- break;
- }
+ sess->cur_conn = 1;
- prev = cur;
- cur = cur->next;
- }
+ /* Note that we may have no active GET or PROPFIND requests, yet the
+ processing has not been completed. This could be from a delay on the
+ network or because we've spooled the entire response into our "pending"
+ content of the XML parser. The DONE flag will get set when all the
+ XML content has been received *and* parsed. */
+ while (!handler->done
+ || ctx->num_active_fetches
+ || ctx->num_active_propfinds
+ || !ctx->done)
+ {
+ svn_error_t *err;
+ int i;
- /* If we found a match, set the new props and remove this
- * propchange from our list.
- */
- if (cur)
- {
- report_info_t *info = cur->data;
-
- if (!prev)
- {
- report->file_propchanges_only = cur->next;
- }
- else
- {
- prev->next = cur->next;
- }
-
- /* If we've got cached file content for this file,
- take care of the locally collected properties and
- file content at once. Otherwise, just deal with
- the collected properties.
-
- NOTE: These functions below could delete
- info->dir->pool (via maybe_close_dir_chain()),
- from which is allocated the list item in
- report->file_propchanges_only.
- */
- if (info->cached_contents)
- {
- SVN_ERR(handle_local_content(info, iterpool_inner));
- }
- else
- {
- SVN_ERR(handle_propchange_only(info, iterpool_inner));
- }
- }
- }
+ svn_pool_clear(iterpool);
- done_list = next_done;
- }
- report->done_propfinds = NULL;
+ err = svn_ra_serf__context_run(sess, &waittime_left, iterpool);
- /* Prune completed fetches from our list. */
- done_list = report->done_fetches;
- while (done_list)
+ if (handler->done && handler->server_error)
{
- report_fetch_t *done_fetch = done_list->data;
- svn_ra_serf__list_t *next_done = done_list->next;
- report_dir_t *cur_dir;
-
- /* Decrease the refcount in the parent directory of the file
- whose fetch has completed. */
- cur_dir = done_fetch->info->dir;
- cur_dir->ref_count--;
+ svn_error_clear(err);
+ err = svn_ra_serf__server_error_create(handler, iterpool);
- /* Decrement our active fetch count. */
- report->num_active_fetches--;
+ SVN_ERR_ASSERT(err != NULL);
+ }
- /* See if the parent directory of this fetched item (and
- perhaps even parents of that) can be closed now.
+ SVN_ERR(err);
- NOTE: This could delete cur_dir->pool, from which is
- allocated the list item in report->done_fetches.
- */
- SVN_ERR(maybe_close_dir_chain(cur_dir));
+ /* If there is pending REPORT data, process it now. */
+ if (ud->spillbuf)
+ SVN_ERR(process_pending(ud, iterpool));
- done_list = next_done;
+ /* Debugging purposes only! */
+ for (i = 0; i < sess->num_conns; i++)
+ {
+ serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
}
- report->done_fetches = NULL;
+ }
- /* Prune completed directory PROPFINDs. */
- done_list = report->done_dir_propfinds;
- while (done_list)
- {
- svn_ra_serf__list_t *next_done = done_list->next;
+ svn_pool_clear(iterpool);
- report->num_active_propfinds--;
+ /* If we got a complete report, close the edit. Otherwise, abort it. */
+ if (ctx->done)
+ SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, iterpool));
+ else
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing update-report close tag"));
- if (report->active_dir_propfinds)
- {
- svn_ra_serf__list_t *cur, *prev;
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
- prev = NULL;
- cur = report->active_dir_propfinds;
+static svn_error_t *
+finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ svn_ra_serf__session_t *sess = report->sess;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *report_target;
+ svn_stringbuf_t *buf = NULL;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ svn_error_t *err;
- while (cur)
- {
- report_dir_t *item = cur->data;
+ svn_xml_make_close_tag(&buf, scratch_pool, "S:update-report");
+ SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
+ SVN_ERR(svn_stream_close(report->body_template));
- if (item->propfind_handler == done_list->data)
- {
- break;
- }
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, scratch_pool));
- prev = cur;
- cur = cur->next;
- }
- SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */
+ xmlctx = svn_ra_serf__xml_context_create(update_ttable,
+ update_opened, update_closed,
+ update_cdata,
+ report,
+ scratch_pool);
+ handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL,
+ scratch_pool);
- /* If we found a match, set the new props and remove this
- * propchange from our list.
- */
- if (cur)
- {
- report_dir_t *cur_dir = cur->data;
-
- if (!prev)
- {
- report->active_dir_propfinds = cur->next;
- }
- else
- {
- prev->next = cur->next;
- }
-
- /* See if this directory (and perhaps even parents of that)
- can be closed now.
-
- NOTE: This could delete cur_dir->pool, from which is
- allocated the list item in report->active_dir_propfinds.
- */
- SVN_ERR(maybe_close_dir_chain(cur_dir));
- }
- }
+ handler->method = "REPORT";
+ handler->path = report_target;
+ handler->body_delegate = create_update_report_body;
+ handler->body_delegate_baton = report;
+ handler->body_type = "text/xml";
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = setup_update_report_headers;
+ handler->header_delegate_baton = report;
- done_list = next_done;
- }
- report->done_dir_propfinds = NULL;
-
- /* If the parser is paused, and the number of active requests has
- dropped far enough, then resume parsing. */
- if (parser_ctx->paused
- && (report->num_active_fetches + report->num_active_propfinds
- < REQUEST_COUNT_TO_RESUME))
- parser_ctx->paused = FALSE;
-
- /* If we have not paused the parser and it looks like data MAY be
- present (we can't know for sure because of the private structure),
- then go process the pending content. */
- if (!parser_ctx->paused && parser_ctx->pending != NULL)
- SVN_ERR(svn_ra_serf__process_pending(parser_ctx,
- &report->report_received,
- iterpool_inner));
+ svn_ra_serf__request_create(handler);
- /* Debugging purposes only! */
- for (i = 0; i < sess->num_conns; i++)
- {
- serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
- }
- }
+ err = process_editor_report(report, handler, scratch_pool);
- /* If we got a complete report, close the edit. Otherwise, abort it. */
- if (report->report_completed)
+ if (err)
{
- /* Ensure that we opened and closed our root dir and that we closed
- * all of our children. */
- if (!report->closed_root && report->root_dir != NULL)
- {
- SVN_ERR(close_all_dirs(report->root_dir));
- }
-
- err = report->update_editor->close_edit(report->update_baton, iterpool);
+ err = svn_error_trace(err);
+ err = svn_error_compose_create(
+ err,
+ svn_error_trace(
+ report->editor->abort_edit(report->editor_baton,
+ scratch_pool)));
}
- else
- {
- /* Tell the editor that something failed */
- err = report->update_editor->abort_edit(report->update_baton, iterpool);
- err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
- _("Missing update-report close tag"));
- }
+ svn_pool_destroy(scratch_pool);
- svn_pool_destroy(iterpool);
return svn_error_trace(err);
}
@@ -3162,7 +2767,7 @@ make_update_reporter(svn_ra_session_t *ra_session,
update_editor,
update_baton,
depth, has_target,
- sess->pool));
+ result_pool));
update_editor = filter_editor;
update_baton = filter_baton;
}
@@ -3170,7 +2775,6 @@ make_update_reporter(svn_ra_session_t *ra_session,
report = apr_pcalloc(result_pool, sizeof(*report));
report->pool = result_pool;
report->sess = sess;
- report->conn = report->sess->conns[0];
report->target_rev = revision;
report->ignore_ancestry = ignore_ancestry;
report->send_copyfrom_args = send_copyfrom_args;
@@ -3181,16 +2785,18 @@ make_update_reporter(svn_ra_session_t *ra_session,
report->destination = dest_path;
report->update_target = update_target;
- report->update_editor = update_editor;
- report->update_baton = update_baton;
+ report->editor = update_editor;
+ report->editor_baton = update_baton;
report->done = FALSE;
*reporter = &ra_serf_reporter;
*report_baton = report;
- SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL,
- svn_io_file_del_on_pool_cleanup,
- report->pool, scratch_pool));
+ report->body = apr_pcalloc(report->pool, sizeof(*report->body));
+ report->body->result_pool = report->pool;
+ report->body_template = svn_stream_create(report->body, report->pool);
+ svn_stream_set_write(report->body_template, body_write_fn);
+ svn_stream_set_close(report->body_template, body_done_fn);
if (sess->bulk_updates == svn_tristate_true)
{
@@ -3231,6 +2837,14 @@ make_update_reporter(svn_ra_session_t *ra_session,
supports inlining properties in update editor report. */
if (sess->supports_inline_props)
{
+ /* NOTE: both inlined properties and server->allows_bulk_update
+ (flag SVN_DAV_ALLOW_BULK_UPDATES) were added in 1.8.0, so
+ this code is never reached with a released version of
+ mod_dav_svn.
+
+ Basically by default a 1.8.0 client connecting to a 1.7.x or
+ older server will always use bulk updates. */
+
/* Inline props supported: do not use bulk updates. */
use_bulk_updates = FALSE;
}
@@ -3248,14 +2862,14 @@ make_update_reporter(svn_ra_session_t *ra_session,
svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
"S:update-report",
"xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
- NULL);
+ SVN_VA_NULL);
}
else
{
svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
"S:update-report",
"xmlns:S", SVN_XML_NAMESPACE,
- NULL);
+ SVN_VA_NULL);
/* Subversion 1.8+ servers can be told to send properties for newly
added items inline even when doing a skelta response. */
make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
@@ -3315,8 +2929,7 @@ make_update_reporter(svn_ra_session_t *ra_session,
make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
- SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
- NULL, scratch_pool));
+ SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
return SVN_NO_ERROR;
}
@@ -3367,7 +2980,8 @@ svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
revision,
session->session_url.path, versus_url, diff_target,
- depth, ignore_ancestry, text_deltas, FALSE,
+ depth, ignore_ancestry, text_deltas,
+ FALSE /* send_copyfrom */,
diff_editor, diff_baton,
pool, scratch_pool));
svn_pool_destroy(scratch_pool);
@@ -3426,195 +3040,3 @@ svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
switch_editor, switch_baton,
result_pool, scratch_pool);
}
-
-/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
- * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
- * present in PROPS.
- *
- * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
- *
- * Performs all temporary allocations in POOL.
- */
-static svn_error_t *
-try_get_wc_contents(svn_boolean_t *found_p,
- svn_ra_serf__session_t *session,
- apr_hash_t *props,
- svn_stream_t *dst_stream,
- apr_pool_t *pool)
-{
- apr_hash_t *svn_props;
- const char *sha1_checksum_prop;
- svn_checksum_t *checksum;
- svn_stream_t *wc_stream;
- svn_error_t *err;
-
- /* No contents found by default. */
- *found_p = FALSE;
-
- if (!session->wc_callbacks->get_wc_contents)
- {
- /* No callback, nothing to do. */
- return SVN_NO_ERROR;
- }
-
-
- svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
- if (!svn_props)
- {
- /* No properties -- therefore no checksum property -- in response. */
- return SVN_NO_ERROR;
- }
-
- sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum");
- if (sha1_checksum_prop == NULL)
- {
- /* No checksum property in response. */
- return SVN_NO_ERROR;
- }
-
- SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
- sha1_checksum_prop, pool));
-
- err = session->wc_callbacks->get_wc_contents(
- session->wc_callback_baton, &wc_stream, checksum, pool);
-
- if (err)
- {
- svn_error_clear(err);
-
- /* Ignore errors for now. */
- return SVN_NO_ERROR;
- }
-
- if (wc_stream)
- {
- SVN_ERR(svn_stream_copy3(wc_stream,
- svn_stream_disown(dst_stream, pool),
- NULL, NULL, pool));
- *found_p = TRUE;
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_ra_serf__get_file(svn_ra_session_t *ra_session,
- const char *path,
- svn_revnum_t revision,
- svn_stream_t *stream,
- svn_revnum_t *fetched_rev,
- apr_hash_t **props,
- apr_pool_t *pool)
-{
- svn_ra_serf__session_t *session = ra_session->priv;
- svn_ra_serf__connection_t *conn;
- const char *fetch_url;
- apr_hash_t *fetch_props;
- svn_node_kind_t res_kind;
- const svn_ra_serf__dav_props_t *which_props;
-
- /* What connection should we go on? */
- conn = session->conns[session->cur_conn];
-
- /* Fetch properties. */
-
- fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
-
- /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
- *
- * Otherwise, we need to get the baseline version for this particular
- * revision and then fetch that file.
- */
- if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
- {
- SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
- session, conn,
- fetch_url, revision,
- pool, pool));
- revision = SVN_INVALID_REVNUM;
- }
- /* REVISION is always SVN_INVALID_REVNUM */
- SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
-
- if (props)
- {
- which_props = all_props;
- }
- else if (stream && session->wc_callbacks->get_wc_contents)
- {
- which_props = type_and_checksum_props;
- }
- else
- {
- which_props = check_path_props;
- }
-
- SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url,
- SVN_INVALID_REVNUM,
- which_props,
- pool, pool));
-
- /* Verify that resource type is not collection. */
- SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props));
- if (res_kind != svn_node_file)
- {
- return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
- _("Can't get text contents of a directory"));
- }
-
- /* TODO Filter out all of our props into a usable format. */
- if (props)
- {
- /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
- ### put them into POOL, so we're okay. */
- SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props,
- pool, pool));
- }
-
- if (stream)
- {
- svn_boolean_t found;
- SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool));
-
- /* No contents found in the WC, let's fetch from server. */
- if (!found)
- {
- report_fetch_t *stream_ctx;
- svn_ra_serf__handler_t *handler;
-
- /* Create the fetch context. */
- stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
- stream_ctx->target_stream = stream;
- stream_ctx->sess = session;
- stream_ctx->conn = conn;
- stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
- stream_ctx->info->name = fetch_url;
-
- handler = apr_pcalloc(pool, sizeof(*handler));
-
- handler->handler_pool = pool;
- handler->method = "GET";
- handler->path = fetch_url;
- handler->conn = conn;
- handler->session = session;
-
- handler->custom_accept_encoding = TRUE;
- handler->header_delegate = headers_fetch;
- handler->header_delegate_baton = stream_ctx;
-
- handler->response_handler = handle_stream;
- handler->response_baton = stream_ctx;
-
- handler->response_error = cancel_fetch;
- handler->response_error_baton = stream_ctx;
-
- stream_ctx->handler = handler;
-
- svn_ra_serf__request_create(handler);
-
- SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
- }
- }
-
- return SVN_NO_ERROR;
-}
OpenPOWER on IntegriCloud