Merge the svnserve-logging branch, in its entirety, to trunk, using the
[svn.git] / subversion / mod_dav_svn / reports / update.c
blobb9c9b84eaa8b976afa6768281cd3b0b137da54fe
1 /*
2 * update.c: handle the update-report request and response
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 * ====================================================================
19 #include <apr_pools.h>
20 #include <apr_strings.h>
21 #include <apr_xml.h>
22 #include <apr_md5.h>
24 #include <http_request.h>
25 #include <http_log.h>
26 #include <mod_dav.h>
28 #include "svn_pools.h"
29 #include "svn_repos.h"
30 #include "svn_fs.h"
31 #include "svn_md5.h"
32 #include "svn_base64.h"
33 #include "svn_xml.h"
34 #include "svn_path.h"
35 #include "svn_dav.h"
36 #include "svn_props.h"
37 #include "private/svn_log.h"
39 #include "../dav_svn.h"
42 typedef struct {
43 const dav_resource *resource;
45 /* the revision we are updating to. used to generated IDs. */
46 svn_fs_root_t *rev_root;
48 const char *anchor;
49 const char *target;
51 /* if doing a regular update, then dst_path == anchor. if this is a
52 'switch' operation, then this field is the fs path that is being
53 switched to. This path needs to telescope in the update-editor
54 just like 'anchor' above; it's used for retrieving CR's and
55 vsn-url's during the edit. */
56 const char *dst_path;
58 /* this buffers the output for a bit and is automatically flushed,
59 at appropriate times, by the Apache filter system. */
60 apr_bucket_brigade *bb;
62 /* where to deliver the output */
63 ap_filter_t *output;
65 /* where do these editor paths *really* point to? */
66 apr_hash_t *pathmap;
68 /* are we doing a resource walk? */
69 svn_boolean_t resource_walk;
71 /* True iff we've already sent the open tag for the update. */
72 svn_boolean_t started_update;
74 /* True iff client requested all data inline in the report. */
75 svn_boolean_t send_all;
77 /* SVNDIFF version to send to client. */
78 int svndiff_version;
79 } update_ctx_t;
81 typedef struct item_baton_t {
82 apr_pool_t *pool;
83 update_ctx_t *uc;
84 struct item_baton_t *parent; /* the parent of this item. */
85 const char *name; /* the single-component name of this item */
86 const char *path; /* a telescoping extension of uc->anchor */
87 const char *path2; /* a telescoping extension of uc->dst_path */
88 const char *path3; /* a telescoping extension of uc->dst_path
89 without dst_path as prefix. */
91 const char *base_checksum; /* base_checksum (from apply_textdelta) */
92 const char *text_checksum; /* text_checksum (from close_file) */
94 svn_boolean_t text_changed; /* Did the file's contents change? */
95 svn_boolean_t added; /* File added? (Implies text_changed.) */
96 svn_boolean_t copyfrom; /* File copied? */
97 apr_array_header_t *changed_props; /* array of const char * prop names */
98 apr_array_header_t *removed_props; /* array of const char * prop names */
100 /* "entry props" */
101 const char *committed_rev;
102 const char *committed_date;
103 const char *last_author;
105 } item_baton_t;
108 #define DIR_OR_FILE(is_dir) ((is_dir) ? "directory" : "file")
111 /* add PATH to the pathmap HASH with a repository path of LINKPATH.
112 if LINKPATH is NULL, PATH will map to itself. */
113 static void
114 add_to_path_map(apr_hash_t *hash, const char *path, const char *linkpath)
116 /* normalize 'root paths' to have a slash */
117 const char *norm_path = strcmp(path, "") ? path : "/";
119 /* if there is an actual linkpath given, it is the repos path, else
120 our path maps to itself. */
121 const char *repos_path = linkpath ? linkpath : norm_path;
123 /* now, geez, put the path in the map already! */
124 apr_hash_set(hash, path, APR_HASH_KEY_STRING, repos_path);
128 /* return the actual repository path referred to by the editor's PATH,
129 allocated in POOL, determined by examining the pathmap HASH. */
130 static const char *
131 get_from_path_map(apr_hash_t *hash, const char *path, apr_pool_t *pool)
133 const char *repos_path;
134 svn_stringbuf_t *my_path;
136 /* no hash means no map. that's easy enough. */
137 if (! hash)
138 return apr_pstrdup(pool, path);
140 if ((repos_path = apr_hash_get(hash, path, APR_HASH_KEY_STRING)))
142 /* what luck! this path is a hash key! if there is a linkpath,
143 use that, else return the path itself. */
144 return apr_pstrdup(pool, repos_path);
147 /* bummer. PATH wasn't a key in path map, so we get to start
148 hacking off components and looking for a parent from which to
149 derive a repos_path. use a stringbuf for convenience. */
150 my_path = svn_stringbuf_create(path, pool);
153 svn_path_remove_component(my_path);
154 if ((repos_path = apr_hash_get(hash, my_path->data, my_path->len)))
156 /* we found a mapping ... but of one of PATH's parents.
157 soooo, we get to re-append the chunks of PATH that we
158 broke off to the REPOS_PATH we found. */
159 return svn_path_join(repos_path, path + my_path->len + 1, pool);
162 while (! svn_path_is_empty(my_path->data)
163 && strcmp(my_path->data, "/") != 0);
165 /* well, we simply never found anything worth mentioning the map.
166 PATH is its own default finding, then. */
167 return apr_pstrdup(pool, path);
171 static item_baton_t *
172 make_child_baton(item_baton_t *parent, const char *path, apr_pool_t *pool)
174 item_baton_t *baton;
176 baton = apr_pcalloc(pool, sizeof(*baton));
177 baton->pool = pool;
178 baton->uc = parent->uc;
179 baton->name = svn_path_basename(path, pool);
180 baton->parent = parent;
182 /* Telescope the path based on uc->anchor. */
183 baton->path = svn_path_join(parent->path, baton->name, pool);
185 /* Telescope the path based on uc->dst_path in the exact same way. */
186 baton->path2 = svn_path_join(parent->path2, baton->name, pool);
188 /* Telescope the third path: it's relative, not absolute, to
189 dst_path. Now, we gotta be careful here, because if this
190 operation had a target, and we're it, then we have to use the
191 basename of our source reflection instead of our own. */
192 if ((*baton->uc->target) && (! parent->parent))
193 baton->path3 = svn_path_join(parent->path3, baton->uc->target, pool);
194 else
195 baton->path3 = svn_path_join(parent->path3, baton->name, pool);
197 return baton;
201 /* Get the real filesystem PATH for BATON, and return the value
202 allocated from POOL. This function juggles the craziness of
203 updates, switches, and updates of switched things. */
204 static const char *
205 get_real_fs_path(item_baton_t *baton, apr_pool_t *pool)
207 const char *path = get_from_path_map(baton->uc->pathmap, baton->path, pool);
208 return strcmp(path, baton->path) ? path : baton->path2;
212 static svn_error_t *
213 send_vsn_url(item_baton_t *baton, apr_pool_t *pool)
215 const char *href;
216 const char *path;
217 svn_revnum_t revision;
219 /* Try to use the CR, assuming the path exists in CR. */
220 path = get_real_fs_path(baton, pool);
221 revision = dav_svn__get_safe_cr(baton->uc->rev_root, path, pool);
223 href = dav_svn__build_uri(baton->uc->resource->info->repos,
224 DAV_SVN__BUILD_URI_VERSION,
225 revision, path, 0 /* add_href */, pool);
227 return dav_svn__send_xml(baton->uc->bb, baton->uc->output,
228 "<D:checked-in><D:href>%s</D:href></D:checked-in>"
229 DEBUG_CR, apr_xml_quote_string(pool, href, 1));
233 static svn_error_t *
234 absent_helper(svn_boolean_t is_dir,
235 const char *path,
236 item_baton_t *parent,
237 apr_pool_t *pool)
239 update_ctx_t *uc = parent->uc;
241 if (! uc->resource_walk)
243 const char *elt = apr_psprintf(pool,
244 "<S:absent-%s name=\"%s\"/>" DEBUG_CR,
245 DIR_OR_FILE(is_dir),
246 apr_xml_quote_string
247 (pool,
248 svn_path_basename(path, pool),
249 1));
250 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output, "%s", elt));
253 return SVN_NO_ERROR;
257 static svn_error_t *
258 upd_absent_directory(const char *path, void *parent_baton, apr_pool_t *pool)
260 return absent_helper(TRUE, path, parent_baton, pool);
264 static svn_error_t *
265 upd_absent_file(const char *path, void *parent_baton, apr_pool_t *pool)
267 return absent_helper(FALSE, path, parent_baton, pool);
271 static svn_error_t *
272 add_helper(svn_boolean_t is_dir,
273 const char *path,
274 item_baton_t *parent,
275 const char *copyfrom_path,
276 svn_revnum_t copyfrom_revision,
277 apr_pool_t *pool,
278 void **child_baton)
280 item_baton_t *child;
281 update_ctx_t *uc = parent->uc;
282 const char *bc_url = NULL;
284 child = make_child_baton(parent, path, pool);
285 child->added = TRUE;
287 if (uc->resource_walk)
289 SVN_ERR(dav_svn__send_xml(child->uc->bb, child->uc->output,
290 "<S:resource path=\"%s\">" DEBUG_CR,
291 apr_xml_quote_string(pool, child->path3, 1)));
293 else
295 const char *qname = apr_xml_quote_string(pool, child->name, 1);
296 const char *elt;
297 const char *real_path = get_real_fs_path(child, pool);
299 if (! is_dir)
301 /* files have checksums */
302 unsigned char digest[APR_MD5_DIGESTSIZE];
303 SVN_ERR(svn_fs_file_md5_checksum
304 (digest, uc->rev_root, real_path, pool));
306 child->text_checksum = svn_md5_digest_to_cstring(digest, pool);
308 else
310 /* we send baseline-collection urls when we add a directory */
311 svn_revnum_t revision;
312 revision = dav_svn__get_safe_cr(child->uc->rev_root, real_path,
313 pool);
314 bc_url = dav_svn__build_uri(child->uc->resource->info->repos,
315 DAV_SVN__BUILD_URI_BC,
316 revision, real_path,
317 0 /* add_href */, pool);
319 /* ugh, build_uri ignores the path and just builds the root
320 of the baseline collection. we have to tack the
321 real_path on manually, ignoring its leading slash. */
322 if (real_path && (! svn_path_is_empty(real_path)))
323 bc_url = svn_path_url_add_component(bc_url, real_path+1, pool);
325 /* make sure that the BC_URL is xml attribute safe. */
326 bc_url = apr_xml_quote_string(pool, bc_url, 1);
330 if (copyfrom_path == NULL)
332 if (bc_url)
333 elt = apr_psprintf(pool, "<S:add-%s name=\"%s\" "
334 "bc-url=\"%s\">" DEBUG_CR,
335 DIR_OR_FILE(is_dir), qname, bc_url);
336 else
337 elt = apr_psprintf(pool, "<S:add-%s name=\"%s\">" DEBUG_CR,
338 DIR_OR_FILE(is_dir), qname);
340 else
342 const char *qcopy = apr_xml_quote_string(pool, copyfrom_path, 1);
344 if (bc_url)
345 elt = apr_psprintf(pool, "<S:add-%s name=\"%s\" "
346 "copyfrom-path=\"%s\" copyfrom-rev=\"%ld\" "
347 "bc-url=\"%s\">" DEBUG_CR,
348 DIR_OR_FILE(is_dir),
349 qname, qcopy, copyfrom_revision,
350 bc_url);
351 else
352 elt = apr_psprintf(pool, "<S:add-%s name=\"%s\" "
353 "copyfrom-path=\"%s\""
354 " copyfrom-rev=\"%ld\">" DEBUG_CR,
355 DIR_OR_FILE(is_dir),
356 qname, qcopy, copyfrom_revision);
358 child->copyfrom = TRUE;
361 /* Resist the temptation to pass 'elt' as the format string.
362 Because it contains URIs, it might have sequences that look
363 like format string insert placeholders. For example,
364 "this%20dir" is a valid printf() format string that means
365 "this[insert an integer of width 20 here]ir". */
366 SVN_ERR(dav_svn__send_xml(child->uc->bb, child->uc->output, "%s", elt));
369 SVN_ERR(send_vsn_url(child, pool));
371 if (uc->resource_walk)
372 SVN_ERR(dav_svn__send_xml(child->uc->bb, child->uc->output,
373 "</S:resource>" DEBUG_CR));
375 *child_baton = child;
377 return SVN_NO_ERROR;
381 static svn_error_t *
382 open_helper(svn_boolean_t is_dir,
383 const char *path,
384 item_baton_t *parent,
385 svn_revnum_t base_revision,
386 apr_pool_t *pool,
387 void **child_baton)
389 item_baton_t *child = make_child_baton(parent, path, pool);
390 const char *qname = apr_xml_quote_string(pool, child->name, 1);
392 SVN_ERR(dav_svn__send_xml(child->uc->bb, child->uc->output,
393 "<S:open-%s name=\"%s\""
394 " rev=\"%ld\">" DEBUG_CR,
395 DIR_OR_FILE(is_dir), qname, base_revision));
396 SVN_ERR(send_vsn_url(child, pool));
397 *child_baton = child;
398 return SVN_NO_ERROR;
402 static svn_error_t *
403 close_helper(svn_boolean_t is_dir, item_baton_t *baton)
405 int i;
407 if (baton->uc->resource_walk)
408 return SVN_NO_ERROR;
410 /* ### ack! binary names won't float here! */
411 /* If this is a copied file/dir, we can have removed props. */
412 if (baton->removed_props && (! baton->added || baton->copyfrom))
414 const char *qname;
416 for (i = 0; i < baton->removed_props->nelts; i++)
418 /* We already XML-escaped the property name in change_xxx_prop. */
419 qname = APR_ARRAY_IDX(baton->removed_props, i, const char *);
420 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
421 "<S:remove-prop name=\"%s\"/>"
422 DEBUG_CR, qname));
426 if ((! baton->uc->send_all) && baton->changed_props && (! baton->added))
428 /* Tell the client to fetch all the props */
429 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
430 "<S:fetch-props/>" DEBUG_CR));
433 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output, "<S:prop>"));
435 /* Both modern and non-modern clients need the checksum... */
436 if (baton->text_checksum)
438 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
439 "<V:md5-checksum>%s</V:md5-checksum>",
440 baton->text_checksum));
443 /* ...but only non-modern clients want the 3 CR-related properties
444 sent like here, because they can't handle receiving these special
445 props inline like any other prop.
446 ### later on, compress via the 'scattered table' solution as
447 discussed with gstein. -bmcs */
448 if (! baton->uc->send_all)
450 /* ### grrr, these DAV: property names are already #defined in
451 ra_dav.h, and statically defined in liveprops.c. And now
452 they're hardcoded here. Isn't there some header file that both
453 sides of the network can share?? */
455 /* ### special knowledge: svn_repos_dir_delta2 will never send
456 *removals* of the commit-info "entry props". */
457 if (baton->committed_rev)
458 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
459 "<D:version-name>%s</D:version-name>",
460 baton->committed_rev));
462 if (baton->committed_date)
463 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
464 "<D:creationdate>%s</D:creationdate>",
465 baton->committed_date));
467 if (baton->last_author)
468 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
469 "<D:creator-displayname>%s"
470 "</D:creator-displayname>",
471 apr_xml_quote_string(baton->pool,
472 baton->last_author,
473 1)));
477 /* Close unconditionally, because we sent checksum unconditionally. */
478 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output, "</S:prop>\n"));
480 if (baton->added)
481 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
482 "</S:add-%s>" DEBUG_CR,
483 DIR_OR_FILE(is_dir)));
484 else
485 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
486 "</S:open-%s>" DEBUG_CR,
487 DIR_OR_FILE(is_dir)));
488 return SVN_NO_ERROR;
492 /* Send the opening tag of the update-report if it hasn't been sent
493 already. */
494 static svn_error_t *
495 maybe_start_update_report(update_ctx_t *uc)
497 if ((! uc->resource_walk) && (! uc->started_update))
499 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
500 DAV_XML_HEADER DEBUG_CR
501 "<S:update-report xmlns:S=\""
502 SVN_XML_NAMESPACE "\" "
503 "xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "
504 "xmlns:D=\"DAV:\" %s>" DEBUG_CR,
505 uc->send_all ? "send-all=\"true\"" : ""));
507 uc->started_update = TRUE;
510 return SVN_NO_ERROR;
514 static svn_error_t *
515 upd_set_target_revision(void *edit_baton,
516 svn_revnum_t target_revision,
517 apr_pool_t *pool)
519 update_ctx_t *uc = edit_baton;
521 SVN_ERR(maybe_start_update_report(uc));
523 if (! uc->resource_walk)
524 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
525 "<S:target-revision rev=\"%ld"
526 "\"/>" DEBUG_CR, target_revision));
528 return SVN_NO_ERROR;
532 static svn_error_t *
533 upd_open_root(void *edit_baton,
534 svn_revnum_t base_revision,
535 apr_pool_t *pool,
536 void **root_baton)
538 update_ctx_t *uc = edit_baton;
539 item_baton_t *b = apr_pcalloc(pool, sizeof(*b));
541 /* note that we create a subpool; the root_baton is passed to the
542 close_directory callback, where we will destroy the pool. */
544 b->uc = uc;
545 b->pool = pool;
546 b->path = uc->anchor;
547 b->path2 = uc->dst_path;
548 b->path3 = "";
550 *root_baton = b;
552 SVN_ERR(maybe_start_update_report(uc));
554 if (uc->resource_walk)
556 const char *qpath = apr_xml_quote_string(pool, b->path3, 1);
557 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
558 "<S:resource path=\"%s\">" DEBUG_CR, qpath));
560 else
562 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
563 "<S:open-directory rev=\"%ld\">"
564 DEBUG_CR, base_revision));
567 /* Only transmit the root directory's Version Resource URL if
568 there's no target. */
569 if (! *uc->target)
570 SVN_ERR(send_vsn_url(b, pool));
572 if (uc->resource_walk)
573 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
574 "</S:resource>" DEBUG_CR));
576 return SVN_NO_ERROR;
580 static svn_error_t *
581 upd_delete_entry(const char *path,
582 svn_revnum_t revision,
583 void *parent_baton,
584 apr_pool_t *pool)
586 item_baton_t *parent = parent_baton;
587 const char *qname = apr_xml_quote_string(pool,
588 svn_path_basename(path, pool), 1);
589 return dav_svn__send_xml(parent->uc->bb, parent->uc->output,
590 "<S:delete-entry name=\"%s\"/>" DEBUG_CR, qname);
594 static svn_error_t *
595 upd_add_directory(const char *path,
596 void *parent_baton,
597 const char *copyfrom_path,
598 svn_revnum_t copyfrom_revision,
599 apr_pool_t *pool,
600 void **child_baton)
602 return add_helper(TRUE /* is_dir */,
603 path, parent_baton, copyfrom_path, copyfrom_revision, pool,
604 child_baton);
608 static svn_error_t *
609 upd_open_directory(const char *path,
610 void *parent_baton,
611 svn_revnum_t base_revision,
612 apr_pool_t *pool,
613 void **child_baton)
615 return open_helper(TRUE /* is_dir */,
616 path, parent_baton, base_revision, pool, child_baton);
620 static svn_error_t *
621 upd_change_xxx_prop(void *baton,
622 const char *name,
623 const svn_string_t *value,
624 apr_pool_t *pool)
626 item_baton_t *b = baton;
627 const char *qname;
629 /* Resource walks say nothing about props. */
630 if (b->uc->resource_walk)
631 return SVN_NO_ERROR;
633 /* Else this not a resource walk, so either send props or cache them
634 to send later, depending on whether this is a modern report
635 response or not. */
637 qname = apr_xml_quote_string(b->pool, name, 1);
639 /* apr_xml_quote_string doesn't realloc if there is nothing to
640 quote, so dup the name, but only if necessary. */
641 if (qname == name)
642 qname = apr_pstrdup(b->pool, name);
645 if (b->uc->send_all)
647 if (value)
649 const char *qval;
651 if (svn_xml_is_xml_safe(value->data, value->len))
653 svn_stringbuf_t *tmp = NULL;
654 svn_xml_escape_cdata_string(&tmp, value, pool);
655 qval = tmp->data;
656 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output,
657 "<S:set-prop name=\"%s\">", qname));
659 else
661 qval = svn_base64_encode_string(value, pool)->data;
662 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output,
663 "<S:set-prop name=\"%s\" "
664 "encoding=\"base64\">" DEBUG_CR,
665 qname));
668 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output, "%s", qval));
669 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output,
670 "</S:set-prop>" DEBUG_CR));
672 else /* value is null, so this is a prop removal */
674 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output,
675 "<S:remove-prop name=\"%s\"/>" DEBUG_CR,
676 qname));
679 else /* don't do inline response, just cache prop names for close_helper */
681 /* For now, store certain entry props, because we'll need to send
682 them later as standard DAV ("D:") props. ### this should go
683 away and we should just tunnel those props on through for the
684 client to deal with. */
685 #define NSLEN (sizeof(SVN_PROP_ENTRY_PREFIX) - 1)
686 if (! strncmp(name, SVN_PROP_ENTRY_PREFIX, NSLEN))
688 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
690 b->committed_rev = value ?
691 apr_pstrdup(b->pool, value->data) : NULL;
693 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
695 b->committed_date = value ?
696 apr_pstrdup(b->pool, value->data) : NULL;
698 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
700 b->last_author = value ?
701 apr_pstrdup(b->pool, value->data) : NULL;
703 else if ((strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) == 0)
704 && (! value))
706 /* We only support delete of lock tokens, not add/modify. */
707 if (! b->removed_props)
708 b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
709 APR_ARRAY_PUSH(b->removed_props, const char *) = qname;
711 return SVN_NO_ERROR;
713 #undef NSLEN
715 if (value)
717 if (! b->changed_props)
718 b->changed_props = apr_array_make(b->pool, 1, sizeof(name));
720 APR_ARRAY_PUSH(b->changed_props, const char *) = qname;
722 else
724 if (! b->removed_props)
725 b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
727 APR_ARRAY_PUSH(b->removed_props, const char *) = qname;
731 return SVN_NO_ERROR;
735 static svn_error_t *
736 upd_close_directory(void *dir_baton, apr_pool_t *pool)
738 return close_helper(TRUE /* is_dir */, dir_baton);
742 static svn_error_t *
743 upd_add_file(const char *path,
744 void *parent_baton,
745 const char *copyfrom_path,
746 svn_revnum_t copyfrom_revision,
747 apr_pool_t *pool,
748 void **file_baton)
750 return add_helper(FALSE /* is_dir */,
751 path, parent_baton, copyfrom_path, copyfrom_revision, pool,
752 file_baton);
756 static svn_error_t *
757 upd_open_file(const char *path,
758 void *parent_baton,
759 svn_revnum_t base_revision,
760 apr_pool_t *pool,
761 void **file_baton)
763 return open_helper(FALSE /* is_dir */,
764 path, parent_baton, base_revision, pool, file_baton);
768 /* We have our own window handler and baton as a simple wrapper around
769 the real handler (which converts txdelta windows to base64-encoded
770 svndiff data). The wrapper is responsible for sending the opening
771 and closing XML tags around the svndiff data. */
772 struct window_handler_baton
774 svn_boolean_t seen_first_window; /* False until first window seen. */
775 update_ctx_t *uc;
777 /* The _real_ window handler and baton. */
778 svn_txdelta_window_handler_t handler;
779 void *handler_baton;
783 /* This implements 'svn_txdelta_window_handler_t'. */
784 static svn_error_t *
785 window_handler(svn_txdelta_window_t *window, void *baton)
787 struct window_handler_baton *wb = baton;
789 if (! wb->seen_first_window)
791 wb->seen_first_window = TRUE;
792 SVN_ERR(dav_svn__send_xml(wb->uc->bb, wb->uc->output, "<S:txdelta>"));
795 SVN_ERR(wb->handler(window, wb->handler_baton));
797 if (window == NULL)
798 SVN_ERR(dav_svn__send_xml(wb->uc->bb, wb->uc->output, "</S:txdelta>"));
800 return SVN_NO_ERROR;
804 /* This implements 'svn_txdelta_window_handler_t'.
805 During a resource walk, the driver sends an empty window as a
806 boolean indicating that a change happened to this file, but we
807 don't want to send anything over the wire as a result. */
808 static svn_error_t *
809 dummy_window_handler(svn_txdelta_window_t *window, void *baton)
811 return SVN_NO_ERROR;
815 static svn_error_t *
816 upd_apply_textdelta(void *file_baton,
817 const char *base_checksum,
818 apr_pool_t *pool,
819 svn_txdelta_window_handler_t *handler,
820 void **handler_baton)
822 item_baton_t *file = file_baton;
823 struct window_handler_baton *wb;
824 svn_stream_t *base64_stream;
826 /* Store the base checksum and the fact the file's text changed. */
827 file->base_checksum = apr_pstrdup(file->pool, base_checksum);
828 file->text_changed = TRUE;
830 /* If this is a resource walk, or if we're not in "send-all" mode,
831 we don't actually want to transmit text-deltas. */
832 if (file->uc->resource_walk || (! file->uc->send_all))
834 *handler = dummy_window_handler;
835 *handler_baton = NULL;
836 return SVN_NO_ERROR;
839 wb = apr_palloc(file->pool, sizeof(*wb));
840 wb->seen_first_window = FALSE;
841 wb->uc = file->uc;
842 base64_stream = dav_svn__make_base64_output_stream(wb->uc->bb,
843 wb->uc->output,
844 file->pool);
846 svn_txdelta_to_svndiff2(&(wb->handler), &(wb->handler_baton),
847 base64_stream, file->uc->svndiff_version,
848 file->pool);
850 *handler = window_handler;
851 *handler_baton = wb;
853 return SVN_NO_ERROR;
857 static svn_error_t *
858 upd_close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool)
860 item_baton_t *file = file_baton;
862 file->text_checksum = text_checksum ?
863 apr_pstrdup(file->pool, text_checksum) : NULL;
865 /* If we are not in "send all" mode, and this file is not a new
866 addition or didn't otherwise have changed text, tell the client
867 to fetch it. */
868 if ((! file->uc->send_all) && (! file->added) && file->text_changed)
870 const char *elt;
871 elt = apr_psprintf(pool, "<S:fetch-file%s%s%s/>" DEBUG_CR,
872 file->base_checksum ? " base-checksum=\"" : "",
873 file->base_checksum ? file->base_checksum : "",
874 file->base_checksum ? "\"" : "");
875 SVN_ERR(dav_svn__send_xml(file->uc->bb, file->uc->output, "%s", elt));
878 return close_helper(FALSE /* is_dir */, file);
882 static svn_error_t *
883 upd_close_edit(void *edit_baton, apr_pool_t *pool)
885 update_ctx_t *uc = edit_baton;
887 /* Our driver will unconditionally close the update report... So if
888 the report hasn't even been started yet, start it now. */
889 return maybe_start_update_report(uc);
893 /* Return a specific error associated with the contents of TAGNAME
894 being malformed. Use pool for allocations. */
895 static dav_error *
896 malformed_element_error(const char *tagname, apr_pool_t *pool)
898 const char *errstr = apr_pstrcat(pool, "The request's '", tagname,
899 "' element is malformed; there "
900 "is a problem with the client.", NULL);
901 return dav_svn__new_error_tag(pool, HTTP_BAD_REQUEST, 0, errstr,
902 SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
906 dav_error *
907 dav_svn__update_report(const dav_resource *resource,
908 const apr_xml_doc *doc,
909 ap_filter_t *output)
911 svn_delta_editor_t *editor;
912 apr_xml_elem *child;
913 void *rbaton = NULL;
914 update_ctx_t uc = { 0 };
915 svn_revnum_t revnum = SVN_INVALID_REVNUM;
916 svn_revnum_t from_revnum = SVN_INVALID_REVNUM;
917 int ns;
918 /* entry_counter and entry_is_empty are for operational logging. */
919 int entry_counter = 0;
920 svn_boolean_t entry_is_empty = FALSE;
921 svn_error_t *serr;
922 dav_error *derr = NULL;
923 apr_status_t apr_err;
924 const char *src_path = NULL;
925 const char *dst_path = NULL;
926 const dav_svn_repos *repos = resource->info->repos;
927 const char *target = "";
928 svn_boolean_t text_deltas = TRUE;
929 svn_depth_t requested_depth = svn_depth_unknown;
930 svn_boolean_t saw_depth = FALSE;
931 svn_boolean_t saw_recursive = FALSE;
932 svn_boolean_t resource_walk = FALSE;
933 svn_boolean_t ignore_ancestry = FALSE;
934 svn_boolean_t send_copyfrom_args = FALSE;
935 dav_svn__authz_read_baton arb;
936 apr_pool_t *subpool = svn_pool_create(resource->pool);
938 /* Construct the authz read check baton. */
939 arb.r = resource->info->r;
940 arb.repos = repos;
942 if (resource->info->restype != DAV_SVN_RESTYPE_VCC)
944 return dav_svn__new_error_tag(resource->pool, HTTP_CONFLICT, 0,
945 "This report can only be run against "
946 "a VCC.",
947 SVN_DAV_ERROR_NAMESPACE,
948 SVN_DAV_ERROR_TAG);
951 ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
952 if (ns == -1)
954 return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
955 "The request does not contain the 'svn:' "
956 "namespace, so it is not going to have an "
957 "svn:target-revision element. That element "
958 "is required.",
959 SVN_DAV_ERROR_NAMESPACE,
960 SVN_DAV_ERROR_TAG);
963 /* If server configuration permits bulk updates (a report with props
964 and textdeltas inline, rather than placeholder tags that tell the
965 client to do further fetches), look to see if client requested as
966 much. */
967 if (repos->bulk_updates)
969 apr_xml_attr *this_attr;
971 for (this_attr = doc->root->attr; this_attr; this_attr = this_attr->next)
973 if ((strcmp(this_attr->name, "send-all") == 0)
974 && (strcmp(this_attr->value, "true") == 0))
976 uc.send_all = TRUE;
977 break;
982 for (child = doc->root->first_child; child != NULL; child = child->next)
984 /* Note that child->name might not match any of the cases below.
985 Thus, the check for non-empty cdata in each of these cases
986 cannot be moved to the top of the loop, because then it would
987 wrongly catch other elements that do allow empty cdata. */
988 const char *cdata;
990 if (child->ns == ns && strcmp(child->name, "target-revision") == 0)
992 cdata = dav_xml_get_cdata(child, resource->pool, 1);
993 if (! *cdata)
994 return malformed_element_error(child->name, resource->pool);
995 revnum = SVN_STR_TO_REV(cdata);
997 if (child->ns == ns && strcmp(child->name, "src-path") == 0)
999 dav_svn__uri_info this_info;
1000 cdata = dav_xml_get_cdata(child, resource->pool, 0);
1001 if (! *cdata)
1002 return malformed_element_error(child->name, resource->pool);
1003 if ((derr = dav_svn__test_canonical(cdata, resource->pool)))
1004 return derr;
1005 if ((serr = dav_svn__simple_parse_uri(&this_info, resource,
1006 cdata, resource->pool)))
1007 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1008 "Could not parse 'src-path' URL.",
1009 resource->pool);
1010 src_path = this_info.repos_path;
1012 if (child->ns == ns && strcmp(child->name, "dst-path") == 0)
1014 dav_svn__uri_info this_info;
1015 cdata = dav_xml_get_cdata(child, resource->pool, 0);
1016 if (! *cdata)
1017 return malformed_element_error(child->name, resource->pool);
1018 if ((derr = dav_svn__test_canonical(cdata, resource->pool)))
1019 return derr;
1020 if ((serr = dav_svn__simple_parse_uri(&this_info, resource,
1021 cdata, resource->pool)))
1022 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1023 "Could not parse 'dst-path' URL.",
1024 resource->pool);
1025 dst_path = this_info.repos_path;
1027 if (child->ns == ns && strcmp(child->name, "update-target") == 0)
1029 cdata = dav_xml_get_cdata(child, resource->pool, 0);
1030 if ((derr = dav_svn__test_canonical(cdata, resource->pool)))
1031 return derr;
1032 target = cdata;
1034 if (child->ns == ns && strcmp(child->name, "depth") == 0)
1036 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1037 if (! *cdata)
1038 return malformed_element_error(child->name, resource->pool);
1039 requested_depth = svn_depth_from_word(cdata);
1040 saw_depth = TRUE;
1042 if ((child->ns == ns && strcmp(child->name, "recursive") == 0)
1043 && (! saw_depth))
1045 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1046 if (! *cdata)
1047 return malformed_element_error(child->name, resource->pool);
1048 if (strcmp(cdata, "no") == 0)
1049 requested_depth = svn_depth_files;
1050 else
1051 requested_depth = svn_depth_infinity;
1052 /* Note that even modern, depth-aware clients still transmit
1053 "no" for "recursive" (along with "files" for "depth") in
1054 the svn_depth_files case, and transmit "no" in the
1055 svn_depth_empty case. This is because they don't know if
1056 they're talking to a depth-aware server or not, and they
1057 don't need to know -- all they have to do is transmit
1058 both, and the server will DTRT either way (although in
1059 the svn_depth_empty case, the client will still have some
1060 work to do in ignoring the files that come down).
1062 When both "depth" and "recursive" are sent, we don't
1063 bother to check if they're mutually consistent, we just
1064 let depth dominate. */
1065 saw_recursive = TRUE;
1067 if (child->ns == ns && strcmp(child->name, "ignore-ancestry") == 0)
1069 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1070 if (! *cdata)
1071 return malformed_element_error(child->name, resource->pool);
1072 if (strcmp(cdata, "no") != 0)
1073 ignore_ancestry = TRUE;
1075 if (child->ns == ns && strcmp(child->name, "send-copyfrom-args") == 0)
1077 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1078 if (! *cdata)
1079 return malformed_element_error(child->name, resource->pool);
1080 if (strcmp(cdata, "no") != 0)
1081 send_copyfrom_args = TRUE;
1083 if (child->ns == ns && strcmp(child->name, "resource-walk") == 0)
1085 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1086 if (! *cdata)
1087 return malformed_element_error(child->name, resource->pool);
1088 if (strcmp(cdata, "no") != 0)
1089 resource_walk = TRUE;
1091 if (child->ns == ns && strcmp(child->name, "text-deltas") == 0)
1093 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1094 if (! *cdata)
1095 return malformed_element_error(child->name, resource->pool);
1096 if (strcmp(cdata, "no") == 0)
1097 text_deltas = FALSE;
1101 if (!saw_depth && !saw_recursive && (requested_depth == svn_depth_unknown))
1102 requested_depth = svn_depth_infinity;
1104 /* If the client never sent a <src-path> element, it's old and
1105 sending a style of report that we no longer allow. */
1106 if (! src_path)
1108 return dav_svn__new_error_tag
1109 (resource->pool, HTTP_BAD_REQUEST, 0,
1110 "The request did not contain the '<src-path>' element.\n"
1111 "This may indicate that your client is too old.",
1112 SVN_DAV_ERROR_NAMESPACE,
1113 SVN_DAV_ERROR_TAG);
1116 /* If a revision for this operation was not dictated to us, this
1117 means "update to whatever the current HEAD is now". */
1118 if (revnum == SVN_INVALID_REVNUM)
1120 if ((serr = svn_fs_youngest_rev(&revnum, repos->fs, resource->pool)))
1121 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1122 "Could not determine the youngest "
1123 "revision for the update process.",
1124 resource->pool);
1127 uc.svndiff_version = resource->info->svndiff_version;
1128 uc.resource = resource;
1129 uc.output = output;
1130 uc.anchor = src_path;
1131 uc.target = target;
1132 uc.bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
1133 uc.pathmap = NULL;
1134 if (dst_path) /* we're doing a 'switch' */
1136 if (*target)
1138 /* if the src is split into anchor/target, so must the
1139 telescoping dst_path be. */
1140 uc.dst_path = svn_path_dirname(dst_path, resource->pool);
1142 /* Also, the svn_repos_dir_delta2() is going to preserve our
1143 target's name, so we need a pathmap entry for that. */
1144 if (! uc.pathmap)
1145 uc.pathmap = apr_hash_make(resource->pool);
1146 add_to_path_map(uc.pathmap,
1147 svn_path_join(src_path, target, resource->pool),
1148 dst_path);
1150 else
1152 uc.dst_path = dst_path;
1155 else /* we're doing an update, so src and dst are the same. */
1156 uc.dst_path = uc.anchor;
1158 /* Get the root of the revision we want to update to. This will be used
1159 to generated stable id values. */
1160 if ((serr = svn_fs_revision_root(&uc.rev_root, repos->fs,
1161 revnum, resource->pool)))
1163 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1164 "The revision root could not be created.",
1165 resource->pool);
1168 /* If the client did *not* request 'send-all' mode, then we will be
1169 sending only a "skelta" of the difference, which will not need to
1170 contain actual text deltas. */
1171 if (! uc.send_all)
1172 text_deltas = FALSE;
1174 /* When we call svn_repos_finish_report, it will ultimately run
1175 dir_delta() between REPOS_PATH/TARGET and TARGET_PATH. In the
1176 case of an update or status, these paths should be identical. In
1177 the case of a switch, they should be different. */
1178 editor = svn_delta_default_editor(resource->pool);
1179 editor->set_target_revision = upd_set_target_revision;
1180 editor->open_root = upd_open_root;
1181 editor->delete_entry = upd_delete_entry;
1182 editor->add_directory = upd_add_directory;
1183 editor->open_directory = upd_open_directory;
1184 editor->change_dir_prop = upd_change_xxx_prop;
1185 editor->close_directory = upd_close_directory;
1186 editor->absent_directory = upd_absent_directory;
1187 editor->add_file = upd_add_file;
1188 editor->open_file = upd_open_file;
1189 editor->apply_textdelta = upd_apply_textdelta;
1190 editor->change_file_prop = upd_change_xxx_prop;
1191 editor->close_file = upd_close_file;
1192 editor->absent_file = upd_absent_file;
1193 editor->close_edit = upd_close_edit;
1194 if ((serr = svn_repos_begin_report2(&rbaton, revnum,
1195 repos->repos,
1196 src_path, target,
1197 dst_path,
1198 text_deltas,
1199 requested_depth,
1200 ignore_ancestry,
1201 send_copyfrom_args,
1202 editor, &uc,
1203 dav_svn__authz_read_func(&arb),
1204 &arb,
1205 resource->pool)))
1207 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1208 "The state report gatherer could not be "
1209 "created.",
1210 resource->pool);
1213 /* scan the XML doc for state information */
1214 for (child = doc->root->first_child; child != NULL; child = child->next)
1215 if (child->ns == ns)
1217 /* Clear our subpool. */
1218 svn_pool_clear(subpool);
1220 if (strcmp(child->name, "entry") == 0)
1222 const char *path;
1223 svn_revnum_t rev = SVN_INVALID_REVNUM;
1224 svn_boolean_t saw_rev = FALSE;
1225 const char *linkpath = NULL;
1226 const char *locktoken = NULL;
1227 svn_boolean_t start_empty = FALSE;
1228 apr_xml_attr *this_attr = child->attr;
1229 /* Default to infinity, for old clients that don't send depth. */
1230 svn_depth_t depth = svn_depth_infinity;
1232 entry_counter++;
1234 while (this_attr)
1236 if (strcmp(this_attr->name, "rev") == 0)
1238 rev = SVN_STR_TO_REV(this_attr->value);
1239 saw_rev = TRUE;
1241 else if (strcmp(this_attr->name, "depth") == 0)
1242 depth = svn_depth_from_word(this_attr->value);
1243 else if (strcmp(this_attr->name, "linkpath") == 0)
1244 linkpath = this_attr->value;
1245 else if (strcmp(this_attr->name, "start-empty") == 0)
1246 start_empty = entry_is_empty = TRUE;
1247 else if (strcmp(this_attr->name, "lock-token") == 0)
1248 locktoken = this_attr->value;
1250 this_attr = this_attr->next;
1253 /* we require the `rev' attribute for this to make sense */
1254 if (! saw_rev)
1256 serr = svn_error_create(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1257 NULL, "Missing XML attribute: rev");
1258 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1259 "A failure occurred while "
1260 "recording one of the items of "
1261 "working copy state.",
1262 resource->pool);
1263 goto cleanup;
1266 /* get cdata, stripping whitespace */
1267 path = dav_xml_get_cdata(child, subpool, 0);
1269 /* determine the "from rev" for revision range ops */
1270 if (strcmp(path, "") == 0)
1271 from_revnum = rev;
1273 if (! linkpath)
1274 serr = svn_repos_set_path3(rbaton, path, rev, depth,
1275 start_empty, locktoken, subpool);
1276 else
1277 serr = svn_repos_link_path3(rbaton, path, linkpath, rev, depth,
1278 start_empty, locktoken, subpool);
1279 if (serr != NULL)
1281 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1282 "A failure occurred while "
1283 "recording one of the items of "
1284 "working copy state.",
1285 resource->pool);
1286 goto cleanup;
1289 /* now, add this path to our path map, but only if we are
1290 doing a regular update (not a `switch') */
1291 if (linkpath && (! dst_path))
1293 const char *this_path;
1294 if (! uc.pathmap)
1295 uc.pathmap = apr_hash_make(resource->pool);
1296 this_path = svn_path_join_many(apr_hash_pool_get(uc.pathmap),
1297 src_path, target, path, NULL);
1298 add_to_path_map(uc.pathmap, this_path, linkpath);
1301 else if (strcmp(child->name, "missing") == 0)
1303 /* get cdata, stripping whitespace */
1304 const char *path = dav_xml_get_cdata(child, subpool, 0);
1305 serr = svn_repos_delete_path(rbaton, path, subpool);
1306 if (serr != NULL)
1308 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1309 "A failure occurred while "
1310 "recording one of the (missing) "
1311 "items of working copy state.",
1312 resource->pool);
1313 goto cleanup;
1318 /* Try to deduce what sort of client command is being run, then
1319 make this guess available to apache's logging subsystem. */
1321 const char *action, *spath, *log_depth;
1323 if (requested_depth == svn_depth_unknown)
1324 log_depth = "";
1325 else
1326 log_depth = apr_pstrcat(resource->pool, " depth=",
1327 svn_depth_to_word(requested_depth), NULL);
1329 if (target)
1330 spath = svn_path_join(src_path, target, resource->pool);
1331 else
1332 spath = src_path;
1334 /* If a second path was passed to svn_repos_dir_delta2(), then it
1335 must have been switch, diff, or merge. */
1336 if (dst_path)
1338 /* diff/merge don't ask for inline text-deltas. */
1339 if (uc.send_all)
1340 action = svn_log__switch(spath, dst_path, revnum,
1341 requested_depth, resource->pool);
1342 else
1343 action = svn_log__diff(spath, from_revnum, dst_path, revnum,
1344 requested_depth, ignore_ancestry,
1345 resource->pool);
1348 /* Otherwise, it must be checkout, export, update, or status -u. */
1349 else
1351 /* svn_client_checkout() creates a single root directory, then
1352 reports it (and it alone) to the server as being empty. */
1353 if (entry_counter == 1 && entry_is_empty)
1354 action = svn_log__checkout(spath, revnum, requested_depth,
1355 resource->pool);
1356 else
1358 if (text_deltas)
1359 action = svn_log__update(spath, revnum, requested_depth,
1360 send_copyfrom_args,
1361 resource->pool);
1362 else
1363 action = svn_log__status(spath, revnum, requested_depth,
1364 resource->pool);
1368 dav_svn__operational_log(resource->info, action);
1371 /* this will complete the report, and then drive our editor to generate
1372 the response to the client. */
1373 serr = svn_repos_finish_report(rbaton, resource->pool);
1374 if (serr)
1376 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1377 "A failure occurred while "
1378 "driving the update report editor",
1379 resource->pool);
1380 goto cleanup;
1383 /* We're finished with the report baton. Note that so we don't try
1384 to abort this report later. */
1385 rbaton = NULL;
1387 /* ### Temporarily disable resource_walks for single-file switch
1388 operations. It isn't strictly necessary. */
1389 if (dst_path && resource_walk)
1391 /* Sanity check: if we switched a file, we can't do a resource
1392 walk. dir_delta would choke if we pass a filepath as the
1393 'target'. Also, there's no need to do the walk, since the
1394 new vsn-rsc-url was already in the earlier part of the report. */
1395 svn_node_kind_t dst_kind;
1396 if ((serr = svn_fs_check_path(&dst_kind, uc.rev_root, dst_path,
1397 resource->pool)))
1399 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1400 "Failed checking destination path kind",
1401 resource->pool);
1402 goto cleanup;
1404 if (dst_kind != svn_node_dir)
1405 resource_walk = FALSE;
1408 /* The potential "resource walk" part of the update-report. */
1409 if (dst_path && resource_walk) /* this was a 'switch' operation */
1411 /* send a second embedded <S:resource-walk> tree that contains
1412 the new vsn-rsc-urls for the switched dir. this walk
1413 contains essentially nothing but <add> tags. */
1414 svn_fs_root_t *zero_root;
1415 serr = svn_fs_revision_root(&zero_root, repos->fs, 0,
1416 resource->pool);
1417 if (serr)
1419 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1420 "Failed to find the revision root",
1421 resource->pool);
1422 goto cleanup;
1425 serr = dav_svn__send_xml(uc.bb, uc.output,
1426 "<S:resource-walk>" DEBUG_CR);
1427 if (serr)
1429 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1430 "Unable to begin resource walk",
1431 resource->pool);
1432 goto cleanup;
1435 uc.resource_walk = TRUE;
1437 /* Compare subtree DST_PATH within a pristine revision to
1438 revision 0. This should result in nothing but 'add' calls
1439 to the editor. */
1440 serr = svn_repos_dir_delta2(zero_root, "", target,
1441 uc.rev_root, dst_path,
1442 /* re-use the editor */
1443 editor, &uc,
1444 dav_svn__authz_read_func(&arb),
1445 &arb, FALSE /* text-deltas */,
1446 requested_depth,
1447 TRUE /* entryprops */,
1448 FALSE /* ignore-ancestry */,
1449 resource->pool);
1451 if (serr)
1453 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1454 "Resource walk failed.",
1455 resource->pool);
1456 goto cleanup;
1459 serr = dav_svn__send_xml(uc.bb, uc.output,
1460 "</S:resource-walk>" DEBUG_CR);
1461 if (serr)
1463 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1464 "Unable to complete resource walk.",
1465 resource->pool);
1466 goto cleanup;
1470 /* Close the report body, unless some error prevented it from being
1471 started in the first place. */
1472 if (uc.started_update)
1474 if ((serr = dav_svn__send_xml(uc.bb, uc.output,
1475 "</S:update-report>" DEBUG_CR)))
1477 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1478 "Unable to complete update report.",
1479 resource->pool);
1480 goto cleanup;
1484 cleanup:
1486 /* Flush the contents of the brigade (returning an error only if we
1487 don't already have one). */
1488 if ((! derr) && ((apr_err = ap_fflush(output, uc.bb))))
1489 derr = dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
1490 HTTP_INTERNAL_SERVER_ERROR,
1491 "Error flushing brigade.",
1492 resource->pool);
1494 /* if an error was produced EITHER by the dir_delta drive or the
1495 resource-walker... */
1496 if (derr)
1498 if (rbaton)
1499 svn_error_clear(svn_repos_abort_report(rbaton, resource->pool));
1500 return derr;
1503 /* Destroy our subpool. */
1504 svn_pool_destroy(subpool);
1506 return NULL;