2 * fetch.c : routines for fetching updates and checkouts
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
22 #include <stdlib.h> /* for free() */
24 #define APR_WANT_STRFUNC
25 #include <apr_want.h> /* for strcmp() */
27 #include <apr_pools.h>
28 #include <apr_tables.h>
29 #include <apr_strings.h>
35 #include "svn_error.h"
36 #include "svn_pools.h"
37 #include "svn_delta.h"
40 #include "svn_base64.h"
42 #include "../libsvn_ra/ra_loader.h"
47 #include "svn_props.h"
49 #include "private/svn_dav_protocol.h"
50 #include "svn_private_config.h"
56 /* the information for this subdir. if rsrc==NULL, then this is a sentinel
57 record in fetch_ctx_t.subdirs to close the directory implied by the
58 parent_baton member. */
59 svn_ra_neon__resource_t
*rsrc
;
61 /* the directory containing this subdirectory. */
69 /* these two are the handler that the editor gave us */
70 svn_txdelta_window_handler_t handler
;
73 /* if we're receiving an svndiff, this is a parser which places the
74 resulting windows into the above handler/baton. */
80 svn_boolean_t do_checksum
; /* only accumulate checksum if set */
81 apr_md5_ctx_t md5_context
; /* accumulating checksum of file contents */
82 svn_stream_t
*stream
; /* stream to write file contents to */
86 svn_ra_neon__request_t
*req
; /* Used to propagate errors out of the reader */
87 int checked_type
; /* have we processed ctype yet? */
92 #define POP_SUBDIR(sds) (APR_ARRAY_IDX((sds), --(sds)->nelts, subdir_t *))
93 #define PUSH_SUBDIR(sds,s) (APR_ARRAY_PUSH((sds), subdir_t *) = (s))
95 typedef svn_error_t
* (*prop_setter_t
)(void *baton
,
97 const svn_string_t
*value
,
101 /* The baton returned by the editor's open_root/open_dir */
104 /* Should we fetch properties for this directory when the close tag
106 svn_boolean_t fetch_props
;
108 /* The version resource URL for this directory. */
111 /* A buffer which stores the relative directory name. We also use this
112 for temporary construction of relative file names. */
113 svn_stringbuf_t
*pathbuf
;
115 /* If a directory, this may contain a hash of prophashes returned
116 from doing a depth 1 PROPFIND. */
117 apr_hash_t
*children
;
119 /* A subpool. It's about memory. Ya dig? */
125 svn_ra_neon__session_t
*ras
;
129 /* The pool of the report baton; used for things that must live during the
130 whole editing operation. */
132 /* Pool initialized when the report_baton is created, and meant for
133 quick scratchwork. This is like a loop pool, but since the loop
134 that drives ra_neon callbacks is in the wrong scope for us to use
135 the normal loop pool idiom, we must resort to this. Always clear
136 this pool right after using it; only YOU can prevent forest fires. */
137 apr_pool_t
*scratch_pool
;
139 svn_boolean_t fetch_content
;
140 svn_boolean_t fetch_props
;
142 const svn_delta_editor_t
*editor
;
145 /* Stack of directory batons/vsn_urls. */
146 apr_array_header_t
*dirs
;
148 #define TOP_DIR(rb) (APR_ARRAY_IDX((rb)->dirs, (rb)->dirs->nelts - 1, \
150 #define DIR_DEPTH(rb) ((rb)->dirs->nelts)
151 #define PUSH_BATON(rb,b) (APR_ARRAY_PUSH((rb)->dirs, void *) = (b))
153 /* These items are only valid inside add- and open-file tags! */
155 apr_pool_t
*file_pool
;
156 const char *result_checksum
; /* hex md5 digest of result; may be null */
158 svn_stringbuf_t
*namestr
;
159 svn_stringbuf_t
*cpathstr
;
160 svn_stringbuf_t
*href
;
162 /* Empty string means no encoding, "base64" means base64. */
163 svn_stringbuf_t
*encoding
;
165 /* These are used when receiving an inline txdelta, and null at all
167 svn_txdelta_window_handler_t whandler
;
168 void *whandler_baton
;
169 svn_stream_t
*svndiff_decoder
;
170 svn_stream_t
*base64_decoder
;
172 /* A generic accumulator for elements that have small bits of cdata,
173 like md5_checksum, href, etc. Uh, or where our own API gives us
174 no choice about holding them in memory, as with prop values, ahem.
175 This is always the empty stringbuf when not in use. */
176 svn_stringbuf_t
*cdata_accum
;
178 /* Are we inside a resource element? */
179 svn_boolean_t in_resource
;
180 /* Valid if in_resource is true. */
181 svn_stringbuf_t
*current_wcprop_path
;
182 svn_boolean_t is_switch
;
184 /* Named target, or NULL if none. For example, in 'svn up wc/foo',
185 this is "wc/foo", but in 'svn up' it is "".
187 The target helps us determine whether a response received from
188 the server should be acted on. Take 'svn up wc/foo': the server
189 may send back a new vsn-rsrc-url wcprop for 'wc' (because the
190 report had to be anchored there just in case the update deletes
191 wc/foo). While this is correct behavior for the server, the
192 client should ignore the new wcprop, because the client knows
193 it's not really updating the top level directory. */
196 /* Whether the server should try to send copyfrom arguments. */
197 svn_boolean_t send_copyfrom_args
;
199 /* Use an intermediate tmpfile for the REPORT response. */
200 svn_boolean_t spool_response
;
202 /* A modern server will understand our "send-all" attribute on the
203 update report request, and will put a "send-all" attribute on
204 its response. If we see that attribute, we set this to true,
205 otherwise, it stays false (i.e., it's not a modern server). */
206 svn_boolean_t receiving_all
;
210 static const svn_ra_neon__xml_elm_t report_elements
[] =
212 { SVN_XML_NAMESPACE
, "update-report", ELEM_update_report
, 0 },
213 { SVN_XML_NAMESPACE
, "resource-walk", ELEM_resource_walk
, 0 },
214 { SVN_XML_NAMESPACE
, "resource", ELEM_resource
, 0 },
215 { SVN_XML_NAMESPACE
, "target-revision", ELEM_target_revision
, 0 },
216 { SVN_XML_NAMESPACE
, "open-directory", ELEM_open_directory
, 0 },
217 { SVN_XML_NAMESPACE
, "add-directory", ELEM_add_directory
, 0 },
218 { SVN_XML_NAMESPACE
, "absent-directory", ELEM_absent_directory
, 0 },
219 { SVN_XML_NAMESPACE
, "open-file", ELEM_open_file
, 0 },
220 { SVN_XML_NAMESPACE
, "add-file", ELEM_add_file
, 0 },
221 { SVN_XML_NAMESPACE
, "txdelta", ELEM_txdelta
, 0 },
222 { SVN_XML_NAMESPACE
, "absent-file", ELEM_absent_file
, 0 },
223 { SVN_XML_NAMESPACE
, "delete-entry", ELEM_delete_entry
, 0 },
224 { SVN_XML_NAMESPACE
, "fetch-props", ELEM_fetch_props
, 0 },
225 { SVN_XML_NAMESPACE
, "set-prop", ELEM_set_prop
, 0 },
226 { SVN_XML_NAMESPACE
, "remove-prop", ELEM_remove_prop
, 0 },
227 { SVN_XML_NAMESPACE
, "fetch-file", ELEM_fetch_file
, 0 },
228 { SVN_XML_NAMESPACE
, "prop", ELEM_SVN_prop
, 0 },
229 { SVN_DAV_PROP_NS_DAV
, "repository-uuid",
230 ELEM_repository_uuid
, SVN_RA_NEON__XML_CDATA
},
232 { SVN_DAV_PROP_NS_DAV
, "md5-checksum", ELEM_md5_checksum
,
233 SVN_RA_NEON__XML_CDATA
},
235 { "DAV:", "version-name", ELEM_version_name
, SVN_RA_NEON__XML_CDATA
},
236 { "DAV:", SVN_DAV__CREATIONDATE
, ELEM_creationdate
, SVN_RA_NEON__XML_CDATA
},
237 { "DAV:", "creator-displayname", ELEM_creator_displayname
,
238 SVN_RA_NEON__XML_CDATA
},
240 { "DAV:", "checked-in", ELEM_checked_in
, 0 },
241 { "DAV:", "href", ELEM_href
, SVN_RA_NEON__XML_CDATA
},
246 static svn_error_t
*simple_store_vsn_url(const char *vsn_url
,
248 prop_setter_t setter
,
251 /* store the version URL as a property */
252 SVN_ERR_W((*setter
)(baton
, SVN_RA_NEON__LP_VSN_URL
,
253 svn_string_create(vsn_url
, pool
), pool
),
254 _("Could not save the URL of the version resource"));
259 static svn_error_t
*get_delta_base(const char **delta_base
,
261 svn_ra_get_wc_prop_func_t get_wc_prop
,
265 const svn_string_t
*value
;
267 if (relpath
== NULL
|| get_wc_prop
== NULL
)
273 SVN_ERR((*get_wc_prop
)(cb_baton
, relpath
, SVN_RA_NEON__LP_VSN_URL
,
276 *delta_base
= value
? value
->data
: NULL
;
280 /* helper func which maps certain DAV: properties to svn:wc:
281 properties. Used during checkouts and updates. */
282 static svn_error_t
*set_special_wc_prop(const char *key
,
283 const svn_string_t
*val
,
284 prop_setter_t setter
,
288 const char *name
= NULL
;
290 if (strcmp(key
, SVN_RA_NEON__PROP_VERSION_NAME
) == 0)
291 name
= SVN_PROP_ENTRY_COMMITTED_REV
;
292 else if (strcmp(key
, SVN_RA_NEON__PROP_CREATIONDATE
) == 0)
293 name
= SVN_PROP_ENTRY_COMMITTED_DATE
;
294 else if (strcmp(key
, SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME
) == 0)
295 name
= SVN_PROP_ENTRY_LAST_AUTHOR
;
296 else if (strcmp(key
, SVN_RA_NEON__PROP_REPOSITORY_UUID
) == 0)
297 name
= SVN_PROP_ENTRY_UUID
;
299 /* If we got a name we care about it, call the setter function. */
301 SVN_ERR((*setter
)(baton
, name
, val
, pool
));
307 static svn_error_t
*add_props(apr_hash_t
*props
,
308 prop_setter_t setter
,
312 apr_hash_index_t
*hi
;
314 for (hi
= apr_hash_first(pool
, props
); hi
; hi
= apr_hash_next(hi
))
319 const svn_string_t
*val
;
321 apr_hash_this(hi
, &vkey
, NULL
, &vval
);
325 #define NSLEN (sizeof(SVN_DAV_PROP_NS_CUSTOM) - 1)
326 if (strncmp(key
, SVN_DAV_PROP_NS_CUSTOM
, NSLEN
) == 0)
328 /* for props in the 'custom' namespace, we strip the
329 namespace and just use whatever name the user gave the
331 SVN_ERR((*setter
)(baton
, key
+ NSLEN
, val
, pool
));
336 #define NSLEN (sizeof(SVN_DAV_PROP_NS_SVN) - 1)
337 if (strncmp(key
, SVN_DAV_PROP_NS_SVN
, NSLEN
) == 0)
339 /* This property is an 'svn:' prop, recognized by client, or
340 server, or both. Convert the URI namespace into normal
341 'svn:' prefix again before pushing it at the wc. */
342 SVN_ERR((*setter
)(baton
, apr_pstrcat(pool
, SVN_PROP_PREFIX
,
350 /* If we get here, then we have a property that is neither
351 in the 'custom' space, nor in the 'svn' space. So it
352 must be either in the 'network' space or 'DAV:' space.
353 The following routine converts a handful of DAV: props
354 into 'svn:wc:' or 'svn:entry:' props that libsvn_wc
356 SVN_ERR(set_special_wc_prop(key
, val
, setter
, baton
, pool
));
363 static svn_error_t
*custom_get_request(svn_ra_neon__session_t
*ras
,
366 svn_ra_neon__block_reader reader
,
368 svn_ra_get_wc_prop_func_t get_wc_prop
,
370 svn_boolean_t use_base
,
373 custom_get_ctx_t cgc
= { 0 };
374 const char *delta_base
;
375 svn_ra_neon__request_t
*request
;
380 /* See if we can get a version URL for this resource. This will
381 refer to what we already have in the working copy, thus we
382 can get a diff against this particular resource. */
383 SVN_ERR(get_delta_base(&delta_base
, relpath
,
384 get_wc_prop
, cb_baton
, pool
));
391 request
= svn_ra_neon__request_create(ras
, "GET", url
, pool
);
395 /* The HTTP delta draft uses an If-None-Match header holding an
396 entity tag corresponding to the copy we have. It is much more
397 natural for us to use a version URL to specify what we have.
398 Thus, we want to use the If: header to specify the URL. But
399 mod_dav sees all "State-token" items as lock tokens. When we
400 get mod_dav updated and the backend APIs expanded, then we
401 can switch to using the If: header. For now, use a custom
402 header to specify the version resource to use as the base. */
403 ne_add_request_header(request
->ne_req
,
404 SVN_DAV_DELTA_BASE_HEADER
, delta_base
);
407 svn_ra_neon__add_response_body_reader(request
, ne_accept_2xx
, reader
, &cgc
);
409 /* complete initialization of the body reading context */
413 /* run the request */
414 err
= svn_ra_neon__request_dispatch(NULL
, request
, NULL
, NULL
,
418 svn_ra_neon__request_destroy(request
);
420 /* The request runner raises internal errors before Neon errors,
421 pass a returned error to our callers */
426 /* This implements the svn_ra_neon__block_reader() callback interface. */
428 fetch_file_reader(void *userdata
, const char *buf
, size_t len
)
430 custom_get_ctx_t
*cgc
= userdata
;
431 file_read_ctx_t
*frc
= cgc
->subctx
;
435 /* file is complete. */
439 if (!cgc
->checked_type
)
441 ne_content_type ctype
= { 0 };
442 int rv
= ne_get_content_type(cgc
->req
->ne_req
, &ctype
);
446 svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS
, NULL
,
447 _("Could not get content-type from response"));
449 /* Neon guarantees non-NULL values when rv==0 */
450 if (!strcmp(ctype
.type
, "application")
451 && !strcmp(ctype
.subtype
, "vnd.svn-svndiff"))
453 /* we are receiving an svndiff. set things up. */
454 frc
->stream
= svn_txdelta_parse_svndiff(frc
->handler
,
463 cgc
->checked_type
= 1;
466 if (frc
->stream
== NULL
)
468 /* receiving plain text. construct a window for it. */
470 svn_txdelta_window_t window
= { 0 };
477 op
.action_code
= svn_txdelta_new
;
481 window
.tview_len
= len
; /* result will be this long */
484 window
.new_data
= &data
;
486 /* We can't really do anything useful if we get an error here. Pass
487 it off to someone who can. */
490 (*frc
->handler
)(&window
, frc
->handler_baton
));
494 /* receiving svndiff. feed it to the svndiff parser. */
496 apr_size_t written
= len
;
498 SVN_ERR(svn_stream_write(frc
->stream
, buf
, &written
));
500 /* ### the svndiff stream parser does not obey svn_stream semantics
501 ### in its write handler. it does not output the number of bytes
502 ### consumed by the handler. specifically, it may decrement the
503 ### number by 4 for the header, then never touch it again. that
504 ### makes it appear like an incomplete write.
505 ### disable this check for now. the svndiff parser actually does
506 ### consume all bytes, all the time.
509 if (written
!= len
&& cgc
->err
== NULL
)
510 cgc
->err
= svn_error_createf(SVN_ERR_INCOMPLETE_DATA
, NULL
,
511 "Unable to completely write the svndiff "
512 "data to the parser stream "
513 "(wrote " APR_SIZE_T_FMT
" "
514 "of " APR_SIZE_T_FMT
" bytes)",
522 static svn_error_t
*simple_fetch_file(svn_ra_neon__session_t
*ras
,
525 svn_boolean_t text_deltas
,
527 const char *base_checksum
,
528 const svn_delta_editor_t
*editor
,
529 svn_ra_get_wc_prop_func_t get_wc_prop
,
533 file_read_ctx_t frc
= { 0 };
535 SVN_ERR_W((*editor
->apply_textdelta
)(file_baton
,
540 _("Could not save file"));
542 /* Only bother with text-deltas if our caller cares. */
545 SVN_ERR((*frc
.handler
)(NULL
, frc
.handler_baton
));
551 SVN_ERR(custom_get_request(ras
, url
, relpath
,
552 fetch_file_reader
, &frc
,
553 get_wc_prop
, cb_baton
,
556 /* close the handler, since the file reading completed successfully. */
557 SVN_ERR((*frc
.handler
)(NULL
, frc
.handler_baton
));
562 /* Helper for svn_ra_neon__get_file. This implements
563 the svn_ra_neon__block_reader() callback interface. */
565 get_file_reader(void *userdata
, const char *buf
, size_t len
)
567 custom_get_ctx_t
*cgc
= userdata
;
569 /* The stream we want to push data at. */
570 file_write_ctx_t
*fwc
= cgc
->subctx
;
571 svn_stream_t
*stream
= fwc
->stream
;
573 if (fwc
->do_checksum
)
574 apr_md5_update(&(fwc
->md5_context
), buf
, len
);
576 /* Write however many bytes were passed in by neon. */
577 SVN_ERR(svn_stream_write(stream
, buf
, &len
));
583 /* minor helper for svn_ra_neon__get_file, of type prop_setter_t */
585 add_prop_to_hash(void *baton
,
587 const svn_string_t
*value
,
590 apr_hash_t
*ht
= (apr_hash_t
*) baton
;
591 apr_hash_set(ht
, name
, APR_HASH_KEY_STRING
, value
);
596 /* Helper for svn_ra_neon__get_file(), svn_ra_neon__get_dir(), and
597 svn_ra_neon__rev_proplist().
599 Loop over the properties in RSRC->propset, examining namespaces and
600 such to filter Subversion, custom, etc. properties.
602 User-visible props get added to the PROPS hash (alloced in POOL).
604 If ADD_ENTRY_PROPS is true, then "special" working copy entry-props
605 are added to the hash by set_special_wc_prop().
608 filter_props(apr_hash_t
*props
,
609 svn_ra_neon__resource_t
*rsrc
,
610 svn_boolean_t add_entry_props
,
613 apr_hash_index_t
*hi
;
615 for (hi
= apr_hash_first(pool
, rsrc
->propset
); hi
; hi
= apr_hash_next(hi
))
620 const svn_string_t
*value
;
622 apr_hash_this(hi
, &key
, NULL
, &val
);
624 value
= svn_string_dup(val
, pool
);
626 /* If the property is in the 'custom' namespace, then it's a
627 normal user-controlled property coming from the fs. Just
628 strip off this prefix and add to the hash. */
629 #define NSLEN (sizeof(SVN_DAV_PROP_NS_CUSTOM) - 1)
630 if (strncmp(name
, SVN_DAV_PROP_NS_CUSTOM
, NSLEN
) == 0)
632 apr_hash_set(props
, name
+ NSLEN
, APR_HASH_KEY_STRING
, value
);
637 /* If the property is in the 'svn' namespace, then it's a
638 normal user-controlled property coming from the fs. Just
639 strip off the URI prefix, add an 'svn:', and add to the hash. */
640 #define NSLEN (sizeof(SVN_DAV_PROP_NS_SVN) - 1)
641 if (strncmp(name
, SVN_DAV_PROP_NS_SVN
, NSLEN
) == 0)
644 apr_pstrcat(pool
, SVN_PROP_PREFIX
, name
+ NSLEN
, NULL
),
650 else if (strcmp(name
, SVN_RA_NEON__PROP_CHECKED_IN
) == 0)
652 /* For files, we currently only have one 'wc' prop. */
653 apr_hash_set(props
, SVN_RA_NEON__LP_VSN_URL
,
654 APR_HASH_KEY_STRING
, value
);
658 /* If it's one of the 'entry' props, this func will
659 recognize the DAV: name & add it to the hash mapped to a
660 new name recognized by libsvn_wc. */
662 SVN_ERR(set_special_wc_prop(name
, value
, add_prop_to_hash
,
670 svn_error_t
*svn_ra_neon__get_file(svn_ra_session_t
*session
,
672 svn_revnum_t revision
,
673 svn_stream_t
*stream
,
674 svn_revnum_t
*fetched_rev
,
678 svn_ra_neon__resource_t
*rsrc
;
679 const char *final_url
;
680 svn_ra_neon__session_t
*ras
= session
->priv
;
681 const char *url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
683 /* If the revision is invalid (head), then we're done. Just fetch
684 the public URL, because that will always get HEAD. */
685 if ((! SVN_IS_VALID_REVNUM(revision
)) && (fetched_rev
== NULL
))
688 /* If the revision is something specific, we need to create a bc_url. */
691 svn_revnum_t got_rev
;
692 svn_string_t bc_url
, bc_relative
;
694 SVN_ERR(svn_ra_neon__get_baseline_info(NULL
,
695 &bc_url
, &bc_relative
,
700 final_url
= svn_path_url_add_component(bc_url
.data
,
703 if (fetched_rev
!= NULL
)
704 *fetched_rev
= got_rev
;
710 const svn_string_t
*expected_checksum
= NULL
;
711 file_write_ctx_t fwc
;
712 ne_propname md5_propname
= { SVN_DAV_PROP_NS_DAV
, "md5-checksum" };
713 unsigned char digest
[APR_MD5_DIGESTSIZE
];
714 const char *hex_digest
;
716 /* Only request a checksum if we're getting the file contents. */
717 /* ### We should arrange for the checksum to be returned in the
718 svn_ra_neon__get_baseline_info() call above; that will prevent
719 the extra round trip, at least some of the time. */
720 err
= svn_ra_neon__get_one_prop(&expected_checksum
,
727 /* Older servers don't serve this prop, but that's okay. */
728 /* ### temporary hack for 0.17. if the server doesn't have the prop,
729 ### then __get_one_prop returns an empty string. deal with it. */
730 if ((err
&& (err
->apr_err
== SVN_ERR_RA_DAV_PROPS_NOT_FOUND
))
731 || (expected_checksum
&& (*expected_checksum
->data
== '\0')))
733 fwc
.do_checksum
= FALSE
;
734 svn_error_clear(err
);
739 fwc
.do_checksum
= TRUE
;
744 apr_md5_init(&(fwc
.md5_context
));
746 /* Fetch the file, shoving it at the provided stream. */
747 SVN_ERR(custom_get_request(ras
, final_url
, path
,
748 get_file_reader
, &fwc
,
749 ras
->callbacks
->get_wc_prop
,
755 apr_md5_final(digest
, &(fwc
.md5_context
));
756 hex_digest
= svn_md5_digest_to_cstring_display(digest
, pool
);
758 if (strcmp(hex_digest
, expected_checksum
->data
) != 0)
759 return svn_error_createf
760 (SVN_ERR_CHECKSUM_MISMATCH
, NULL
,
761 _("Checksum mismatch for '%s':\n"
762 " expected checksum: %s\n"
763 " actual checksum: %s\n"),
764 path
, expected_checksum
->data
, hex_digest
);
770 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc
, ras
, final_url
,
771 NULL
, NULL
/* all props */,
773 *props
= apr_hash_make(pool
);
774 SVN_ERR(filter_props(*props
, rsrc
, TRUE
, pool
));
780 /* The property we need to fetch to see whether the server we are
781 connected to supports the deadprop-count property. */
782 static const ne_propname deadprop_count_support_props
[] =
784 { SVN_DAV_PROP_NS_DAV
, "deadprop-count" },
788 svn_error_t
*svn_ra_neon__get_dir(svn_ra_session_t
*session
,
789 apr_hash_t
**dirents
,
790 svn_revnum_t
*fetched_rev
,
793 svn_revnum_t revision
,
794 apr_uint32_t dirent_fields
,
797 svn_ra_neon__resource_t
*rsrc
;
798 apr_hash_index_t
*hi
;
799 apr_hash_t
*resources
;
800 const char *final_url
;
801 apr_size_t final_url_n_components
;
802 svn_boolean_t supports_deadprop_count
;
803 svn_ra_neon__session_t
*ras
= session
->priv
;
804 const char *url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
806 /* If the revision is invalid (head), then we're done. Just fetch
807 the public URL, because that will always get HEAD. */
808 if ((! SVN_IS_VALID_REVNUM(revision
)) && (fetched_rev
== NULL
))
811 /* If the revision is something specific, we need to create a bc_url. */
814 svn_revnum_t got_rev
;
815 svn_string_t bc_url
, bc_relative
;
817 SVN_ERR(svn_ra_neon__get_baseline_info(NULL
,
818 &bc_url
, &bc_relative
,
823 final_url
= svn_path_url_add_component(bc_url
.data
,
826 if (fetched_rev
!= NULL
)
827 *fetched_rev
= got_rev
;
830 /* For issue 2151: See if we are dealing with a server that
831 understands the deadprop-count property. If it doesn't, we'll
832 need to do an allprop PROPFIND. If it does, we'll execute a more
833 targeted PROPFIND. */
835 const svn_string_t
*deadprop_count
;
837 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc
, ras
,
839 deadprop_count_support_props
,
842 deadprop_count
= apr_hash_get(rsrc
->propset
,
843 SVN_RA_NEON__PROP_DEADPROP_COUNT
,
844 APR_HASH_KEY_STRING
);
846 supports_deadprop_count
= (deadprop_count
!= NULL
);
851 ne_propname
*which_props
;
853 /* if we didn't ask for the has_props field, we can get individual
855 if ((SVN_DIRENT_HAS_PROPS
& dirent_fields
) == 0
856 || supports_deadprop_count
)
858 int num_props
= 1; /* start with one for the final NULL */
860 if (dirent_fields
& SVN_DIRENT_KIND
)
863 if (dirent_fields
& SVN_DIRENT_SIZE
)
866 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
869 if (dirent_fields
& SVN_DIRENT_CREATED_REV
)
872 if (dirent_fields
& SVN_DIRENT_TIME
)
875 if (dirent_fields
& SVN_DIRENT_LAST_AUTHOR
)
878 which_props
= apr_pcalloc(pool
, num_props
* sizeof(ne_propname
));
880 --num_props
; /* damn zero based arrays... */
882 /* first, null out the end... */
883 which_props
[num_props
].nspace
= NULL
;
884 which_props
[num_props
--].name
= NULL
;
886 /* Now, go through and fill in the ones we care about, moving along
887 the array as we go. */
889 if (dirent_fields
& SVN_DIRENT_KIND
)
891 which_props
[num_props
].nspace
= "DAV:";
892 which_props
[num_props
--].name
= "resourcetype";
895 if (dirent_fields
& SVN_DIRENT_SIZE
)
897 which_props
[num_props
].nspace
= "DAV:";
898 which_props
[num_props
--].name
= "getcontentlength";
901 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
903 which_props
[num_props
].nspace
= SVN_DAV_PROP_NS_DAV
;
904 which_props
[num_props
--].name
= "deadprop-count";
907 if (dirent_fields
& SVN_DIRENT_CREATED_REV
)
909 which_props
[num_props
].nspace
= "DAV:";
910 which_props
[num_props
--].name
= "version-name";
913 if (dirent_fields
& SVN_DIRENT_TIME
)
915 which_props
[num_props
].nspace
= "DAV:";
916 which_props
[num_props
--].name
= SVN_DAV__CREATIONDATE
;
919 if (dirent_fields
& SVN_DIRENT_LAST_AUTHOR
)
921 which_props
[num_props
].nspace
= "DAV:";
922 which_props
[num_props
--].name
= "creator-displayname";
925 assert(num_props
== -1);
929 /* get all props, since we need them all to do has_props */
933 /* Just like Nautilus, Cadaver, or any other browser, we do a
934 PROPFIND on the directory of depth 1. */
935 SVN_ERR(svn_ra_neon__get_props(&resources
, ras
,
936 final_url
, SVN_RA_NEON__DEPTH_ONE
,
937 NULL
, which_props
, pool
));
939 /* Count the number of path components in final_url. */
940 final_url_n_components
= svn_path_component_count(final_url
);
942 /* Now we have a hash that maps a bunch of url children to resource
943 objects. Each resource object contains the properties of the
944 child. Parse these resources into svn_dirent_t structs. */
945 *dirents
= apr_hash_make(pool
);
946 for (hi
= apr_hash_first(pool
, resources
);
948 hi
= apr_hash_next(hi
))
952 const char *childname
;
953 svn_ra_neon__resource_t
*resource
;
954 const svn_string_t
*propval
;
958 apr_hash_this(hi
, &key
, NULL
, &val
);
962 /* Skip the effective '.' entry that comes back from
963 SVN_RA_NEON__DEPTH_ONE. The children must have one more
964 component then final_url.
965 Note that we can't just strcmp the URLs because of URL encoding
966 differences (i.e. %3c vs. %3C etc.) */
967 if (svn_path_component_count(childname
) == final_url_n_components
)
970 entry
= apr_pcalloc(pool
, sizeof(*entry
));
972 if (dirent_fields
& SVN_DIRENT_KIND
)
975 entry
->kind
= resource
->is_collection
? svn_node_dir
979 if (dirent_fields
& SVN_DIRENT_SIZE
)
982 propval
= apr_hash_get(resource
->propset
,
983 SVN_RA_NEON__PROP_GETCONTENTLENGTH
,
984 APR_HASH_KEY_STRING
);
988 entry
->size
= svn__atoui64(propval
->data
);
991 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
993 /* Does this resource contain any 'svn' or 'custom'
994 properties (e.g. ones actually created and set by the
996 if (supports_deadprop_count
)
998 propval
= apr_hash_get(resource
->propset
,
999 SVN_RA_NEON__PROP_DEADPROP_COUNT
,
1000 APR_HASH_KEY_STRING
);
1002 if (propval
== NULL
)
1004 /* we thought that the server supported the
1005 deadprop-count property. apparently not. */
1006 return svn_error_create(SVN_ERR_INCOMPLETE_DATA
, NULL
,
1007 _("Server response missing the "
1008 "expected deadprop-count "
1013 apr_int64_t prop_count
= svn__atoui64(propval
->data
);
1014 entry
->has_props
= (prop_count
> 0);
1019 /* The server doesn't support the deadprop_count prop,
1021 for (h
= apr_hash_first(pool
, resource
->propset
);
1022 h
; h
= apr_hash_next(h
))
1025 apr_hash_this(h
, &kkey
, NULL
, NULL
);
1027 if (strncmp((const char *) kkey
, SVN_DAV_PROP_NS_CUSTOM
,
1028 sizeof(SVN_DAV_PROP_NS_CUSTOM
) - 1) == 0
1029 || strncmp((const char *) kkey
, SVN_DAV_PROP_NS_SVN
,
1030 sizeof(SVN_DAV_PROP_NS_SVN
) - 1) == 0)
1031 entry
->has_props
= TRUE
;
1036 if (dirent_fields
& SVN_DIRENT_CREATED_REV
)
1038 /* created_rev & friends */
1039 propval
= apr_hash_get(resource
->propset
,
1040 SVN_RA_NEON__PROP_VERSION_NAME
,
1041 APR_HASH_KEY_STRING
);
1042 if (propval
!= NULL
)
1043 entry
->created_rev
= SVN_STR_TO_REV(propval
->data
);
1046 if (dirent_fields
& SVN_DIRENT_TIME
)
1048 propval
= apr_hash_get(resource
->propset
,
1049 SVN_RA_NEON__PROP_CREATIONDATE
,
1050 APR_HASH_KEY_STRING
);
1051 if (propval
!= NULL
)
1052 SVN_ERR(svn_time_from_cstring(&(entry
->time
),
1053 propval
->data
, pool
));
1056 if (dirent_fields
& SVN_DIRENT_LAST_AUTHOR
)
1058 propval
= apr_hash_get(resource
->propset
,
1059 SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME
,
1060 APR_HASH_KEY_STRING
);
1061 if (propval
!= NULL
)
1062 entry
->last_author
= propval
->data
;
1065 apr_hash_set(*dirents
,
1066 svn_path_uri_decode(svn_path_basename(childname
, pool
),
1068 APR_HASH_KEY_STRING
, entry
);
1074 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc
, ras
, final_url
,
1075 NULL
, NULL
/* all props */,
1078 *props
= apr_hash_make(pool
);
1079 SVN_ERR(filter_props(*props
, rsrc
, TRUE
, pool
));
1082 return SVN_NO_ERROR
;
1086 /* ------------------------------------------------------------------------- */
1088 svn_error_t
*svn_ra_neon__get_latest_revnum(svn_ra_session_t
*session
,
1089 svn_revnum_t
*latest_revnum
,
1092 svn_ra_neon__session_t
*ras
= session
->priv
;
1094 /* ### should we perform an OPTIONS to validate the server we're about
1097 /* we don't need any of the baseline URLs and stuff, but this does
1098 give us the latest revision number */
1099 SVN_ERR(svn_ra_neon__get_baseline_info(NULL
, NULL
, NULL
, latest_revnum
,
1100 ras
, ras
->root
.path
,
1101 SVN_INVALID_REVNUM
, pool
));
1103 SVN_ERR(svn_ra_neon__maybe_store_auth_info(ras
, pool
));
1108 /* ------------------------------------------------------------------------- */
1111 svn_error_t
*svn_ra_neon__change_rev_prop(svn_ra_session_t
*session
,
1114 const svn_string_t
*value
,
1117 svn_ra_neon__session_t
*ras
= session
->priv
;
1118 svn_ra_neon__resource_t
*baseline
;
1120 apr_hash_t
*prop_changes
= NULL
;
1121 apr_array_header_t
*prop_deletes
= NULL
;
1122 static const ne_propname wanted_props
[] =
1124 { "DAV:", "auto-version" },
1128 /* Main objective: do a PROPPATCH (allprops) on a baseline object */
1130 /* ### A Word From Our Sponsor: see issue #916.
1132 Be it heretofore known that this Subversion behavior is
1133 officially in violation of WebDAV/DeltaV. DeltaV has *no*
1134 concept of unversioned properties, anywhere. If you proppatch
1135 something, some new version of *something* is created.
1137 In particular, we've decided that a 'baseline' maps to an svn
1138 revision; if we attempted to proppatch a baseline, a *normal*
1139 DeltaV server would do an auto-checkout, patch the working
1140 baseline, auto-checkin, and create a new baseline. But
1141 mod_dav_svn just changes the baseline destructively.
1144 /* Get the baseline resource. */
1145 SVN_ERR(svn_ra_neon__get_baseline_props(NULL
, &baseline
,
1149 wanted_props
, /* DAV:auto-version */
1152 /* ### TODO: if we got back some value for the baseline's
1153 'DAV:auto-version' property, interpret it. We *don't* want
1154 to attempt the PROPPATCH if the deltaV server is going to do
1155 auto-versioning and create a new baseline! */
1159 prop_changes
= apr_hash_make(pool
);
1160 apr_hash_set(prop_changes
, name
, APR_HASH_KEY_STRING
, value
);
1164 prop_deletes
= apr_array_make(pool
, 1, sizeof(const char *));
1165 APR_ARRAY_PUSH(prop_deletes
, const char *) = name
;
1168 err
= svn_ra_neon__do_proppatch(ras
, baseline
->url
, prop_changes
,
1169 prop_deletes
, NULL
, pool
);
1173 (SVN_ERR_RA_DAV_REQUEST_FAILED
, err
,
1174 _("DAV request failed; it's possible that the repository's "
1175 "pre-revprop-change hook either failed or is non-existent"));
1177 return SVN_NO_ERROR
;
1181 svn_error_t
*svn_ra_neon__rev_proplist(svn_ra_session_t
*session
,
1186 svn_ra_neon__session_t
*ras
= session
->priv
;
1187 svn_ra_neon__resource_t
*baseline
;
1189 *props
= apr_hash_make(pool
);
1191 /* Main objective: do a PROPFIND (allprops) on a baseline object */
1192 SVN_ERR(svn_ra_neon__get_baseline_props(NULL
, &baseline
,
1196 NULL
, /* get ALL properties */
1199 /* Build a new property hash, based on the one in the baseline
1200 resource. In particular, convert the xml-property-namespaces
1201 into ones that the client understands. Strip away the DAV:
1202 liveprops as well. */
1203 SVN_ERR(filter_props(*props
, baseline
, FALSE
, pool
));
1205 return SVN_NO_ERROR
;
1209 svn_error_t
*svn_ra_neon__rev_prop(svn_ra_session_t
*session
,
1212 svn_string_t
**value
,
1217 /* We just call svn_ra_neon__rev_proplist() and filter its results here
1218 * because sending the property name to the server may create an error
1219 * if it has a colon in its name. While more costly this allows DAV
1220 * clients to still gain access to all the allowed property names.
1221 * See Issue #1807 for more details. */
1222 SVN_ERR(svn_ra_neon__rev_proplist(session
, rev
, &props
, pool
));
1224 *value
= apr_hash_get(props
, name
, APR_HASH_KEY_STRING
);
1226 return SVN_NO_ERROR
;
1232 /* -------------------------------------------------------------------------
1238 ** DTD of the update report:
1239 ** ### open/add file/dir. first child is always checked-in/href (vsn_url).
1240 ** ### next are subdir elems, possibly fetch-file, then fetch-prop.
1243 /* Determine whether we're receiving the expected XML response.
1244 Return CHILD when interested in receiving the child's contents
1245 or one of SVN_RA_NEON__XML_INVALID and SVN_RA_NEON__XML_DECLINE
1246 when respectively this is the incorrect response or
1247 the element (and its children) are uninteresting */
1248 static int validate_element(svn_ra_neon__xml_elmid parent
,
1249 svn_ra_neon__xml_elmid child
)
1251 /* We're being very strict with the validity of XML elements here. If
1252 something exists that we don't know about, then we might not update
1253 the client properly. We also make various assumptions in the element
1254 processing functions, and the strong validation enables those
1260 if (child
== ELEM_update_report
)
1263 return SVN_RA_NEON__XML_INVALID
;
1265 case ELEM_update_report
:
1266 if (child
== ELEM_target_revision
1267 || child
== ELEM_open_directory
1268 || child
== ELEM_resource_walk
)
1271 return SVN_RA_NEON__XML_INVALID
;
1273 case ELEM_resource_walk
:
1274 if (child
== ELEM_resource
)
1277 return SVN_RA_NEON__XML_INVALID
;
1280 if (child
== ELEM_checked_in
)
1283 return SVN_RA_NEON__XML_INVALID
;
1285 case ELEM_open_directory
:
1286 if (child
== ELEM_absent_directory
1287 || child
== ELEM_open_directory
1288 || child
== ELEM_add_directory
1289 || child
== ELEM_absent_file
1290 || child
== ELEM_open_file
1291 || child
== ELEM_add_file
1292 || child
== ELEM_fetch_props
1293 || child
== ELEM_set_prop
1294 || child
== ELEM_remove_prop
1295 || child
== ELEM_delete_entry
1296 || child
== ELEM_SVN_prop
1297 || child
== ELEM_checked_in
)
1300 return SVN_RA_NEON__XML_INVALID
;
1302 case ELEM_add_directory
:
1303 if (child
== ELEM_absent_directory
1304 || child
== ELEM_add_directory
1305 || child
== ELEM_absent_file
1306 || child
== ELEM_add_file
1307 || child
== ELEM_set_prop
1308 || child
== ELEM_SVN_prop
1309 || child
== ELEM_checked_in
)
1312 return SVN_RA_NEON__XML_INVALID
;
1314 case ELEM_open_file
:
1315 if (child
== ELEM_checked_in
1316 || child
== ELEM_fetch_file
1317 || child
== ELEM_SVN_prop
1318 || child
== ELEM_txdelta
1319 || child
== ELEM_fetch_props
1320 || child
== ELEM_set_prop
1321 || child
== ELEM_remove_prop
)
1324 return SVN_RA_NEON__XML_INVALID
;
1327 if (child
== ELEM_checked_in
1328 || child
== ELEM_txdelta
1329 || child
== ELEM_set_prop
1330 || child
== ELEM_SVN_prop
)
1333 return SVN_RA_NEON__XML_INVALID
;
1335 case ELEM_checked_in
:
1336 if (child
== ELEM_href
)
1339 return SVN_RA_NEON__XML_INVALID
;
1342 /* Prop name is an attribute, prop value is CDATA, so no child elts. */
1346 /* if (child == ELEM_version_name
1347 || child == ELEM_creationdate
1348 || child == ELEM_creator_displayname
1349 || child == ELEM_md5_checksum
1350 || child == ELEM_repository_uuid
1351 || child == ELEM_remove_prop)
1354 return SVN_RA_NEON__XML_DECLINE;
1356 /* ### TODO: someday uncomment the block above, and make the
1357 else clause return NE_XML_IGNORE. But first, neon needs to
1358 define that value. :-) */
1362 return SVN_RA_NEON__XML_DECLINE
;
1368 static void push_dir(report_baton_t
*rb
,
1370 svn_stringbuf_t
*pathbuf
,
1373 dir_item_t
*di
= apr_array_push(rb
->dirs
);
1375 memset(di
, 0, sizeof(*di
));
1377 di
->pathbuf
= pathbuf
;
1381 /* This implements the `ne_xml_startelm_cb' prototype. */
1382 static svn_error_t
*
1383 start_element(int *elem
, void *userdata
, int parent
, const char *nspace
,
1384 const char *elt_name
, const char **atts
)
1386 report_baton_t
*rb
= userdata
;
1391 svn_stringbuf_t
*cpath
= NULL
;
1392 svn_revnum_t crev
= SVN_INVALID_REVNUM
;
1393 dir_item_t
*parent_dir
;
1394 void *new_dir_baton
;
1395 svn_stringbuf_t
*pathbuf
;
1396 apr_pool_t
*subpool
;
1397 const char *base_checksum
= NULL
;
1398 const svn_ra_neon__xml_elm_t
*elm
;
1400 elm
= svn_ra_neon__lookup_xml_elem(report_elements
, nspace
, elt_name
);
1401 *elem
= elm
? validate_element(parent
, elm
->id
) : SVN_RA_NEON__XML_DECLINE
;
1402 if (*elem
< 1) /* not a valid element */
1403 return SVN_NO_ERROR
;
1407 case ELEM_update_report
:
1408 att
= svn_xml_get_attr_value("send-all", atts
);
1409 if (att
&& (strcmp(att
, "true") == 0))
1410 rb
->receiving_all
= TRUE
;
1413 case ELEM_target_revision
:
1414 att
= svn_xml_get_attr_value("rev", atts
);
1416 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1417 _("Missing rev attr in target-revision"
1419 SVN_ERR((*rb
->editor
->set_target_revision
)(rb
->edit_baton
,
1420 SVN_STR_TO_REV(att
),
1424 case ELEM_absent_directory
:
1425 name
= svn_xml_get_attr_value("name", atts
);
1427 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1428 _("Missing name attr in absent-directory"
1431 parent_dir
= &TOP_DIR(rb
);
1432 pathbuf
= svn_stringbuf_dup(parent_dir
->pathbuf
, parent_dir
->pool
);
1433 svn_path_add_component(pathbuf
, name
);
1435 SVN_ERR((*rb
->editor
->absent_directory
)(pathbuf
->data
,
1440 case ELEM_absent_file
:
1441 name
= svn_xml_get_attr_value("name", atts
);
1443 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1444 _("Missing name attr in absent-file"
1446 parent_dir
= &TOP_DIR(rb
);
1447 pathbuf
= svn_stringbuf_dup(parent_dir
->pathbuf
, parent_dir
->pool
);
1448 svn_path_add_component(pathbuf
, name
);
1450 SVN_ERR((*rb
->editor
->absent_file
)(pathbuf
->data
,
1456 att
= svn_xml_get_attr_value("path", atts
);
1458 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1459 _("Missing path attr in resource element"));
1460 svn_stringbuf_set(rb
->current_wcprop_path
, att
);
1461 rb
->in_resource
= TRUE
;
1464 case ELEM_open_directory
:
1465 att
= svn_xml_get_attr_value("rev", atts
);
1467 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1468 _("Missing rev attr in open-directory"
1470 base
= SVN_STR_TO_REV(att
);
1472 if (DIR_DEPTH(rb
) == 0)
1474 /* pathbuf has to live for the whole edit! */
1475 pathbuf
= svn_stringbuf_create("", rb
->pool
);
1477 /* During switch operations, we need to invalidate the
1478 tree's version resource URLs in case something goes
1480 if (rb
->is_switch
&& rb
->ras
->callbacks
->invalidate_wc_props
)
1482 SVN_ERR(rb
->ras
->callbacks
->invalidate_wc_props
1483 (rb
->ras
->callback_baton
, rb
->target
,
1484 SVN_RA_NEON__LP_VSN_URL
, rb
->pool
));
1487 subpool
= svn_pool_create(rb
->pool
);
1488 SVN_ERR((*rb
->editor
->open_root
)(rb
->edit_baton
, base
,
1489 subpool
, &new_dir_baton
));
1491 /* push the new baton onto the directory baton stack */
1492 push_dir(rb
, new_dir_baton
, pathbuf
, subpool
);
1496 name
= svn_xml_get_attr_value("name", atts
);
1498 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1499 _("Missing name attr in open-directory"
1501 svn_stringbuf_set(rb
->namestr
, name
);
1503 parent_dir
= &TOP_DIR(rb
);
1504 subpool
= svn_pool_create(parent_dir
->pool
);
1506 pathbuf
= svn_stringbuf_dup(parent_dir
->pathbuf
, subpool
);
1507 svn_path_add_component(pathbuf
, rb
->namestr
->data
);
1509 SVN_ERR((*rb
->editor
->open_directory
)(pathbuf
->data
,
1510 parent_dir
->baton
, base
,
1514 /* push the new baton onto the directory baton stack */
1515 push_dir(rb
, new_dir_baton
, pathbuf
, subpool
);
1518 /* Property fetching is NOT implied in replacement. */
1519 TOP_DIR(rb
).fetch_props
= FALSE
;
1522 case ELEM_add_directory
:
1523 name
= svn_xml_get_attr_value("name", atts
);
1525 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1526 _("Missing name attr in add-directory"
1528 svn_stringbuf_set(rb
->namestr
, name
);
1530 att
= svn_xml_get_attr_value("copyfrom-path", atts
);
1533 cpath
= rb
->cpathstr
;
1534 svn_stringbuf_set(cpath
, att
);
1536 att
= svn_xml_get_attr_value("copyfrom-rev", atts
);
1538 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1539 _("Missing copyfrom-rev attr in"
1540 " add-directory element"));
1541 crev
= SVN_STR_TO_REV(att
);
1544 parent_dir
= &TOP_DIR(rb
);
1545 subpool
= svn_pool_create(parent_dir
->pool
);
1547 pathbuf
= svn_stringbuf_dup(parent_dir
->pathbuf
, subpool
);
1548 svn_path_add_component(pathbuf
, rb
->namestr
->data
);
1550 SVN_ERR((*rb
->editor
->add_directory
)(pathbuf
->data
, parent_dir
->baton
,
1551 cpath
? cpath
->data
: NULL
,
1555 /* push the new baton onto the directory baton stack */
1556 push_dir(rb
, new_dir_baton
, pathbuf
, subpool
);
1558 /* Property fetching is implied in addition. This flag is only
1559 for parsing old-style reports; it is ignored when talking to
1561 TOP_DIR(rb
).fetch_props
= TRUE
;
1563 bc_url
= svn_xml_get_attr_value("bc-url", atts
);
1565 /* In non-modern report responses, we're just told to fetch the
1566 props later. In that case, we can at least do a pre-emptive
1567 depth-1 propfind on the directory right now; this prevents
1568 individual propfinds on added-files later on, thus reducing
1569 the number of network turnarounds (though not by as much as
1570 simply getting a modern report response!). */
1571 if ((! rb
->receiving_all
) && bc_url
)
1573 apr_hash_t
*bc_children
;
1574 SVN_ERR(svn_ra_neon__get_props(&bc_children
,
1577 SVN_RA_NEON__DEPTH_ONE
,
1578 NULL
, NULL
/* allprops */,
1581 /* re-index the results into a more usable hash.
1582 bc_children maps bc-url->resource_t, but we want the
1583 dir_item_t's hash to map vc-url->resource_t. */
1586 apr_hash_index_t
*hi
;
1587 TOP_DIR(rb
).children
= apr_hash_make(TOP_DIR(rb
).pool
);
1589 for (hi
= apr_hash_first(TOP_DIR(rb
).pool
, bc_children
);
1590 hi
; hi
= apr_hash_next(hi
))
1593 svn_ra_neon__resource_t
*rsrc
;
1594 const svn_string_t
*vc_url
;
1596 apr_hash_this(hi
, NULL
, NULL
, &val
);
1599 vc_url
= apr_hash_get(rsrc
->propset
,
1600 SVN_RA_NEON__PROP_CHECKED_IN
,
1601 APR_HASH_KEY_STRING
);
1603 apr_hash_set(TOP_DIR(rb
).children
,
1604 vc_url
->data
, vc_url
->len
,
1612 case ELEM_open_file
:
1613 att
= svn_xml_get_attr_value("rev", atts
);
1615 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1616 _("Missing rev attr in open-file"
1618 base
= SVN_STR_TO_REV(att
);
1620 name
= svn_xml_get_attr_value("name", atts
);
1622 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1623 _("Missing name attr in open-file"
1625 svn_stringbuf_set(rb
->namestr
, name
);
1627 parent_dir
= &TOP_DIR(rb
);
1628 rb
->file_pool
= svn_pool_create(parent_dir
->pool
);
1629 rb
->result_checksum
= NULL
;
1631 /* Add this file's name into the directory's path buffer. It will be
1632 removed in end_element() */
1633 svn_path_add_component(parent_dir
->pathbuf
, rb
->namestr
->data
);
1635 SVN_ERR((*rb
->editor
->open_file
)(parent_dir
->pathbuf
->data
,
1636 parent_dir
->baton
, base
,
1640 /* Property fetching is NOT implied in replacement. */
1641 rb
->fetch_props
= FALSE
;
1646 name
= svn_xml_get_attr_value("name", atts
);
1648 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1649 _("Missing name attr in add-file"
1651 svn_stringbuf_set(rb
->namestr
, name
);
1653 att
= svn_xml_get_attr_value("copyfrom-path", atts
);
1656 cpath
= rb
->cpathstr
;
1657 svn_stringbuf_set(cpath
, att
);
1659 att
= svn_xml_get_attr_value("copyfrom-rev", atts
);
1661 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1662 _("Missing copyfrom-rev attr in add-file"
1664 crev
= SVN_STR_TO_REV(att
);
1667 parent_dir
= &TOP_DIR(rb
);
1668 rb
->file_pool
= svn_pool_create(parent_dir
->pool
);
1669 rb
->result_checksum
= NULL
;
1671 /* Add this file's name into the directory's path buffer. It will be
1672 removed in end_element() */
1673 svn_path_add_component(parent_dir
->pathbuf
, rb
->namestr
->data
);
1675 SVN_ERR((*rb
->editor
->add_file
)(parent_dir
->pathbuf
->data
,
1677 cpath
? cpath
->data
: NULL
,
1678 crev
, rb
->file_pool
,
1681 /* Property fetching is implied in addition. This flag is only
1682 for parsing old-style reports; it is ignored when talking to
1684 rb
->fetch_props
= TRUE
;
1689 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
1690 <fetch-file>s and such) when *not* in "send-all" mode. As a
1691 client, we're smart enough to know that's wrong, so when not
1692 in "receiving-all" mode, we'll ignore <txdelta> tags
1694 if (! rb
->receiving_all
)
1697 SVN_ERR((*rb
->editor
->apply_textdelta
)(rb
->file_baton
,
1698 NULL
, /* ### base_checksum */
1701 &(rb
->whandler_baton
)));
1703 rb
->svndiff_decoder
= svn_txdelta_parse_svndiff(rb
->whandler
,
1705 TRUE
, rb
->file_pool
);
1707 rb
->base64_decoder
= svn_base64_decode(rb
->svndiff_decoder
,
1713 const char *encoding
= svn_xml_get_attr_value("encoding", atts
);
1714 name
= svn_xml_get_attr_value("name", atts
);
1716 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1717 _("Missing name attr in set-prop element"));
1718 svn_stringbuf_set(rb
->namestr
, name
);
1720 svn_stringbuf_set(rb
->encoding
, encoding
);
1722 svn_stringbuf_setempty(rb
->encoding
);
1727 case ELEM_remove_prop
:
1728 name
= svn_xml_get_attr_value("name", atts
);
1730 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1731 _("Missing name attr in remove-prop element"));
1732 svn_stringbuf_set(rb
->namestr
, name
);
1734 /* Removing a prop. */
1735 if (rb
->file_baton
== NULL
)
1736 SVN_ERR(rb
->editor
->change_dir_prop(TOP_DIR(rb
).baton
,
1738 NULL
, TOP_DIR(rb
).pool
));
1740 SVN_ERR(rb
->editor
->change_file_prop(rb
->file_baton
, rb
->namestr
->data
,
1741 NULL
, rb
->file_pool
));
1744 case ELEM_fetch_props
:
1745 if (!rb
->fetch_content
)
1747 /* If this is just a status check, the specifics of the
1748 property change are uninteresting. Simply call our
1749 editor function with bogus data so it registers a
1751 svn_stringbuf_set(rb
->namestr
, SVN_PROP_PREFIX
"BOGOSITY");
1753 if (rb
->file_baton
== NULL
)
1754 SVN_ERR(rb
->editor
->change_dir_prop(TOP_DIR(rb
).baton
,
1756 NULL
, TOP_DIR(rb
).pool
));
1758 SVN_ERR(rb
->editor
->change_file_prop(rb
->file_baton
,
1760 NULL
, rb
->file_pool
));
1764 /* Note that we need to fetch props for this... */
1765 if (rb
->file_baton
== NULL
)
1766 TOP_DIR(rb
).fetch_props
= TRUE
; /* ...directory. */
1768 rb
->fetch_props
= TRUE
; /* ...file. */
1772 case ELEM_fetch_file
:
1773 base_checksum
= svn_xml_get_attr_value("base-checksum", atts
);
1774 rb
->result_checksum
= NULL
;
1776 /* If we aren't expecting to see the file contents inline, we
1777 should ignore server requests to fetch them.
1779 ### This conditional was added to counteract a little bug in
1780 Subversion 0.33.0's mod_dav_svn whereby both the <txdelta>
1781 and <fetch-file> tags were being transmitted. Someday, we
1782 should remove the conditional again to give the server the
1783 option of sending inline text-deltas for some files while
1784 telling the client to fetch others. */
1785 if (! rb
->receiving_all
)
1787 /* assert: rb->href->len > 0 */
1788 SVN_ERR(simple_fetch_file(rb
->ras
,
1790 TOP_DIR(rb
).pathbuf
->data
,
1795 rb
->ras
->callbacks
->get_wc_prop
,
1796 rb
->ras
->callback_baton
,
1801 case ELEM_delete_entry
:
1802 name
= svn_xml_get_attr_value("name", atts
);
1804 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1805 _("Missing name attr in delete-entry"
1807 svn_stringbuf_set(rb
->namestr
, name
);
1809 parent_dir
= &TOP_DIR(rb
);
1811 /* Pool use is a little non-standard here. When lots of items in the
1812 same directory get deleted each one will trigger a call to
1813 editor->delete_entry, but we don't have a pool that readily fits
1814 the usual iteration pattern and so memory use could grow without
1815 bound (see issue 1635). To avoid such growth we use a temporary,
1816 short-lived, pool. */
1817 subpool
= svn_pool_create(parent_dir
->pool
);
1819 pathbuf
= svn_stringbuf_dup(parent_dir
->pathbuf
, subpool
);
1820 svn_path_add_component(pathbuf
, rb
->namestr
->data
);
1822 SVN_ERR((*rb
->editor
->delete_entry
)(pathbuf
->data
,
1826 svn_pool_destroy(subpool
);
1835 return SVN_NO_ERROR
;
1839 static svn_error_t
*
1840 add_node_props(report_baton_t
*rb
, apr_pool_t
*pool
)
1842 svn_ra_neon__resource_t
*rsrc
= NULL
;
1843 apr_hash_t
*props
= NULL
;
1845 /* Do nothing if parsing a modern report, because the properties
1846 already come inline. */
1847 if (rb
->receiving_all
)
1848 return SVN_NO_ERROR
;
1850 /* Do nothing if we aren't fetching content. */
1851 if (!rb
->fetch_content
)
1852 return SVN_NO_ERROR
;
1856 if (! rb
->fetch_props
)
1857 return SVN_NO_ERROR
;
1859 /* Check to see if your parent directory already has your props
1860 stored, possibly from a depth-1 propfind. Otherwise just do
1861 a propfind directly on the file url. */
1862 if ( ! ((TOP_DIR(rb
).children
)
1863 && (props
= apr_hash_get(TOP_DIR(rb
).children
, rb
->href
->data
,
1864 APR_HASH_KEY_STRING
))) )
1866 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc
,
1872 props
= rsrc
->propset
;
1875 SVN_ERR(add_props(props
,
1876 rb
->editor
->change_file_prop
,
1882 if (! TOP_DIR(rb
).fetch_props
)
1883 return SVN_NO_ERROR
;
1885 /* Check to see if your props are already stored, possibly from
1886 a depth-1 propfind. Otherwise just do a propfind directly on
1887 the directory url. */
1888 if ( ! ((TOP_DIR(rb
).children
)
1889 && (props
= apr_hash_get(TOP_DIR(rb
).children
,
1890 TOP_DIR(rb
).vsn_url
,
1891 APR_HASH_KEY_STRING
))) )
1893 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc
,
1895 TOP_DIR(rb
).vsn_url
,
1899 props
= rsrc
->propset
;
1902 SVN_ERR(add_props(props
,
1903 rb
->editor
->change_dir_prop
,
1908 return SVN_NO_ERROR
;
1911 /* This implements the `svn_ra_neon__cdata_cb_t' prototype. */
1912 static svn_error_t
*
1913 cdata_handler(void *userdata
, int state
, const char *cdata
, size_t len
)
1915 report_baton_t
*rb
= userdata
;
1921 case ELEM_md5_checksum
:
1922 case ELEM_version_name
:
1923 case ELEM_creationdate
:
1924 case ELEM_creator_displayname
:
1925 svn_stringbuf_appendbytes(rb
->cdata_accum
, cdata
, len
);
1930 apr_size_t nlen
= len
;
1932 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
1933 <fetch-file>s and such) when *not* in "send-all" mode. As a
1934 client, we're smart enough to know that's wrong, so when not
1935 in "receiving-all" mode, we'll ignore <txdelta> tags
1937 if (! rb
->receiving_all
)
1940 SVN_ERR(svn_stream_write(rb
->base64_decoder
, cdata
, &nlen
));
1943 /* Short write without associated error? "Can't happen." */
1944 return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF
, NULL
,
1945 _("Error writing to '%s': unexpected EOF"),
1946 svn_path_local_style(rb
->namestr
->data
,
1953 return 0; /* no error */
1956 /* This implements the `svn_ra_neon_endelm_cb_t' prototype. */
1957 static svn_error_t
*
1958 end_element(void *userdata
, int state
,
1959 const char *nspace
, const char *elt_name
)
1961 report_baton_t
*rb
= userdata
;
1962 const svn_delta_editor_t
*editor
= rb
->editor
;
1963 const svn_ra_neon__xml_elm_t
*elm
;
1965 elm
= svn_ra_neon__lookup_xml_elem(report_elements
, nspace
, elt_name
);
1968 return SVN_NO_ERROR
;
1973 rb
->in_resource
= FALSE
;
1976 case ELEM_update_report
:
1977 /* End of report; close up the editor. */
1978 SVN_ERR((*rb
->editor
->close_edit
)(rb
->edit_baton
, rb
->pool
));
1979 rb
->edit_baton
= NULL
;
1982 case ELEM_add_directory
:
1983 case ELEM_open_directory
:
1985 /* fetch node props, unless this is the top dir and the real
1986 target of the operation is not the top dir. */
1987 if (! ((DIR_DEPTH(rb
) == 1) && *rb
->target
))
1988 SVN_ERR(add_node_props(rb
, TOP_DIR(rb
).pool
));
1990 /* Close the directory on top of the stack, and pop it. Also,
1991 destroy the subpool used exclusive by this directory and its
1993 SVN_ERR((*rb
->editor
->close_directory
)(TOP_DIR(rb
).baton
,
1995 svn_pool_destroy(TOP_DIR(rb
).pool
);
1996 apr_array_pop(rb
->dirs
);
2000 /* we wait until the close element to do the work. this allows us to
2001 retrieve the href before fetching. */
2004 if (! rb
->receiving_all
)
2006 SVN_ERR(simple_fetch_file(rb
->ras
,
2008 TOP_DIR(rb
).pathbuf
->data
,
2011 NULL
, /* no base checksum in an add */
2013 rb
->ras
->callbacks
->get_wc_prop
,
2014 rb
->ras
->callback_baton
,
2017 /* fetch node props as necessary. */
2018 SVN_ERR(add_node_props(rb
, rb
->file_pool
));
2021 /* close the file and mark that we are no longer operating on a file */
2022 SVN_ERR((*rb
->editor
->close_file
)(rb
->file_baton
,
2023 rb
->result_checksum
,
2025 rb
->file_baton
= NULL
;
2027 /* Yank this file out of the directory's path buffer. */
2028 svn_path_remove_component(TOP_DIR(rb
).pathbuf
);
2029 svn_pool_destroy(rb
->file_pool
);
2030 rb
->file_pool
= NULL
;
2034 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2035 <fetch-file>s and such) when *not* in "send-all" mode. As a
2036 client, we're smart enough to know that's wrong, so when not
2037 in "receiving-all" mode, we'll ignore <txdelta> tags
2039 if (! rb
->receiving_all
)
2042 SVN_ERR(svn_stream_close(rb
->base64_decoder
));
2043 rb
->whandler
= NULL
;
2044 rb
->whandler_baton
= NULL
;
2045 rb
->svndiff_decoder
= NULL
;
2046 rb
->base64_decoder
= NULL
;
2049 case ELEM_open_file
:
2050 /* fetch node props as necessary. */
2051 SVN_ERR(add_node_props(rb
, rb
->file_pool
));
2053 /* close the file and mark that we are no longer operating on a file */
2054 SVN_ERR((*rb
->editor
->close_file
)(rb
->file_baton
,
2055 rb
->result_checksum
,
2057 rb
->file_baton
= NULL
;
2059 /* Yank this file out of the directory's path buffer. */
2060 svn_path_remove_component(TOP_DIR(rb
).pathbuf
);
2061 svn_pool_destroy(rb
->file_pool
);
2062 rb
->file_pool
= NULL
;
2067 svn_string_t decoded_value
;
2068 const svn_string_t
*decoded_value_p
;
2072 pool
= rb
->file_pool
;
2074 pool
= TOP_DIR(rb
).pool
;
2076 decoded_value
.data
= rb
->cdata_accum
->data
;
2077 decoded_value
.len
= rb
->cdata_accum
->len
;
2079 /* Determine the cdata encoding, if any. */
2080 if (svn_stringbuf_isempty(rb
->encoding
))
2082 decoded_value_p
= &decoded_value
;
2084 else if (strcmp(rb
->encoding
->data
, "base64") == 0)
2086 decoded_value_p
= svn_base64_decode_string(&decoded_value
, pool
);
2087 svn_stringbuf_setempty(rb
->encoding
);
2091 SVN_ERR(svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING
, NULL
,
2092 _("Unknown XML encoding: '%s'"),
2093 rb
->encoding
->data
));
2094 abort(); /* Not reached. */
2100 SVN_ERR(rb
->editor
->change_file_prop(rb
->file_baton
,
2102 decoded_value_p
, pool
));
2106 SVN_ERR(rb
->editor
->change_dir_prop(TOP_DIR(rb
).baton
,
2108 decoded_value_p
, pool
));
2112 svn_stringbuf_setempty(rb
->cdata_accum
);
2116 if (rb
->fetch_content
)
2117 /* record the href that we just found */
2118 SVN_ERR(svn_ra_neon__copy_href(rb
->href
, rb
->cdata_accum
->data
,
2121 svn_stringbuf_setempty(rb
->cdata_accum
);
2123 /* do nothing if we aren't fetching content. */
2124 if (!rb
->fetch_content
)
2127 /* if we're within a <resource> tag, then just call the generic
2128 RA set_wcprop_callback directly; no need to use the
2130 if (rb
->in_resource
)
2132 svn_string_t href_val
;
2133 href_val
.data
= rb
->href
->data
;
2134 href_val
.len
= rb
->href
->len
;
2136 if (rb
->ras
->callbacks
->set_wc_prop
!= NULL
)
2137 SVN_ERR(rb
->ras
->callbacks
->set_wc_prop
2138 (rb
->ras
->callback_baton
,
2139 rb
->current_wcprop_path
->data
,
2140 SVN_RA_NEON__LP_VSN_URL
,
2143 svn_pool_clear(rb
->scratch_pool
);
2145 /* else we're setting a wcprop in the context of an editor drive. */
2146 else if (rb
->file_baton
== NULL
)
2148 /* Update the wcprop here, unless this is the top directory
2149 and the real target of this operation is something other
2150 than the top directory. */
2151 if (! ((DIR_DEPTH(rb
) == 1) && *rb
->target
))
2153 SVN_ERR(simple_store_vsn_url(rb
->href
->data
, TOP_DIR(rb
).baton
,
2154 rb
->editor
->change_dir_prop
,
2157 /* save away the URL in case a fetch-props arrives after all of
2158 the subdir processing. we will need this copy of the URL to
2159 fetch the properties (i.e. rb->href will be toast by then). */
2160 TOP_DIR(rb
).vsn_url
= apr_pmemdup(TOP_DIR(rb
).pool
,
2167 SVN_ERR(simple_store_vsn_url(rb
->href
->data
, rb
->file_baton
,
2168 rb
->editor
->change_file_prop
,
2173 case ELEM_md5_checksum
:
2174 /* We only care about file checksums. */
2177 rb
->result_checksum
= apr_pstrdup(rb
->file_pool
,
2178 rb
->cdata_accum
->data
);
2180 svn_stringbuf_setempty(rb
->cdata_accum
);
2183 case ELEM_version_name
:
2184 case ELEM_creationdate
:
2185 case ELEM_creator_displayname
:
2187 /* The name of the xml tag is the property that we want to set. */
2189 rb
->file_baton
? rb
->file_pool
: TOP_DIR(rb
).pool
;
2190 prop_setter_t setter
=
2191 rb
->file_baton
? editor
->change_file_prop
: editor
->change_dir_prop
;
2192 const char *name
= apr_pstrcat(pool
, elm
->nspace
, elm
->name
, NULL
);
2193 void *baton
= rb
->file_baton
? rb
->file_baton
: TOP_DIR(rb
).baton
;
2194 svn_string_t valstr
;
2196 valstr
.data
= rb
->cdata_accum
->data
;
2197 valstr
.len
= rb
->cdata_accum
->len
;
2198 SVN_ERR(set_special_wc_prop(name
, &valstr
, setter
, baton
, pool
));
2199 svn_stringbuf_setempty(rb
->cdata_accum
);
2207 return SVN_NO_ERROR
;
2211 static svn_error_t
* reporter_set_path(void *report_baton
,
2213 svn_revnum_t revision
,
2215 svn_boolean_t start_empty
,
2216 const char *lock_token
,
2219 report_baton_t
*rb
= report_baton
;
2221 svn_stringbuf_t
*qpath
= NULL
;
2222 const char *tokenstring
= "";
2223 const char *depthstring
= apr_psprintf(pool
, "depth=\"%s\"",
2224 svn_depth_to_word(depth
));
2227 tokenstring
= apr_psprintf(pool
, "lock-token=\"%s\"", lock_token
);
2229 svn_xml_escape_cdata_cstring(&qpath
, path
, pool
);
2231 entry
= apr_psprintf(pool
,
2232 "<S:entry rev=\"%ld\" %s %s"
2233 " start-empty=\"true\">%s</S:entry>" DEBUG_CR
,
2234 revision
, depthstring
, tokenstring
, qpath
->data
);
2236 entry
= apr_psprintf(pool
,
2237 "<S:entry rev=\"%ld\" %s %s>"
2238 "%s</S:entry>" DEBUG_CR
,
2239 revision
, depthstring
, tokenstring
, qpath
->data
);
2241 return svn_io_file_write_full(rb
->tmpfile
, entry
, strlen(entry
), NULL
, pool
);
2245 static svn_error_t
* reporter_link_path(void *report_baton
,
2248 svn_revnum_t revision
,
2250 svn_boolean_t start_empty
,
2251 const char *lock_token
,
2254 report_baton_t
*rb
= report_baton
;
2256 svn_stringbuf_t
*qpath
= NULL
, *qlinkpath
= NULL
;
2257 svn_string_t bc_relative
;
2258 const char *tokenstring
= "";
2259 const char *depthstring
= apr_psprintf(pool
, "depth=\"%s\"",
2260 svn_depth_to_word(depth
));
2263 tokenstring
= apr_psprintf(pool
, "lock-token=\"%s\"", lock_token
);
2265 /* Convert the copyfrom_* url/rev "public" pair into a Baseline
2266 Collection (BC) URL that represents the revision -- and a
2267 relative path under that BC. */
2268 SVN_ERR(svn_ra_neon__get_baseline_info(NULL
, NULL
, &bc_relative
, NULL
,
2274 svn_xml_escape_cdata_cstring(&qpath
, path
, pool
);
2275 svn_xml_escape_attr_cstring(&qlinkpath
, bc_relative
.data
, pool
);
2277 entry
= apr_psprintf(pool
,
2278 "<S:entry rev=\"%ld\" %s %s"
2279 " linkpath=\"/%s\" start-empty=\"true\""
2280 ">%s</S:entry>" DEBUG_CR
,
2281 revision
, depthstring
, tokenstring
,
2282 qlinkpath
->data
, qpath
->data
);
2284 entry
= apr_psprintf(pool
,
2285 "<S:entry rev=\"%ld\" %s %s"
2286 " linkpath=\"/%s\">%s</S:entry>" DEBUG_CR
,
2287 revision
, depthstring
, tokenstring
,
2288 qlinkpath
->data
, qpath
->data
);
2290 return svn_io_file_write_full(rb
->tmpfile
, entry
, strlen(entry
), NULL
, pool
);
2294 static svn_error_t
* reporter_delete_path(void *report_baton
,
2298 report_baton_t
*rb
= report_baton
;
2300 svn_stringbuf_t
*qpath
= NULL
;
2302 svn_xml_escape_cdata_cstring(&qpath
, path
, pool
);
2303 s
= apr_psprintf(pool
,
2304 "<S:missing>%s</S:missing>" DEBUG_CR
,
2307 return svn_io_file_write_full(rb
->tmpfile
, s
, strlen(s
), NULL
, pool
);
2311 static svn_error_t
* reporter_abort_report(void *report_baton
,
2314 report_baton_t
*rb
= report_baton
;
2316 (void) apr_file_close(rb
->tmpfile
);
2318 return SVN_NO_ERROR
;
2322 static svn_error_t
* reporter_finish_report(void *report_baton
,
2325 report_baton_t
*rb
= report_baton
;
2328 apr_hash_t
*request_headers
= apr_hash_make(pool
);
2329 apr_hash_set(request_headers
, "Accept-Encoding", APR_HASH_KEY_STRING
,
2330 "svndiff1;q=0.9,svndiff;q=0.8");
2333 #define SVN_RA_NEON__REPORT_TAIL "</S:update-report>" DEBUG_CR
2334 /* write the final closing gunk to our request body. */
2335 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
,
2336 SVN_RA_NEON__REPORT_TAIL
,
2337 sizeof(SVN_RA_NEON__REPORT_TAIL
) - 1,
2339 #undef SVN_RA_NEON__REPORT_TAIL
2341 /* get the editor process prepped */
2342 rb
->dirs
= apr_array_make(rb
->pool
, 5, sizeof(dir_item_t
));
2343 rb
->namestr
= MAKE_BUFFER(rb
->pool
);
2344 rb
->cpathstr
= MAKE_BUFFER(rb
->pool
);
2345 rb
->encoding
= MAKE_BUFFER(rb
->pool
);
2346 rb
->href
= MAKE_BUFFER(rb
->pool
);
2348 /* get the VCC. if this doesn't work out for us, don't forget to
2349 remove the tmpfile before returning the error. */
2350 if ((err
= svn_ra_neon__get_vcc(&vcc
, rb
->ras
,
2351 rb
->ras
->url
->data
, pool
)))
2353 (void) apr_file_close(rb
->tmpfile
);
2357 /* dispatch the REPORT. */
2358 err
= svn_ra_neon__parsed_request(rb
->ras
, "REPORT", vcc
,
2359 NULL
, rb
->tmpfile
, NULL
,
2364 request_headers
, NULL
,
2365 rb
->spool_response
, pool
);
2367 /* we're done with the file */
2368 (void) apr_file_close(rb
->tmpfile
);
2372 /* We got the whole HTTP response thing done. *Whew*. Our edit
2373 baton should have been closed by now, so return a failure if it
2377 return svn_error_createf
2378 (SVN_ERR_RA_DAV_REQUEST_FAILED
, NULL
,
2379 _("REPORT response handling failed to complete the editor drive"));
2382 /* store auth info if we can. */
2383 SVN_ERR(svn_ra_neon__maybe_store_auth_info(rb
->ras
, pool
));
2385 return SVN_NO_ERROR
;
2388 static const svn_ra_reporter3_t ra_neon_reporter
= {
2390 reporter_delete_path
,
2392 reporter_finish_report
,
2393 reporter_abort_report
2397 /* Make a generic REPORTER / REPORT_BATON for reporting the state of
2398 the working copy against REVISION during updates or status checks.
2399 The server will drive EDITOR / EDIT_BATON to indicate how to
2400 transform the working copy into the requested target.
2402 SESSION is the RA session in use. TARGET is an optional single
2403 path component will restrict the scope of the operation to an entry
2404 in the directory represented by the SESSION's URL, or empty if the
2405 entire directory is meant to be the target.
2407 DEPTH is the requested depth of the operation. It will be
2408 transmitted to the server, which (if it understands depths) can use
2409 the information to limit the information it sends back. Also store
2410 DEPTH in the REPORT_BATON: that way, if the server is old and does
2411 not understand depth requests, the client can notice this when the
2412 response starts streaming in, and adjust accordingly (as of this
2413 writnig, by wrapping REPORTER->editor and REPORTER->edit_baton in a
2414 filtering editor that simply tosses out the data the client doesn't
2417 If SEND_COPYFROM_ARGS is set, then ask the server to transmit
2418 copyfrom args in add_file() in add_directory() calls.
2420 If IGNORE_ANCESTRY is set, the server will transmit real diffs
2421 between the working copy and the target even if those objects are
2422 not historically related. Otherwise, the response will generally
2423 look like a giant delete followed by a giant add.
2425 RESOURCE_WALK controls whether to ask the DAV server to supply an
2426 entire tree's worth of version-resource-URL working copy cache
2429 FETCH_CONTENT is used by the REPORT response parser to determine
2430 whether it should bother getting the contents of files represented
2431 in the delta response (of if a directory delta is all that is of
2434 If SEND_ALL is set, the server will be asked to embed contents into
2437 If SPOOL_RESPONSE is set, the REPORT response will be cached to
2438 disk in a tmpfile (in full), then read back and parsed.
2440 Oh, and do all this junk in POOL. */
2441 static svn_error_t
*
2442 make_reporter(svn_ra_session_t
*session
,
2443 const svn_ra_reporter3_t
**reporter
,
2444 void **report_baton
,
2445 svn_revnum_t revision
,
2447 const char *dst_path
,
2449 svn_boolean_t send_copyfrom_args
,
2450 svn_boolean_t ignore_ancestry
,
2451 svn_boolean_t resource_walk
,
2452 const svn_delta_editor_t
*editor
,
2454 svn_boolean_t fetch_content
,
2455 svn_boolean_t send_all
,
2456 svn_boolean_t spool_response
,
2459 svn_ra_neon__session_t
*ras
= session
->priv
;
2462 svn_stringbuf_t
*xml_s
;
2463 const svn_delta_editor_t
*filter_editor
;
2465 svn_boolean_t has_target
= *target
? TRUE
: FALSE
;
2466 svn_boolean_t server_supports_depth
;
2468 SVN_ERR(svn_ra_neon__has_capability(session
, &server_supports_depth
,
2469 SVN_RA_CAPABILITY_DEPTH
, pool
));
2470 /* We can skip the depth filtering when the user requested
2471 depth_files or depth_infinity because the server will
2472 transmit the right stuff anyway. */
2473 if ((depth
!= svn_depth_files
)
2474 && (depth
!= svn_depth_infinity
)
2475 && ! server_supports_depth
)
2477 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor
,
2484 editor
= filter_editor
;
2485 edit_baton
= filter_baton
;
2488 rb
= apr_pcalloc(pool
, sizeof(*rb
));
2491 rb
->scratch_pool
= svn_pool_create(pool
);
2492 rb
->editor
= editor
;
2493 rb
->edit_baton
= edit_baton
;
2494 rb
->fetch_content
= fetch_content
;
2495 rb
->in_resource
= FALSE
;
2496 rb
->current_wcprop_path
= svn_stringbuf_create("", pool
);
2497 rb
->is_switch
= dst_path
? TRUE
: FALSE
;
2498 rb
->target
= target
;
2499 rb
->receiving_all
= FALSE
;
2500 rb
->spool_response
= spool_response
;
2501 rb
->whandler
= NULL
;
2502 rb
->whandler_baton
= NULL
;
2503 rb
->svndiff_decoder
= NULL
;
2504 rb
->base64_decoder
= NULL
;
2505 rb
->cdata_accum
= svn_stringbuf_create("", pool
);
2506 rb
->send_copyfrom_args
= send_copyfrom_args
;
2508 /* Neon "pulls" request body content from the caller. The reporter is
2509 organized where data is "pushed" into self. To match these up, we use
2510 an intermediate file -- push data into the file, then let Neon pull
2513 Note: one day we could spin up a thread and use a pipe between this
2514 code and Neon. We write to a pipe, Neon reads from the pipe. Each
2515 thread can block on the pipe, waiting for the other to complete its
2519 /* Use the client callback to create a tmpfile. */
2520 SVN_ERR(ras
->callbacks
->open_tmp_file(&rb
->tmpfile
, ras
->callback_baton
,
2524 s
= apr_psprintf(pool
, "<S:update-report send-all=\"%s\" xmlns:S=\""
2525 SVN_XML_NAMESPACE
"\">" DEBUG_CR
,
2526 send_all
? "true" : "false");
2527 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, s
, strlen(s
), NULL
, pool
));
2529 /* always write the original source path. this is part of the "new
2530 style" update-report syntax. if the tmpfile is used in an "old
2531 style' update-report request, older servers will just ignore this
2532 unknown xml element. */
2534 svn_xml_escape_cdata_cstring(&xml_s
, ras
->url
->data
, pool
);
2535 s
= apr_psprintf(pool
, "<S:src-path>%s</S:src-path>" DEBUG_CR
, xml_s
->data
);
2536 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, s
, strlen(s
), NULL
, pool
));
2538 /* an invalid revnum means "latest". we can just omit the target-revision
2539 element in that case. */
2540 if (SVN_IS_VALID_REVNUM(revision
))
2542 s
= apr_psprintf(pool
,
2543 "<S:target-revision>%ld</S:target-revision>" DEBUG_CR
,
2545 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, s
, strlen(s
), NULL
, pool
));
2548 /* Pre-0.36 servers don't like to see an empty target string. */
2552 svn_xml_escape_cdata_cstring(&xml_s
, target
, pool
);
2553 s
= apr_psprintf(pool
, "<S:update-target>%s</S:update-target>" DEBUG_CR
,
2555 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, s
, strlen(s
), NULL
, pool
));
2559 /* A NULL dst_path is also no problem; this is only passed during a
2560 'switch' operation. If NULL, we don't mention it in the custom
2561 report, and mod_dav_svn automatically runs dir_delta() on two
2566 svn_xml_escape_cdata_cstring(&xml_s
, dst_path
, pool
);
2567 s
= apr_psprintf(pool
, "<S:dst-path>%s</S:dst-path>" DEBUG_CR
,
2569 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, s
, strlen(s
), NULL
, pool
));
2572 /* Old servers know "recursive" but not "depth"; help them DTRT. */
2573 if (depth
== svn_depth_files
|| depth
== svn_depth_empty
)
2575 const char *data
= "<S:recursive>no</S:recursive>" DEBUG_CR
;
2576 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, data
, strlen(data
),
2580 /* mod_dav_svn defaults to svn_depth_infinity, but we always send anyway. */
2582 s
= apr_psprintf(pool
, "<S:depth>%s</S:depth>" DEBUG_CR
,
2583 svn_depth_to_word(depth
));
2584 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, s
, strlen(s
), NULL
, pool
));
2587 /* mod_dav_svn will use ancestry in diffs unless it finds this element. */
2588 if (ignore_ancestry
)
2590 const char *data
= "<S:ignore-ancestry>yes</S:ignore-ancestry>" DEBUG_CR
;
2591 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, data
, strlen(data
),
2595 /* mod_dav_svn 1.5 and later won't send copyfrom args unless it
2596 finds this element. older mod_dav_svn modules should just
2597 ignore the unknown element. */
2598 if (send_copyfrom_args
)
2601 "<S:send-copyfrom-args>yes</S:send-copyfrom-args>" DEBUG_CR
;
2602 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, data
, strlen(data
),
2606 /* If we want a resource walk to occur, note that now. */
2609 const char *data
= "<S:resource-walk>yes</S:resource-walk>" DEBUG_CR
;
2610 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, data
, strlen(data
),
2614 /* When in 'send-all' mode, mod_dav_svn will assume that it should
2615 calculate and transmit real text-deltas (instead of empty windows
2616 that merely indicate "text is changed") unless it finds this
2617 element. When not in 'send-all' mode, mod_dav_svn will never
2618 send text-deltas at all.
2620 NOTE: Do NOT count on servers actually obeying this, as some exist
2621 which obey send-all, but do not check for this directive at all! */
2622 if (send_all
&& (! fetch_content
))
2624 const char *data
= "<S:text-deltas>no</S:text-deltas>" DEBUG_CR
;
2625 SVN_ERR(svn_io_file_write_full(rb
->tmpfile
, data
, strlen(data
),
2629 *reporter
= &ra_neon_reporter
;
2632 return SVN_NO_ERROR
;
2636 svn_error_t
* svn_ra_neon__do_update(svn_ra_session_t
*session
,
2637 const svn_ra_reporter3_t
**reporter
,
2638 void **report_baton
,
2639 svn_revnum_t revision_to_update_to
,
2640 const char *update_target
,
2642 svn_boolean_t send_copyfrom_args
,
2643 const svn_delta_editor_t
*wc_update
,
2644 void *wc_update_baton
,
2647 return make_reporter(session
,
2650 revision_to_update_to
,
2659 TRUE
, /* fetch_content */
2660 TRUE
, /* send_all */
2661 FALSE
, /* spool_response */
2666 svn_error_t
* svn_ra_neon__do_status(svn_ra_session_t
*session
,
2667 const svn_ra_reporter3_t
**reporter
,
2668 void **report_baton
,
2669 const char *status_target
,
2670 svn_revnum_t revision
,
2672 const svn_delta_editor_t
*wc_status
,
2673 void *wc_status_baton
,
2676 return make_reporter(session
,
2688 FALSE
, /* fetch_content */
2689 TRUE
, /* send_all */
2690 FALSE
, /* spool_response */
2695 svn_error_t
* svn_ra_neon__do_switch(svn_ra_session_t
*session
,
2696 const svn_ra_reporter3_t
**reporter
,
2697 void **report_baton
,
2698 svn_revnum_t revision_to_update_to
,
2699 const char *update_target
,
2701 const char *switch_url
,
2702 const svn_delta_editor_t
*wc_update
,
2703 void *wc_update_baton
,
2706 return make_reporter(session
,
2709 revision_to_update_to
,
2713 FALSE
, /* ### TODO(sussman): no copyfrom args */
2715 FALSE
, /* ### Disabled, pre-1.2 servers sometimes
2716 return incorrect resource-walk data */
2719 TRUE
, /* fetch_content */
2720 TRUE
, /* send_all */
2721 FALSE
, /* spool_response */
2726 svn_error_t
* svn_ra_neon__do_diff(svn_ra_session_t
*session
,
2727 const svn_ra_reporter3_t
**reporter
,
2728 void **report_baton
,
2729 svn_revnum_t revision
,
2730 const char *diff_target
,
2732 svn_boolean_t ignore_ancestry
,
2733 svn_boolean_t text_deltas
,
2734 const char *versus_url
,
2735 const svn_delta_editor_t
*wc_diff
,
2736 void *wc_diff_baton
,
2739 return make_reporter(session
,
2751 text_deltas
, /* fetch_content */
2752 FALSE
, /* send_all */
2753 TRUE
, /* spool_response */