| /* |
| * fetch.c : routines for fetching updates and checkouts |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2002 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> /* for strcmp() */ |
| |
| #include <apr_pools.h> |
| #include <apr_tables.h> |
| #include <apr_strings.h> |
| #include <apr_portable.h> |
| |
| #include <ne_basic.h> |
| #include <ne_utils.h> |
| #include <ne_basic.h> |
| #include <ne_207.h> |
| #include <ne_props.h> |
| #include <ne_xml.h> |
| #include <ne_request.h> |
| |
| #include "svn_error.h" |
| #include "svn_pools.h" |
| #include "svn_delta.h" |
| #include "svn_io.h" |
| #include "svn_ra.h" |
| #include "svn_path.h" |
| #include "svn_xml.h" |
| #include "svn_dav.h" |
| |
| #include "ra_dav.h" |
| |
| |
| #define CHKERR(e) \ |
| do { \ |
| if ((rb->err = (e)) != NULL) \ |
| return 1; \ |
| } while(0) |
| |
| typedef struct { |
| /* the information for this subdir. if rsrc==NULL, then this is a sentinel |
| record in fetch_ctx_t.subdirs to close the directory implied by the |
| parent_baton member. */ |
| svn_ra_dav_resource_t *rsrc; |
| |
| /* the directory containing this subdirectory. */ |
| void *parent_baton; |
| |
| } subdir_t; |
| |
| typedef struct { |
| apr_pool_t *pool; |
| |
| /* these two are the handler that the editor gave us */ |
| svn_txdelta_window_handler_t handler; |
| void *handler_baton; |
| |
| /* if we're receiving an svndiff, this is a parser which places the |
| resulting windows into the above handler/baton. */ |
| svn_stream_t *stream; |
| |
| } file_read_ctx_t; |
| |
| typedef struct { |
| svn_error_t *err; /* propagate an error out of the reader */ |
| |
| int checked_type; /* have we processed ctype yet? */ |
| ne_content_type ctype; /* the Content-Type header */ |
| |
| void *subctx; |
| } custom_get_ctx_t; |
| |
| #define POP_SUBDIR(sds) (((subdir_t **)(sds)->elts)[--(sds)->nelts]) |
| #define PUSH_SUBDIR(sds,s) (*(subdir_t **)apr_array_push(sds) = (s)) |
| |
| /* setting properties requires svn_stringbuf_t; this helps out */ |
| typedef struct { |
| svn_stringbuf_t *name; |
| svn_stringbuf_t *value; |
| } vsn_url_helper; |
| |
| typedef svn_error_t * (*prop_setter_t)(void *baton, |
| svn_stringbuf_t *name, |
| svn_stringbuf_t *value); |
| |
| typedef struct { |
| /* The baton returned by the editor's open_root/open_dir */ |
| void *baton; |
| |
| /* Should we fetch properties for this directory when the close tag |
| is found? */ |
| svn_boolean_t fetch_props; |
| |
| /* The version resource URL for this directory. */ |
| const char *vsn_url; |
| |
| /* A buffer which stores the relative directory name. We also use this |
| for temporary construction relative file names. */ |
| svn_stringbuf_t *pathbuf; |
| |
| } dir_item_t; |
| |
| typedef struct { |
| svn_ra_session_t *ras; |
| |
| apr_file_t *tmpfile; |
| |
| svn_boolean_t fetch_content; |
| svn_boolean_t fetch_props; |
| |
| const svn_delta_edit_fns_t *editor; |
| void *edit_baton; |
| |
| apr_array_header_t *dirs; /* stack of directory batons/vsn_urls */ |
| #define TOP_DIR(rb) (((dir_item_t *)(rb)->dirs->elts)[(rb)->dirs->nelts - 1]) |
| #define PUSH_BATON(rb,b) (*(void **)apr_array_push((rb)->dirs) = (b)) |
| |
| void *file_baton; |
| svn_stringbuf_t *namestr; |
| svn_stringbuf_t *cpathstr; |
| svn_stringbuf_t *href; |
| |
| vsn_url_helper vuh; |
| |
| svn_error_t *err; |
| |
| } report_baton_t; |
| |
| static const char report_head[] = "<S:update-report xmlns:S=\"" |
| SVN_XML_NAMESPACE |
| "\">" DEBUG_CR; |
| static const char report_tail[] = "</S:update-report>" DEBUG_CR; |
| |
| static const struct ne_xml_elm report_elements[] = |
| { |
| { SVN_XML_NAMESPACE, "update-report", ELEM_update_report, 0 }, |
| { SVN_XML_NAMESPACE, "target-revision", ELEM_target_revision, 0 }, |
| { SVN_XML_NAMESPACE, "open-directory", ELEM_open_directory, 0 }, |
| /* ### Sat 24 Nov 2001: after all clients have upgraded, change the |
| "replace-" elements here to "open-" and upgrade the server. -kff */ |
| { SVN_XML_NAMESPACE, "replace-directory", ELEM_open_directory, 0 }, |
| { SVN_XML_NAMESPACE, "add-directory", ELEM_add_directory, 0 }, |
| { SVN_XML_NAMESPACE, "open-file", ELEM_open_file, 0 }, |
| { SVN_XML_NAMESPACE, "replace-file", ELEM_open_file, 0 }, |
| { SVN_XML_NAMESPACE, "add-file", ELEM_add_file, 0 }, |
| { SVN_XML_NAMESPACE, "delete-entry", ELEM_delete_entry, 0 }, |
| { SVN_XML_NAMESPACE, "fetch-props", ELEM_fetch_props, 0 }, |
| { SVN_XML_NAMESPACE, "remove-prop", ELEM_remove_prop, 0 }, |
| { SVN_XML_NAMESPACE, "fetch-file", ELEM_fetch_file, 0 }, |
| { SVN_XML_NAMESPACE, "prop", ELEM_prop, 0 }, |
| |
| { "DAV:", "version-name", ELEM_version_name, NE_XML_CDATA }, |
| { "DAV:", "creationdate", ELEM_creationdate, NE_XML_CDATA }, |
| { "DAV:", "creator-displayname", ELEM_creator_displayname, NE_XML_CDATA }, |
| |
| { "DAV:", "checked-in", ELEM_checked_in, 0 }, |
| { "DAV:", "href", NE_ELM_href, NE_XML_CDATA }, |
| |
| { NULL } |
| }; |
| |
| |
| static svn_stringbuf_t *my_basename(const char *url, apr_pool_t *pool) |
| { |
| /* ### be nice to lose the stringbuf portion */ |
| return svn_stringbuf_create(svn_path_basename(url, pool), pool); |
| } |
| |
| /* ### fold this function into store_vsn_url; not really needed */ |
| static const char *get_vsn_url(const svn_ra_dav_resource_t *rsrc) |
| { |
| return apr_hash_get(rsrc->propset, |
| SVN_RA_DAV__PROP_CHECKED_IN, APR_HASH_KEY_STRING); |
| } |
| |
| static svn_error_t *simple_store_vsn_url(const char *vsn_url, |
| void *baton, |
| prop_setter_t setter, |
| vsn_url_helper *vuh) |
| { |
| svn_error_t *err; |
| |
| /* store the version URL as a property */ |
| svn_stringbuf_set(vuh->value, vsn_url); |
| err = (*setter)(baton, vuh->name, vuh->value); |
| if (err) |
| return svn_error_quick_wrap(err, |
| "could not save the URL of the " |
| "version resource"); |
| |
| return NULL; |
| } |
| |
| static svn_error_t *store_vsn_url(const svn_ra_dav_resource_t *rsrc, |
| void *baton, |
| prop_setter_t setter, |
| vsn_url_helper *vuh) |
| { |
| const char *vsn_url; |
| |
| vsn_url = get_vsn_url(rsrc); |
| if (vsn_url == NULL) |
| return NULL; |
| |
| return simple_store_vsn_url(vsn_url, baton, setter, vuh); |
| } |
| |
| static svn_error_t *get_delta_base(const char **delta_base, |
| const char *relpath, |
| svn_ra_get_wc_prop_func_t get_wc_prop, |
| void *cb_baton, |
| apr_pool_t *pool) |
| { |
| const svn_string_t *value; |
| |
| if (relpath == NULL || get_wc_prop == NULL) |
| { |
| *delta_base = NULL; |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR( (*get_wc_prop)(cb_baton, relpath, SVN_RA_DAV__LP_VSN_URL, |
| &value, pool) ); |
| |
| *delta_base = value ? value->data : NULL; |
| return SVN_NO_ERROR; |
| } |
| |
| /* helper func which maps certain DAV: properties to svn:wc: |
| properties. Used during checkouts and updates. */ |
| static svn_error_t *set_special_wc_prop (const char *key, |
| const char *val, |
| prop_setter_t setter, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| svn_stringbuf_t *skey = svn_stringbuf_create("", pool); |
| svn_stringbuf_t *sval = svn_stringbuf_create("", pool); |
| |
| if (strcmp(key, SVN_RA_DAV__PROP_VERSION_NAME) == 0) |
| { |
| svn_stringbuf_set(skey, SVN_PROP_ENTRY_COMMITTED_REV); |
| svn_stringbuf_set(sval, val); |
| SVN_ERR( (*setter)(baton, skey, sval) ); |
| } |
| else if (strcmp(key, SVN_RA_DAV__PROP_CREATIONDATE) == 0) |
| { |
| svn_stringbuf_set(skey, SVN_PROP_ENTRY_COMMITTED_DATE); |
| svn_stringbuf_set(sval, val); |
| SVN_ERR( (*setter)(baton, skey, sval) ); |
| } |
| else if (strcmp(key, SVN_RA_DAV__PROP_CREATOR_DISPLAYNAME) == 0) |
| { |
| svn_stringbuf_set(skey, SVN_PROP_ENTRY_LAST_AUTHOR); |
| svn_stringbuf_set(sval, val); |
| SVN_ERR( (*setter)(baton, skey, sval) ); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static void add_props(const svn_ra_dav_resource_t *r, |
| prop_setter_t setter, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| apr_hash_index_t *hi; |
| svn_stringbuf_t *skey; |
| svn_stringbuf_t *sval; |
| |
| skey = svn_stringbuf_create("", pool); |
| sval = svn_stringbuf_create("", pool); |
| |
| for (hi = apr_hash_first(pool, r->propset); hi; hi = apr_hash_next(hi)) |
| { |
| const char *key; |
| char *val; |
| |
| apr_hash_this(hi, (const void **)&key, NULL, (void *)&val); |
| |
| #define NSLEN (sizeof(SVN_PROP_CUSTOM_PREFIX) - 1) |
| if (strncmp(key, SVN_PROP_CUSTOM_PREFIX, NSLEN) == 0) |
| { |
| /* for custom props, we strip the namespace, and just use whatever |
| name the user gave the property. */ |
| |
| svn_stringbuf_set(skey, key + NSLEN); |
| |
| /* ### urk. this value isn't binary-safe... */ |
| svn_stringbuf_set(sval, val); |
| |
| (*setter)(baton, skey, sval); |
| } |
| #undef NSLEN |
| #define NSLEN (sizeof(SVN_PROP_PREFIX) - 1) |
| else if (strncmp(key, SVN_PROP_PREFIX, NSLEN) == 0) |
| { |
| /* this is one of our properties. pass it straight through. */ |
| |
| /* ### oops. watch out for props that the server sets, which we |
| ### don't want reflected in the WC. we should put these into |
| ### a server-prop namespace. */ |
| if (strcmp(key + NSLEN, "baseline-relative-path") == 0) |
| continue; |
| |
| svn_stringbuf_set(skey, key); |
| |
| /* ### urk. this value isn't binary-safe... */ |
| svn_stringbuf_set(sval, val); |
| |
| (*setter)(baton, skey, sval); |
| } |
| #undef NSLEN |
| else |
| { |
| /* If we get here, then we have a property that is neither |
| in the 'svn:custom:' space, nor in the plain old 'svn:' |
| space. There are a few properties like this. Some of |
| them, like 'checked-in', 'version-controlled-configuration', |
| and 'getetag' seem to be handled in other places |
| (get_vsn_url()). For all others, we filter them here. */ |
| set_special_wc_prop (key, val, setter, baton, pool); |
| } |
| } |
| } |
| |
| |
| static svn_error_t * fetch_dirents(svn_ra_session_t *ras, |
| const char *url, |
| void *dir_baton, |
| svn_boolean_t recurse, |
| apr_array_header_t *subdirs, |
| apr_array_header_t *files, |
| prop_setter_t setter, |
| vsn_url_helper *vuh, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *dirents; |
| struct uri parsed_url; |
| apr_hash_index_t *hi; |
| |
| /* Fetch all properties so we can snarf ones out of the svn:custom |
| * namspace. */ |
| SVN_ERR( svn_ra_dav__get_props(&dirents, ras->sess, url, NE_DEPTH_ONE, NULL, |
| NULL /* allprop */, pool) ); |
| |
| /* ### This question is from Karl: |
| * |
| * I don't understand the implementation of this function. The |
| * first thing we do is fetch all properties for a URL, and store |
| * them in a hash named `dirents'. That's where I get confused -- |
| * sure, if url is a directory, then its entries will show up as |
| * properties, but couldn't there be various other properties that |
| * are not entries in the dir? Or is it guaranteed that the *only* |
| * properties we'd get above are the directory entries, and no other |
| * properties are possible? |
| * |
| * The rest of the function is just more in the same vein -- it |
| * makes sense if and only if that guarantee is present. But I'd |
| * just be very surprised to learn that entries are the *only* |
| * possible properties one could ever get from a successful PROPFIND |
| * with depth 1 on a url. Is it really true? |
| * |
| * Anyway, regardless of the above, svn_ra_dav__get_props() needs to |
| * get properly documented in ra_dav.h, along with |
| * svn_ra_dav__get_props_resource() and svn_ra_dav__get_one_prop(). |
| * :-) |
| */ |
| |
| uri_parse(url, &parsed_url, NULL); |
| |
| for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) |
| { |
| void *val; |
| svn_ra_dav_resource_t *r; |
| |
| apr_hash_this(hi, NULL, NULL, &val); |
| r = val; |
| |
| if (r->is_collection) |
| { |
| if (uri_compare(parsed_url.path, r->url) == 0) |
| { |
| /* don't insert "this dir" into the set of subdirs */ |
| |
| /* store the version URL for this resource */ |
| SVN_ERR( store_vsn_url(r, dir_baton, setter, vuh) ); |
| } |
| else |
| { |
| if (recurse) |
| { |
| subdir_t *subdir = apr_palloc(pool, sizeof(*subdir)); |
| |
| subdir->rsrc = r; |
| subdir->parent_baton = dir_baton; |
| |
| PUSH_SUBDIR(subdirs, subdir); |
| } |
| } |
| } |
| else |
| { |
| svn_ra_dav_resource_t **file = apr_array_push(files); |
| *file = r; |
| } |
| } |
| |
| uri_free(&parsed_url); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *custom_get_request(ne_session *sess, |
| const char *url, |
| const char *relpath, |
| ne_block_reader reader, |
| void *subctx, |
| svn_ra_get_wc_prop_func_t get_wc_prop, |
| void *cb_baton, |
| apr_pool_t *pool) |
| { |
| custom_get_ctx_t cgc = { 0 }; |
| const char *delta_base; |
| ne_request *req; |
| svn_error_t *err; |
| int code; |
| |
| /* See if we can get a version URL for this resource. This will refer to |
| what we already have in the working copy, thus we can get a diff against |
| this particular resource. */ |
| SVN_ERR( get_delta_base(&delta_base, relpath, get_wc_prop, cb_baton, pool) ); |
| |
| req = ne_request_create(sess, "GET", url); |
| if (req == NULL) |
| { |
| return svn_error_createf(SVN_ERR_RA_CREATING_REQUEST, 0, NULL, pool, |
| "Could not create a GET request for %s", |
| url); |
| } |
| |
| /* we want to get the Content-Type so that we can figure out whether |
| this is an svndiff or a fulltext */ |
| ne_add_response_header_handler(req, "Content-Type", ne_content_type_handler, |
| &cgc.ctype); |
| |
| if (delta_base) |
| { |
| /* The HTTP delta draft uses an If-None-Match header holding an |
| entity tag corresponding to the copy we have. It is much more |
| natural for us to use a version URL to specify what we have. |
| Thus, we want to use the If: header to specify the URL. But |
| mod_dav sees all "State-token" items as lock tokens. When we |
| get mod_dav updated and the backend APIs expanded, then we |
| can switch to using the If: header. For now, use a custom |
| header to specify the version resource to use as the base. */ |
| ne_add_request_header(req, SVN_DAV_DELTA_BASE_HEADER, delta_base); |
| } |
| |
| /* add in a reader to capture the body of the response. */ |
| ne_add_response_body_reader(req, ne_accept_2xx, reader, &cgc); |
| |
| /* complete initialization of the body reading context */ |
| cgc.subctx = subctx; |
| |
| /* run the request and get the resulting status code (and svn_error_t) */ |
| err = svn_ra_dav__request_dispatch(&code, req, sess, "GET", url, pool); |
| |
| /* we no longer need this */ |
| if (cgc.ctype.value != NULL) |
| free(cgc.ctype.value); |
| |
| /* if there was an error writing the contents, then return it rather |
| than Neon-related errors */ |
| if (cgc.err) |
| return cgc.err; |
| |
| if (err) |
| return err; |
| |
| if (code != 200 && code != 226) |
| { |
| return svn_error_createf(SVN_ERR_RA_REQUEST_FAILED, 0, NULL, pool, |
| "GET request failed for %s", url); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static void fetch_file_reader(void *userdata, const char *buf, size_t len) |
| { |
| custom_get_ctx_t *cgc = userdata; |
| file_read_ctx_t *frc = cgc->subctx; |
| |
| if (cgc->err) |
| { |
| /* We must have gotten an error during the last read... |
| |
| ### what we'd *really* like to do here (or actually, at the |
| bottom of this function) is to somehow abort the read |
| process...no sense on banging a server for 10 megs of data |
| when we've already established that we, for some reason, |
| can't handle that data. */ |
| return; |
| } |
| |
| if (len == 0) |
| { |
| /* file is complete. */ |
| return; |
| } |
| |
| if (!cgc->checked_type) |
| { |
| |
| if (!strcmp(cgc->ctype.type, "application") && |
| !strcmp(cgc->ctype.subtype, "vnd.svn-svndiff")) |
| { |
| /* we are receiving an svndiff. set things up. */ |
| frc->stream = svn_txdelta_parse_svndiff(frc->handler, |
| frc->handler_baton, |
| TRUE, |
| frc->pool); |
| } |
| |
| cgc->checked_type = 1; |
| } |
| |
| if (frc->stream == NULL) |
| { |
| /* receiving plain text. construct a window for it. */ |
| |
| svn_txdelta_window_t window = { 0 }; |
| svn_txdelta_op_t op; |
| svn_string_t data; |
| |
| data.data = buf; |
| data.len = len; |
| |
| op.action_code = svn_txdelta_new; |
| op.offset = 0; |
| op.length = len; |
| |
| window.tview_len = len; /* result will be this long */ |
| window.num_ops = 1; |
| window.ops = &op; |
| window.new_data = &data; |
| |
| /* We can't really do anything useful if we get an error here. Pass |
| it off to someone who can. */ |
| cgc->err = (*frc->handler)(&window, frc->handler_baton); |
| } |
| else |
| { |
| /* receiving svndiff. feed it to the svndiff parser. */ |
| |
| apr_size_t written = len; |
| |
| cgc->err = svn_stream_write(frc->stream, buf, &written); |
| |
| /* ### the svndiff stream parser does not obey svn_stream semantics |
| ### in its write handler. it does not output the number of bytes |
| ### consumed by the handler. specifically, it may decrement the |
| ### number by 4 for the header, then never touch it again. that |
| ### makes it appear like an incomplete write. |
| ### disable this check for now. the svndiff parser actually does |
| ### consume all bytes, all the time. |
| */ |
| #if 0 |
| if (written != len && cgc->err == NULL) |
| cgc->err = svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0, NULL, |
| frc->pool, |
| "unable to completely write the svndiff " |
| "data to the parser stream (wrote %lu " |
| "of %lu bytes)", |
| (unsigned long)written, |
| (unsigned long)len); |
| #endif |
| } |
| } |
| |
| static svn_error_t *simple_fetch_file(ne_session *sess, |
| const char *url, |
| const char *relpath, |
| svn_boolean_t text_deltas, |
| void *file_baton, |
| const svn_delta_edit_fns_t *editor, |
| svn_ra_get_wc_prop_func_t get_wc_prop, |
| void *cb_baton, |
| apr_pool_t *pool) |
| { |
| file_read_ctx_t frc = { 0 }; |
| svn_string_t my_url; |
| svn_stringbuf_t *url_str; |
| |
| my_url.data = url; |
| my_url.len = strlen(url); |
| url_str = svn_path_uri_encode (&my_url, pool); |
| |
| SVN_ERR_W( (*editor->apply_textdelta)(file_baton, |
| &frc.handler, |
| &frc.handler_baton), |
| "could not save file"); |
| |
| /* Only bother with text-deltas if our caller cares. */ |
| if (! text_deltas) |
| { |
| SVN_ERR ((*frc.handler)(NULL, frc.handler_baton)); |
| return SVN_NO_ERROR; |
| } |
| |
| frc.pool = pool; |
| |
| SVN_ERR( custom_get_request(sess, url_str->data, relpath, |
| fetch_file_reader, &frc, |
| get_wc_prop, cb_baton, pool) ); |
| |
| /* close the handler, since the file reading completed successfully. */ |
| SVN_ERR( (*frc.handler)(NULL, frc.handler_baton) ); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *fetch_file(ne_session *sess, |
| const svn_ra_dav_resource_t *rsrc, |
| void *dir_baton, |
| vsn_url_helper *vuh, |
| const svn_delta_edit_fns_t *editor, |
| apr_pool_t *pool) |
| { |
| const char *bc_url = rsrc->url; /* url in the Baseline Collection */ |
| svn_error_t *err; |
| svn_error_t *err2; |
| svn_stringbuf_t *name; |
| void *file_baton; |
| |
| name = my_basename(bc_url, pool); |
| SVN_ERR_W( (*editor->add_file)(name, dir_baton, |
| NULL, SVN_INVALID_REVNUM, |
| &file_baton), |
| "could not add a file"); |
| |
| /* fetch_file() is only used for checkout, so we just pass NULL for the |
| simple_fetch_file() params related to fetching version URLs (for |
| fetching deltas) */ |
| err = simple_fetch_file(sess, bc_url, NULL, TRUE, file_baton, editor, |
| NULL, NULL, pool); |
| if (err) |
| { |
| /* ### do we really need to bother with closing the file_baton? */ |
| goto error; |
| } |
| |
| /* Add the properties. */ |
| add_props(rsrc, editor->change_file_prop, file_baton, pool); |
| |
| /* store the version URL as a property */ |
| err = store_vsn_url(rsrc, file_baton, editor->change_file_prop, vuh); |
| |
| error: |
| err2 = (*editor->close_file)(file_baton); |
| return err ? err : err2; |
| } |
| |
| static svn_error_t * begin_checkout(svn_ra_session_t *ras, |
| svn_revnum_t revision, |
| const svn_string_t **activity_url, |
| svn_revnum_t *target_rev, |
| const char **bc_root) |
| { |
| apr_pool_t *pool = ras->pool; |
| svn_boolean_t is_dir; |
| svn_string_t bc_url; |
| svn_string_t bc_relative; |
| #ifdef BUSTED_CRAP |
| svn_stringbuf_t *path; |
| #endif |
| |
| /* ### if REVISION means "get latest", then we can use an expand-property |
| ### REPORT rather than two PROPFINDs to reach the baseline-collection */ |
| |
| /* Fetch the activity-collection-set from the server. */ |
| /* ### also need to fetch/validate the DAV capabilities */ |
| SVN_ERR( svn_ra_dav__get_activity_url(activity_url, ras, ras->root.path, |
| pool) ); |
| |
| SVN_ERR( svn_ra_dav__get_baseline_info(&is_dir, &bc_url, &bc_relative, |
| target_rev, ras->sess, |
| ras->root.path, revision, pool) ); |
| if (!is_dir) |
| { |
| /* ### eek. what to do? */ |
| |
| /* ### need an SVN_ERR here */ |
| return svn_error_create(APR_EGENERAL, 0, NULL, pool, |
| "URL does not identify a collection."); |
| } |
| |
| /* The root for the checkout is the Baseline Collection root, plus the |
| relative location of the public URL to its repository root. */ |
| |
| #ifdef BUSTED_CRAP |
| path = svn_stringbuf_create_from_string(&bc_url, pool); |
| svn_path_add_component_nts(path, bc_relative.data); |
| |
| *bc_root = path->data; |
| #else |
| /* ### this is broken cuz it assumes bc_url has a trailing slash. */ |
| *bc_root = apr_pstrcat(pool, bc_url.data, bc_relative.data, NULL); |
| #endif |
| |
| return NULL; |
| } |
| |
| |
| /* Helper (neon callback) for svn_ra_dav__get_file. */ |
| static void get_file_reader(void *userdata, const char *buf, size_t len) |
| { |
| custom_get_ctx_t *cgc = userdata; |
| apr_size_t wlen; |
| |
| /* The stream we want to push data at. */ |
| svn_stream_t *stream = cgc->subctx; |
| |
| /* Write however many bytes were passed in by neon. */ |
| wlen = len; |
| svn_stream_write(stream, buf, &wlen); |
| |
| #if 0 |
| /* Neon's callback won't let us return error. Joe knows this is a |
| bug in his API, so this section can be reactivated someday. */ |
| |
| if (wlen != len) |
| { |
| /* Uh oh, didn't write as many bytes as neon gave us. */ |
| return |
| svn_error_create(SVN_ERR_UNEXPECTED_EOF, 0, NULL, |
| sbaton->pool, "Error writing to svn_stream."); |
| } |
| #endif |
| |
| } |
| |
| |
| /* minor helper for svn_ra_dav__get_file, of type prop_setter_t */ |
| static svn_error_t * |
| add_prop_to_hash (void *baton, |
| svn_stringbuf_t *name, |
| svn_stringbuf_t *value) |
| { |
| apr_hash_t *ht = (apr_hash_t *) baton; |
| |
| apr_hash_set(ht, name->data, name->len, value); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t *svn_ra_dav__get_file(void *session_baton, |
| const char *path, |
| svn_revnum_t revision, |
| svn_stream_t *stream, |
| svn_revnum_t *fetched_rev, |
| apr_hash_t **props) |
| { |
| svn_ra_dav_resource_t *rsrc; |
| apr_hash_index_t *hi; |
| svn_stringbuf_t *url_str; |
| const char *final_url; |
| svn_ra_session_t *ras = (svn_ra_session_t *) session_baton; |
| |
| /* First, create the full URL that the user wants to get. */ |
| url_str = svn_stringbuf_create (ras->url, ras->pool); |
| svn_path_add_component_nts (url_str, path); |
| |
| /* If the revision is invalid (head), then we're done. Just fetch |
| the public URL, because that will always get HEAD. */ |
| if ((! SVN_IS_VALID_REVNUM(revision)) && (fetched_rev == NULL)) |
| final_url = url_str->data; |
| |
| /* If the revision is something specific, we need to create a bc_url. */ |
| else |
| { |
| svn_revnum_t got_rev; |
| svn_string_t bc_url, bc_relative; |
| svn_stringbuf_t *final_bc_url; |
| |
| SVN_ERR (svn_ra_dav__get_baseline_info(NULL, |
| &bc_url, &bc_relative, |
| &got_rev, |
| ras->sess, |
| url_str->data, revision, |
| ras->pool)); |
| |
| final_bc_url = svn_stringbuf_create_from_string(&bc_url, ras->pool); |
| svn_path_add_component_nts (final_bc_url, bc_relative.data); |
| |
| final_url = final_bc_url->data; |
| |
| if (fetched_rev != NULL) |
| *fetched_rev = got_rev; |
| } |
| |
| /* Fetch the file, shoving it at the provided stream. */ |
| SVN_ERR( custom_get_request(ras->sess, final_url, path, |
| get_file_reader, stream, |
| ras->callbacks->get_wc_prop, |
| ras->callback_baton, ras->pool) ); |
| |
| if (props) |
| { |
| SVN_ERR( svn_ra_dav__get_props_resource(&rsrc, ras->sess, final_url, |
| NULL, NULL /* all props */, |
| ras->pool) ); |
| |
| *props = apr_hash_make(ras->pool); |
| |
| for (hi = apr_hash_first(ras->pool, rsrc->propset); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| |
| apr_hash_this(hi, &key, NULL, &val); |
| |
| /* If the property starts with "svn:custom:", then it's a |
| normal user-controlled property coming from the fs. Just |
| strip off this prefix and add to the hash. */ |
| #define NSLEN (sizeof(SVN_PROP_CUSTOM_PREFIX) - 1) |
| if (strncmp(key, SVN_PROP_CUSTOM_PREFIX, NSLEN) == 0) |
| apr_hash_set(*props, &((const char *)key)[NSLEN], |
| APR_HASH_KEY_STRING, |
| svn_string_create(val, ras->pool)); |
| #undef NSLEN |
| |
| else if (strcmp(key, SVN_RA_DAV__PROP_CHECKED_IN) == 0) |
| /* For files, we currently only have one 'wc' prop. */ |
| apr_hash_set(*props, SVN_RA_DAV__LP_VSN_URL, APR_HASH_KEY_STRING, |
| svn_string_create(val, ras->pool)); |
| |
| else |
| /* If it's one of the 'entry' props, this func will |
| recognize the DAV name & add it to the hash mapped to a |
| new name recognized by libsvn_wc. */ |
| SVN_ERR( set_special_wc_prop ((const char *) key, |
| (const char *) val, |
| add_prop_to_hash, *props, |
| ras->pool) ); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * svn_ra_dav__do_checkout(void *session_baton, |
| svn_revnum_t revision, |
| svn_boolean_t recurse, |
| const svn_delta_edit_fns_t *editor, |
| void *edit_baton) |
| { |
| svn_ra_session_t *ras = session_baton; |
| |
| svn_error_t *err; |
| void *root_baton; |
| svn_stringbuf_t *act_url_name; |
| svn_stringbuf_t *act_url_value; |
| vsn_url_helper vuh; |
| const svn_string_t *activity_url; |
| svn_revnum_t target_rev; |
| const char *bc_root; |
| subdir_t *subdir; |
| apr_array_header_t *subdirs; /* subdirs to scan (subdir_t *) */ |
| apr_array_header_t *files; /* files to grab (svn_ra_dav_resource_t *) */ |
| apr_pool_t *subpool; |
| |
| /* ### use quick_wrap rather than SVN_ERR on some of these? */ |
| |
| /* this subpool will be used during various iteration loops, and cleared |
| each time. long-lived stuff should go into ras->pool. */ |
| subpool = svn_pool_create(ras->pool); |
| |
| /* begin the checkout process by fetching some basic information */ |
| SVN_ERR( begin_checkout(ras, revision, &activity_url, &target_rev, |
| &bc_root) ); |
| |
| /* all the files we checkout will have TARGET_REV for the revision */ |
| SVN_ERR( (*editor->set_target_revision)(edit_baton, target_rev) ); |
| |
| /* In the checkout case, we don't really have a base revision, so |
| pass SVN_IGNORED_REVNUM. */ |
| SVN_ERR( (*editor->open_root)(edit_baton, SVN_IGNORED_REVNUM, |
| &root_baton) ); |
| |
| /* store the subdirs into an array for processing, rather than recursing */ |
| subdirs = apr_array_make(ras->pool, 5, sizeof(subdir_t *)); |
| files = apr_array_make(ras->pool, 10, sizeof(svn_ra_dav_resource_t *)); |
| |
| /* Build a directory resource for the root. We'll pop this off and fetch |
| the information for it. */ |
| subdir = apr_palloc(ras->pool, sizeof(*subdir)); |
| subdir->parent_baton = root_baton; |
| |
| subdir->rsrc = apr_pcalloc(ras->pool, sizeof(*subdir->rsrc)); |
| subdir->rsrc->url = bc_root; |
| |
| PUSH_SUBDIR(subdirs, subdir); |
| |
| /* ### damn. gotta build a string. */ |
| act_url_name = svn_stringbuf_create(SVN_RA_DAV__LP_ACTIVITY_URL, ras->pool); |
| act_url_value = svn_stringbuf_create_from_string(activity_url, ras->pool); |
| |
| /* prep the helper */ |
| vuh.name = svn_stringbuf_create(SVN_RA_DAV__LP_VSN_URL, ras->pool); |
| vuh.value = MAKE_BUFFER(ras->pool); |
| |
| do |
| { |
| const char *url; |
| svn_ra_dav_resource_t *rsrc; |
| void *parent_baton; |
| void *this_baton; |
| int i; |
| |
| /* pop a subdir off the stack */ |
| while (1) |
| { |
| subdir = POP_SUBDIR(subdirs); |
| parent_baton = subdir->parent_baton; |
| |
| if (subdir->rsrc != NULL) |
| { |
| url = subdir->rsrc->url; |
| break; |
| } |
| /* sentinel reached. close the dir. possibly done! */ |
| |
| err = (*editor->close_directory) (parent_baton); |
| if (err) |
| return svn_error_quick_wrap(err, "could not finish directory"); |
| |
| if (subdirs->nelts == 0) |
| { |
| /* Finish the edit */ |
| SVN_ERR( ((*editor->close_edit) (edit_baton)) ); |
| |
| /* Store auth info if necessary */ |
| SVN_ERR( (svn_ra_dav__maybe_store_auth_info (ras)) ); |
| |
| return SVN_NO_ERROR; |
| } |
| } |
| |
| if (strlen(url) > strlen(bc_root)) |
| { |
| svn_stringbuf_t *name; |
| |
| /* We're not in the root, add a directory */ |
| name = my_basename(url, subpool); |
| |
| SVN_ERR_W( (*editor->add_directory) (name, parent_baton, |
| NULL, SVN_INVALID_REVNUM, |
| &this_baton), |
| "could not add directory"); |
| } |
| else |
| { |
| /* We are operating in the root of the repository */ |
| this_baton = root_baton; |
| } |
| |
| SVN_ERR (svn_ra_dav__get_props_resource(&rsrc, |
| ras->sess, |
| url, |
| NULL, |
| NULL, |
| subpool)); |
| add_props(rsrc, editor->change_dir_prop, this_baton, subpool); |
| |
| /* finished processing the directory. clear out the gunk. */ |
| svn_pool_clear(subpool); |
| |
| /* add a sentinel. this will be used to signal a close_directory |
| for this directory's baton. */ |
| subdir = apr_pcalloc(ras->pool, sizeof(*subdir)); |
| subdir->parent_baton = this_baton; |
| PUSH_SUBDIR(subdirs, subdir); |
| |
| err = fetch_dirents(ras, url, this_baton, recurse, subdirs, files, |
| editor->change_dir_prop, &vuh, ras->pool); |
| if (err) |
| return svn_error_quick_wrap(err, "could not fetch directory entries"); |
| |
| /* ### use set_wc_dir_prop() */ |
| |
| /* store the activity URL as a property */ |
| /* ### should we close the dir batons before returning?? */ |
| SVN_ERR_W( (*editor->change_dir_prop)(this_baton, act_url_name, |
| act_url_value), |
| "could not save the URL to indicate " |
| "where to create activities"); |
| |
| /* process each of the files that were found */ |
| for (i = files->nelts; i--; ) |
| { |
| rsrc = ((svn_ra_dav_resource_t **)files->elts)[i]; |
| |
| /* ### should we close the dir batons first? */ |
| SVN_ERR_W( fetch_file(ras->sess, rsrc, this_baton, &vuh, editor, |
| subpool), |
| "could not checkout a file"); |
| |
| /* trash the per-file stuff. */ |
| svn_pool_clear(subpool); |
| } |
| |
| /* reset the list of files */ |
| files->nelts = 0; |
| |
| } while (subdirs->nelts > 0); |
| |
| /* ### should never reach??? */ |
| |
| /* Finish the edit */ |
| SVN_ERR( ((*editor->close_edit) (edit_baton)) ); |
| |
| /* Store auth info if necessary */ |
| SVN_ERR( (svn_ra_dav__maybe_store_auth_info (ras)) ); |
| |
| svn_pool_destroy(subpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* ------------------------------------------------------------------------- */ |
| |
| svn_error_t *svn_ra_dav__get_latest_revnum(void *session_baton, |
| svn_revnum_t *latest_revnum) |
| { |
| svn_ra_session_t *ras = session_baton; |
| |
| /* ### should we perform an OPTIONS to validate the server we're about |
| ### to talk to? */ |
| |
| /* we don't need any of the baseline URLs and stuff, but this does |
| give us the latest revision number */ |
| SVN_ERR( svn_ra_dav__get_baseline_info(NULL, NULL, NULL, latest_revnum, |
| ras->sess, ras->root.path, |
| SVN_INVALID_REVNUM, ras->pool) ); |
| |
| SVN_ERR( svn_ra_dav__maybe_store_auth_info(ras) ); |
| |
| return NULL; |
| } |
| |
| |
| /* ### DUMMY FUNC. To be marshalled over network like previous |
| routine. */ |
| |
| svn_error_t *svn_ra_dav__get_dated_revision (void *session_baton, |
| svn_revnum_t *revision, |
| apr_time_t timestamp) |
| { |
| svn_ra_session_t *ras = session_baton; |
| |
| /* ### need to implement this function... |
| ### |
| ### marshal the query over and call svn_repos_dated_revision() |
| */ |
| |
| *revision = SVN_INVALID_REVNUM; |
| |
| return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, ras->pool, |
| "ra_dav does not currently support date-based " |
| "operations."); |
| } |
| |
| |
| /* ------------------------------------------------------------------------- |
| ** |
| ** UPDATE HANDLING |
| ** |
| ** ### docco... |
| ** |
| ** DTD of the update report: |
| ** ### open/add file/dir. first child is always checked-in/href (vsn_url). |
| ** ### next are subdir elems, possibly fetch-file, then fetch-prop. |
| */ |
| |
| /* This implements the `ne_xml_validate_cb' prototype. */ |
| static int validate_element(void *userdata, |
| ne_xml_elmid parent, |
| ne_xml_elmid child) |
| { |
| /* We're being very strict with the validity of XML elements here. If |
| something exists that we don't know about, then we might not update |
| the client properly. We also make various assumptions in the element |
| processing functions, and the strong validation enables those |
| assumptions. */ |
| |
| switch (parent) |
| { |
| case NE_ELM_root: |
| if (child == ELEM_update_report) |
| return NE_XML_VALID; |
| else |
| return NE_XML_INVALID; |
| |
| case ELEM_update_report: |
| if (child == ELEM_target_revision |
| || child == ELEM_open_directory) |
| return NE_XML_VALID; |
| else |
| return NE_XML_INVALID; |
| |
| case ELEM_open_directory: |
| if (child == ELEM_open_directory |
| || child == ELEM_add_directory |
| || child == ELEM_open_file |
| || child == ELEM_add_file |
| || child == ELEM_fetch_props |
| || child == ELEM_remove_prop |
| || child == ELEM_delete_entry |
| || child == ELEM_prop |
| || child == ELEM_checked_in) |
| return NE_XML_VALID; |
| else |
| return NE_XML_INVALID; |
| |
| case ELEM_add_directory: |
| if (child == ELEM_add_directory |
| || child == ELEM_add_file |
| || child == ELEM_prop |
| || child == ELEM_checked_in) |
| return NE_XML_VALID; |
| else |
| return NE_XML_INVALID; |
| |
| case ELEM_open_file: |
| if (child == ELEM_checked_in |
| || child == ELEM_fetch_file |
| || child == ELEM_prop |
| || child == ELEM_fetch_props |
| || child == ELEM_remove_prop) |
| return NE_XML_VALID; |
| else |
| return NE_XML_INVALID; |
| |
| case ELEM_add_file: |
| if (child == ELEM_checked_in |
| || child == ELEM_prop) |
| return NE_XML_VALID; |
| else |
| return NE_XML_INVALID; |
| |
| case ELEM_checked_in: |
| if (child == NE_ELM_href) |
| return NE_XML_VALID; |
| else |
| return NE_XML_INVALID; |
| |
| case ELEM_prop: |
| if (child == ELEM_version_name |
| || child == ELEM_creationdate |
| || child == ELEM_creator_displayname |
| || child == ELEM_remove_prop) |
| return NE_XML_VALID; |
| else |
| return NE_XML_INVALID; |
| |
| default: |
| return NE_XML_DECLINE; |
| } |
| |
| /* NOTREACHED */ |
| } |
| |
| static const char *get_attr(const char **atts, const char *which) |
| { |
| for (; *atts != NULL; atts += 2) |
| if (strcmp(*atts, which) == 0) |
| return atts[1]; |
| return NULL; |
| } |
| |
| static void push_dir(report_baton_t *rb, void *baton, svn_stringbuf_t *pathbuf) |
| { |
| dir_item_t *di = (dir_item_t *)apr_array_push(rb->dirs); |
| |
| memset(di, 0, sizeof(*di)); |
| di->baton = baton; |
| di->pathbuf = pathbuf; |
| } |
| |
| |
| /* This implements the `ne_xml_startelm_cb' prototype. */ |
| static int start_element(void *userdata, const struct ne_xml_elm *elm, |
| const char **atts) |
| { |
| report_baton_t *rb = userdata; |
| const char *att; |
| svn_revnum_t base; |
| const char *name; |
| svn_stringbuf_t *cpath = NULL; |
| svn_revnum_t crev = SVN_INVALID_REVNUM; |
| dir_item_t *parent_dir; |
| void *new_dir_baton; |
| svn_stringbuf_t *pathbuf; |
| |
| switch (elm->id) |
| { |
| case ELEM_target_revision: |
| att = get_attr(atts, "rev"); |
| /* ### verify we got it. punt on error. */ |
| |
| CHKERR( (*rb->editor->set_target_revision)(rb->edit_baton, |
| SVN_STR_TO_REV(att)) ); |
| break; |
| |
| case ELEM_open_directory: |
| att = get_attr(atts, "rev"); |
| /* ### verify we got it. punt on error. */ |
| base = SVN_STR_TO_REV(att); |
| if (rb->dirs->nelts == 0) |
| { |
| pathbuf = svn_stringbuf_create(".", rb->ras->pool); |
| CHKERR( (*rb->editor->open_root)(rb->edit_baton, base, |
| &new_dir_baton) ); |
| } |
| else |
| { |
| name = get_attr(atts, "name"); |
| /* ### verify we got it. punt on error. */ |
| svn_stringbuf_set(rb->namestr, name); |
| |
| parent_dir = &TOP_DIR(rb); |
| |
| pathbuf = svn_stringbuf_dup(parent_dir->pathbuf, rb->ras->pool); |
| svn_path_add_component(pathbuf, rb->namestr); |
| |
| CHKERR( (*rb->editor->open_directory)(rb->namestr, |
| parent_dir->baton, base, |
| &new_dir_baton) ); |
| } |
| |
| /* push the new baton onto the directory baton stack */ |
| push_dir(rb, new_dir_baton, pathbuf); |
| |
| /* Property fetching is NOT implied in replacement. */ |
| TOP_DIR(rb).fetch_props = FALSE; |
| break; |
| |
| case ELEM_add_directory: |
| name = get_attr(atts, "name"); |
| /* ### verify we got it. punt on error. */ |
| svn_stringbuf_set(rb->namestr, name); |
| |
| att = get_attr(atts, "copyfrom-path"); |
| if (att != NULL) |
| { |
| cpath = rb->cpathstr; |
| svn_stringbuf_set(cpath, att); |
| |
| att = get_attr(atts, "copyfrom-rev"); |
| /* ### verify we got it. punt on error. */ |
| crev = SVN_STR_TO_REV(att); |
| } |
| |
| parent_dir = &TOP_DIR(rb); |
| |
| pathbuf = svn_stringbuf_dup(parent_dir->pathbuf, rb->ras->pool); |
| svn_path_add_component(pathbuf, rb->namestr); |
| |
| CHKERR( (*rb->editor->add_directory)(rb->namestr, parent_dir->baton, |
| cpath, crev, &new_dir_baton) ); |
| |
| /* push the new baton onto the directory baton stack */ |
| push_dir(rb, new_dir_baton, pathbuf); |
| |
| /* Property fetching is implied in addition. */ |
| TOP_DIR(rb).fetch_props = TRUE; |
| break; |
| |
| case ELEM_open_file: |
| att = get_attr(atts, "rev"); |
| /* ### verify we got it. punt on error. */ |
| base = SVN_STR_TO_REV(att); |
| |
| name = get_attr(atts, "name"); |
| /* ### verify we got it. punt on error. */ |
| svn_stringbuf_set(rb->namestr, name); |
| |
| parent_dir = &TOP_DIR(rb); |
| CHKERR( (*rb->editor->open_file)(rb->namestr, parent_dir->baton, base, |
| &rb->file_baton) ); |
| |
| /* Property fetching is NOT implied in replacement. */ |
| rb->fetch_props = FALSE; |
| |
| /* Add this file's name into the directory's path buffer. It will be |
| removed in end_element() */ |
| svn_path_add_component(parent_dir->pathbuf, rb->namestr); |
| break; |
| |
| case ELEM_add_file: |
| name = get_attr(atts, "name"); |
| /* ### verify we got it. punt on error. */ |
| svn_stringbuf_set(rb->namestr, name); |
| |
| att = get_attr(atts, "copyfrom-path"); |
| if (att != NULL) |
| { |
| cpath = rb->cpathstr; |
| svn_stringbuf_set(cpath, att); |
| |
| att = get_attr(atts, "copyfrom-rev"); |
| /* ### verify we got it. punt on error. */ |
| crev = SVN_STR_TO_REV(att); |
| } |
| |
| parent_dir = &TOP_DIR(rb); |
| CHKERR( (*rb->editor->add_file)(rb->namestr, parent_dir->baton, |
| cpath, crev, &rb->file_baton) ); |
| |
| /* Property fetching is implied in addition. */ |
| rb->fetch_props = TRUE; |
| |
| /* Add this file's name into the directory's path buffer. It will be |
| removed in end_element() */ |
| svn_path_add_component(parent_dir->pathbuf, rb->namestr); |
| break; |
| |
| case ELEM_remove_prop: |
| name = get_attr(atts, "name"); |
| /* ### verify we got it. punt on error. */ |
| svn_stringbuf_set(rb->namestr, name); |
| |
| /* Removing a prop. */ |
| if (rb->file_baton == NULL) |
| rb->editor->change_dir_prop(TOP_DIR(rb).baton, rb->namestr, NULL); |
| else |
| rb->editor->change_file_prop(rb->file_baton, rb->namestr, NULL); |
| break; |
| |
| case ELEM_fetch_props: |
| if (!rb->fetch_content) |
| { |
| /* If this is just a status check, the specifics of the |
| property change are uninteresting. Simply call our |
| editor function with bogus data so it registers a |
| property mod. */ |
| svn_stringbuf_set(rb->namestr, SVN_PROP_PREFIX "BOGOSITY"); |
| |
| if (rb->file_baton == NULL) |
| rb->editor->change_dir_prop(TOP_DIR(rb).baton, rb->namestr, NULL); |
| else |
| rb->editor->change_file_prop(rb->file_baton, rb->namestr, NULL); |
| } |
| else |
| { |
| /* Note that we need to fetch props for this... */ |
| if (rb->file_baton == NULL) |
| TOP_DIR(rb).fetch_props = TRUE; /* ...directory. */ |
| else |
| rb->fetch_props = TRUE; /* ...file. */ |
| } |
| break; |
| |
| case ELEM_fetch_file: |
| /* assert: rb->href->len > 0 */ |
| CHKERR( simple_fetch_file(rb->ras->sess2, rb->href->data, |
| TOP_DIR(rb).pathbuf->data, |
| rb->fetch_content, |
| rb->file_baton, rb->editor, |
| rb->ras->callbacks->get_wc_prop, |
| rb->ras->callback_baton, |
| rb->ras->pool) ); |
| break; |
| |
| case ELEM_delete_entry: |
| name = get_attr(atts, "name"); |
| /* ### verify we got it. punt on error. */ |
| svn_stringbuf_set(rb->namestr, name); |
| |
| CHKERR( (*rb->editor->delete_entry)(rb->namestr, |
| SVN_INVALID_REVNUM, |
| TOP_DIR(rb).baton) ); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| |
| static svn_error_t * |
| add_node_props (report_baton_t *rb) |
| { |
| svn_ra_dav_resource_t *rsrc; |
| |
| /* Do nothing if we aren't fetching content. */ |
| if (!rb->fetch_content) |
| return SVN_NO_ERROR; |
| |
| if (rb->file_baton) |
| { |
| if (! rb->fetch_props) |
| return SVN_NO_ERROR; |
| |
| /* Fetch dir props. */ |
| SVN_ERR (svn_ra_dav__get_props_resource(&rsrc, |
| rb->ras->sess2, |
| rb->href->data, |
| NULL, |
| NULL, |
| rb->ras->pool)); |
| add_props(rsrc, |
| rb->editor->change_file_prop, |
| rb->file_baton, |
| rb->ras->pool); |
| } |
| else |
| { |
| if (! TOP_DIR(rb).fetch_props) |
| return SVN_NO_ERROR; |
| |
| /* Fetch dir props. */ |
| SVN_ERR (svn_ra_dav__get_props_resource(&rsrc, |
| rb->ras->sess2, |
| TOP_DIR(rb).vsn_url, |
| NULL, |
| NULL, |
| rb->ras->pool)); |
| add_props(rsrc, |
| rb->editor->change_dir_prop, |
| TOP_DIR(rb).baton, |
| rb->ras->pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* This implements the `ne_xml_endelm_cb' prototype. */ |
| static int end_element(void *userdata, |
| const struct ne_xml_elm *elm, |
| const char *cdata) |
| { |
| report_baton_t *rb = userdata; |
| |
| switch (elm->id) |
| { |
| case ELEM_add_directory: |
| |
| /*** FALLTHRU ***/ |
| |
| case ELEM_open_directory: |
| /* fetch node props as necessary. */ |
| CHKERR( add_node_props (rb) ); |
| |
| /* close the topmost directory, and pop it from the stack */ |
| CHKERR( (*rb->editor->close_directory)(TOP_DIR(rb).baton) ); |
| --rb->dirs->nelts; |
| break; |
| |
| case ELEM_add_file: |
| /* we wait until the close element to do the work. this allows us to |
| retrieve the href before fetching. */ |
| |
| /* fetch file */ |
| CHKERR( simple_fetch_file(rb->ras->sess2, rb->href->data, |
| TOP_DIR(rb).pathbuf->data, |
| rb->fetch_content, |
| rb->file_baton, rb->editor, |
| rb->ras->callbacks->get_wc_prop, |
| rb->ras->callback_baton, |
| rb->ras->pool) ); |
| |
| |
| /*** FALLTHRU ***/ |
| |
| case ELEM_open_file: |
| /* fetch node props as necessary. */ |
| CHKERR( add_node_props (rb) ); |
| |
| /* close the file and mark that we are no longer operating on a file */ |
| CHKERR( (*rb->editor->close_file)(rb->file_baton) ); |
| rb->file_baton = NULL; |
| |
| /* Yank this file out of the directory's path buffer. */ |
| svn_path_remove_component(TOP_DIR(rb).pathbuf); |
| break; |
| |
| case NE_ELM_href: |
| /* do nothing if we aren't fetching content. */ |
| if (!rb->fetch_content) |
| break; |
| |
| /* record the href that we just found */ |
| svn_ra_dav__copy_href(rb->href, cdata); |
| |
| if (rb->file_baton == NULL) |
| { |
| CHKERR( simple_store_vsn_url(rb->href->data, TOP_DIR(rb).baton, |
| rb->editor->change_dir_prop, |
| &rb->vuh) ); |
| |
| /* save away the URL in case a fetch-props arrives after all of |
| the subdir processing. we will need this copy of the URL to |
| fetch the properties (i.e. rb->href will be toast by then). */ |
| TOP_DIR(rb).vsn_url = apr_pmemdup(rb->ras->pool, |
| rb->href->data, rb->href->len + 1); |
| } |
| else |
| { |
| CHKERR( simple_store_vsn_url(rb->href->data, rb->file_baton, |
| rb->editor->change_file_prop, |
| &rb->vuh) ); |
| } |
| break; |
| |
| case ELEM_version_name: |
| case ELEM_creationdate: |
| case ELEM_creator_displayname: |
| { |
| /* The name of the xml tag is the property that we want to set. */ |
| svn_stringbuf_t *tagname = |
| svn_stringbuf_create (elm->nspace, rb->ras->pool); |
| svn_stringbuf_appendcstr (tagname, elm->name); |
| |
| CHKERR( set_special_wc_prop (tagname->data, |
| cdata, |
| rb->file_baton ? |
| rb->editor->change_file_prop : |
| rb->editor->change_dir_prop, |
| rb->file_baton ? |
| rb->file_baton : |
| TOP_DIR(rb).baton, |
| rb->ras->pool) ); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| |
| static svn_error_t * reporter_set_path(void *report_baton, |
| const char *path, |
| svn_revnum_t revision) |
| { |
| report_baton_t *rb = report_baton; |
| apr_status_t status; |
| const char *entry; |
| svn_stringbuf_t *qpath = NULL; |
| |
| svn_xml_escape_nts (&qpath, path, rb->ras->pool); |
| entry = apr_psprintf(rb->ras->pool, |
| "<S:entry rev=\"%ld\">%s</S:entry>" DEBUG_CR, |
| revision, qpath->data); |
| |
| status = apr_file_write_full(rb->tmpfile, entry, strlen(entry), NULL); |
| if (status) |
| { |
| (void) apr_file_close(rb->tmpfile); |
| return svn_error_create(status, 0, NULL, rb->ras->pool, |
| "Could not write an entry to the temporary " |
| "report file."); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * reporter_delete_path(void *report_baton, |
| const char *path) |
| { |
| report_baton_t *rb = report_baton; |
| apr_status_t status; |
| const char *s; |
| svn_stringbuf_t *qpath = NULL; |
| |
| svn_xml_escape_nts (&qpath, path, rb->ras->pool); |
| s = apr_psprintf(rb->ras->pool, |
| "<S:missing>%s</S:missing>" DEBUG_CR, |
| qpath->data); |
| |
| status = apr_file_write_full(rb->tmpfile, s, strlen(s), NULL); |
| if (status) |
| { |
| (void) apr_file_close(rb->tmpfile); |
| return svn_error_create(status, 0, NULL, rb->ras->pool, |
| "Could not write a missing entry to the " |
| "temporary report file."); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * reporter_abort_report(void *report_baton) |
| { |
| report_baton_t *rb = report_baton; |
| |
| (void) apr_file_close(rb->tmpfile); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * reporter_finish_report(void *report_baton) |
| { |
| report_baton_t *rb = report_baton; |
| apr_status_t status; |
| int fdesc; |
| svn_error_t *err; |
| apr_off_t offset = 0; |
| |
| status = apr_file_write_full(rb->tmpfile, |
| report_tail, sizeof(report_tail) - 1, NULL); |
| if (status) |
| { |
| (void) apr_file_close(rb->tmpfile); |
| return svn_error_create(status, 0, NULL, rb->ras->pool, |
| "Could not write the trailer for the temporary " |
| "report file."); |
| } |
| |
| /* get the editor process prepped */ |
| rb->dirs = apr_array_make(rb->ras->pool, 5, sizeof(dir_item_t)); |
| rb->namestr = MAKE_BUFFER(rb->ras->pool); |
| rb->cpathstr = MAKE_BUFFER(rb->ras->pool); |
| rb->href = MAKE_BUFFER(rb->ras->pool); |
| |
| rb->vuh.name = svn_stringbuf_create(SVN_RA_DAV__LP_VSN_URL, rb->ras->pool); |
| rb->vuh.value = MAKE_BUFFER(rb->ras->pool); |
| |
| /* Rewind the tmpfile. */ |
| status = apr_file_seek(rb->tmpfile, APR_SET, &offset); |
| if (status) |
| { |
| (void) apr_file_close(rb->tmpfile); |
| return svn_error_create(status, 0, NULL, rb->ras->pool, |
| "Couldn't rewind tmpfile."); |
| } |
| /* Convert the (apr_file_t *)tmpfile into a file descriptor for neon. */ |
| status = svn_io_fd_from_file(&fdesc, rb->tmpfile); |
| if (status) |
| { |
| (void) apr_file_close(rb->tmpfile); |
| return svn_error_create(status, 0, NULL, rb->ras->pool, |
| "Couldn't get file-descriptor of tmpfile."); |
| } |
| |
| err = svn_ra_dav__parsed_request(rb->ras, "REPORT", rb->ras->root.path, |
| NULL, fdesc, |
| report_elements, validate_element, |
| start_element, end_element, rb, |
| rb->ras->pool); |
| |
| /* we're done with the file */ |
| (void) apr_file_close(rb->tmpfile); |
| |
| if (err != NULL) |
| return err; |
| if (rb->err != NULL) |
| return rb->err; |
| |
| /* we got the whole HTTP response thing done. now wrap up the update |
| process with a close_edit call. */ |
| SVN_ERR( (*rb->editor->close_edit)(rb->edit_baton) ); |
| |
| /* store auth info if we can. */ |
| SVN_ERR( svn_ra_dav__maybe_store_auth_info (rb->ras) ); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static const svn_ra_reporter_t ra_dav_reporter = { |
| reporter_set_path, |
| reporter_delete_path, |
| reporter_finish_report, |
| reporter_abort_report |
| }; |
| |
| |
| /* Make a generic reporter/baton for reporting the state of the |
| working copy during updates or status checks. */ |
| static svn_error_t * |
| make_reporter (void *session_baton, |
| const svn_ra_reporter_t **reporter, |
| void **report_baton, |
| svn_revnum_t revision, |
| svn_stringbuf_t *target, |
| const char *dst_path, |
| svn_boolean_t recurse, |
| const svn_delta_edit_fns_t *editor, |
| void *edit_baton, |
| svn_boolean_t fetch_content) |
| { |
| svn_ra_session_t *ras = session_baton; |
| report_baton_t *rb; |
| apr_status_t status; |
| const char *s; |
| const char *msg; |
| |
| /* ### create a subpool for this operation? */ |
| |
| rb = apr_pcalloc(ras->pool, sizeof(*rb)); |
| rb->ras = ras; |
| rb->editor = editor; |
| rb->edit_baton = edit_baton; |
| rb->fetch_content = fetch_content; |
| |
| /* Neon "pulls" request body content from the caller. The reporter is |
| organized where data is "pushed" into self. To match these up, we use |
| an intermediate file -- push data into the file, then let Neon pull |
| from the file. |
| |
| Note: one day we could spin up a thread and use a pipe between this |
| code and Neon. We write to a pipe, Neon reads from the pipe. Each |
| thread can block on the pipe, waiting for the other to complete its |
| work. |
| */ |
| |
| /* Use the client callback to create a tmpfile. */ |
| SVN_ERR(ras->callbacks->open_tmp_file (&rb->tmpfile, ras->callback_baton)); |
| |
| /* ### register a cleanup on our (sub)pool which removes the file. this |
| ### will ensure that the file always gets tossed, even if we exit |
| ### with an error. */ |
| |
| /* prep the file */ |
| status = apr_file_write_full(rb->tmpfile, |
| report_head, sizeof(report_head) - 1, NULL); |
| if (status) |
| { |
| msg = "Could not write the header for the temporary report file."; |
| goto error; |
| } |
| |
| /* an invalid revnum means "latest". we can just omit the target-revision |
| element in that case. */ |
| if (SVN_IS_VALID_REVNUM(revision)) |
| { |
| s = apr_psprintf(ras->pool, |
| "<S:target-revision>%ld</S:target-revision>", |
| revision); |
| status = apr_file_write_full(rb->tmpfile, s, strlen(s), NULL); |
| if (status) |
| { |
| msg = "Failed writing the target revision to the report tempfile."; |
| goto error; |
| } |
| } |
| |
| /* A NULL target is no problem. */ |
| if (target && target->data) |
| { |
| s = apr_psprintf(ras->pool, |
| "<S:update-target>%s</S:update-target>", |
| target->data); |
| status = apr_file_write_full(rb->tmpfile, s, strlen(s), NULL); |
| if (status) |
| { |
| msg = "Failed writing the target to the report tempfile."; |
| goto error; |
| } |
| } |
| |
| |
| /* A NULL dst_path is also no problem; this is only passed during a |
| 'switch' operation. If NULL, we don't mention it in the custom |
| report, and mod_dav_svn automatically runs dir_delta() on two |
| identical paths. */ |
| if (dst_path) |
| { |
| svn_stringbuf_t *dst_path_str = NULL; |
| svn_xml_escape_nts (&dst_path_str, dst_path, ras->pool); |
| |
| s = apr_psprintf(ras->pool, "<S:dst-path>%s</S:dst-path>", |
| dst_path_str->data); |
| status = apr_file_write_full(rb->tmpfile, s, strlen(s), NULL); |
| if (status) |
| { |
| msg = "Failed writing the dst-path to the report tempfile."; |
| goto error; |
| } |
| } |
| |
| /* mod_dav_svn will assume recursive, unless it finds this element. */ |
| if (!recurse) |
| { |
| const char * data = "<S:recursive>no</S:recursive>"; |
| status = apr_file_write_full(rb->tmpfile, data, strlen(data), NULL); |
| if (status) |
| { |
| msg = "Failed writing the target to the report tempfile."; |
| goto error; |
| } |
| } |
| |
| *reporter = &ra_dav_reporter; |
| *report_baton = rb; |
| |
| return SVN_NO_ERROR; |
| |
| error: |
| (void) apr_file_close(rb->tmpfile); |
| return svn_error_create(status, 0, NULL, ras->pool, msg); |
| } |
| |
| |
| svn_error_t * svn_ra_dav__do_update(void *session_baton, |
| const svn_ra_reporter_t **reporter, |
| void **report_baton, |
| svn_revnum_t revision_to_update_to, |
| svn_stringbuf_t *update_target, |
| svn_boolean_t recurse, |
| const svn_delta_edit_fns_t *wc_update, |
| void *wc_update_baton) |
| { |
| return make_reporter (session_baton, |
| reporter, |
| report_baton, |
| revision_to_update_to, |
| update_target, |
| NULL, |
| recurse, |
| wc_update, |
| wc_update_baton, |
| TRUE); /* fetch_content */ |
| } |
| |
| |
| svn_error_t * svn_ra_dav__do_status(void *session_baton, |
| const svn_ra_reporter_t **reporter, |
| void **report_baton, |
| svn_stringbuf_t *status_target, |
| svn_boolean_t recurse, |
| const svn_delta_edit_fns_t *wc_status, |
| void *wc_status_baton) |
| { |
| return make_reporter (session_baton, |
| reporter, |
| report_baton, |
| SVN_INVALID_REVNUM, |
| status_target, |
| NULL, |
| recurse, |
| wc_status, |
| wc_status_baton, |
| FALSE); /* fetch_content */ |
| } |
| |
| |
| svn_error_t * svn_ra_dav__do_switch(void *session_baton, |
| const svn_ra_reporter_t **reporter, |
| void **report_baton, |
| svn_revnum_t revision_to_update_to, |
| svn_stringbuf_t *update_target, |
| svn_boolean_t recurse, |
| svn_stringbuf_t *switch_url, |
| const svn_delta_edit_fns_t *wc_update, |
| void *wc_update_baton) |
| { |
| return make_reporter (session_baton, |
| reporter, |
| report_baton, |
| revision_to_update_to, |
| update_target, |
| switch_url->data, |
| recurse, |
| wc_update, |
| wc_update_baton, |
| TRUE); /* fetch_content */ |
| } |
| |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: |
| */ |