Reorganize the output to "svnserve --help".
[svn.git] / subversion / mod_dav_svn / reports / update.c
blob29ad869a09226a5ebc12faa24f0e0eda8165737e
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"
38 #include "../dav_svn.h"
41 typedef struct {
42 const dav_resource *resource;
44 /* the revision we are updating to. used to generated IDs. */
45 svn_fs_root_t *rev_root;
47 const char *anchor;
48 const char *target;
50 /* if doing a regular update, then dst_path == anchor. if this is a
51 'switch' operation, then this field is the fs path that is being
52 switched to. This path needs to telescope in the update-editor
53 just like 'anchor' above; it's used for retrieving CR's and
54 vsn-url's during the edit. */
55 const char *dst_path;
57 /* this buffers the output for a bit and is automatically flushed,
58 at appropriate times, by the Apache filter system. */
59 apr_bucket_brigade *bb;
61 /* where to deliver the output */
62 ap_filter_t *output;
64 /* where do these editor paths *really* point to? */
65 apr_hash_t *pathmap;
67 /* are we doing a resource walk? */
68 svn_boolean_t resource_walk;
70 /* True iff we've already sent the open tag for the update. */
71 svn_boolean_t started_update;
73 /* True iff client requested all data inline in the report. */
74 svn_boolean_t send_all;
76 /* SVNDIFF version to send to client. */
77 int svndiff_version;
78 } update_ctx_t;
80 typedef struct item_baton_t {
81 apr_pool_t *pool;
82 update_ctx_t *uc;
83 struct item_baton_t *parent; /* the parent of this item. */
84 const char *name; /* the single-component name of this item */
85 const char *path; /* a telescoping extension of uc->anchor */
86 const char *path2; /* a telescoping extension of uc->dst_path */
87 const char *path3; /* a telescoping extension of uc->dst_path
88 without dst_path as prefix. */
90 const char *base_checksum; /* base_checksum (from apply_textdelta) */
91 const char *text_checksum; /* text_checksum (from close_file) */
93 svn_boolean_t text_changed; /* Did the file's contents change? */
94 svn_boolean_t added; /* File added? (Implies text_changed.) */
95 apr_array_header_t *changed_props; /* array of const char * prop names */
96 apr_array_header_t *removed_props; /* array of const char * prop names */
98 /* "entry props" */
99 const char *committed_rev;
100 const char *committed_date;
101 const char *last_author;
103 } item_baton_t;
106 #define DIR_OR_FILE(is_dir) ((is_dir) ? "directory" : "file")
109 /* add PATH to the pathmap HASH with a repository path of LINKPATH.
110 if LINKPATH is NULL, PATH will map to itself. */
111 static void
112 add_to_path_map(apr_hash_t *hash, const char *path, const char *linkpath)
114 /* normalize 'root paths' to have a slash */
115 const char *norm_path = strcmp(path, "") ? path : "/";
117 /* if there is an actual linkpath given, it is the repos path, else
118 our path maps to itself. */
119 const char *repos_path = linkpath ? linkpath : norm_path;
121 /* now, geez, put the path in the map already! */
122 apr_hash_set(hash, path, APR_HASH_KEY_STRING, repos_path);
126 /* return the actual repository path referred to by the editor's PATH,
127 allocated in POOL, determined by examining the pathmap HASH. */
128 static const char *
129 get_from_path_map(apr_hash_t *hash, const char *path, apr_pool_t *pool)
131 const char *repos_path;
132 svn_stringbuf_t *my_path;
134 /* no hash means no map. that's easy enough. */
135 if (! hash)
136 return apr_pstrdup(pool, path);
138 if ((repos_path = apr_hash_get(hash, path, APR_HASH_KEY_STRING)))
140 /* what luck! this path is a hash key! if there is a linkpath,
141 use that, else return the path itself. */
142 return apr_pstrdup(pool, repos_path);
145 /* bummer. PATH wasn't a key in path map, so we get to start
146 hacking off components and looking for a parent from which to
147 derive a repos_path. use a stringbuf for convenience. */
148 my_path = svn_stringbuf_create(path, pool);
151 svn_path_remove_component(my_path);
152 if ((repos_path = apr_hash_get(hash, my_path->data, my_path->len)))
154 /* we found a mapping ... but of one of PATH's parents.
155 soooo, we get to re-append the chunks of PATH that we
156 broke off to the REPOS_PATH we found. */
157 return apr_pstrcat(pool, repos_path, "/",
158 path + my_path->len + 1, NULL);
161 while (! svn_path_is_empty(my_path->data)
162 && strcmp(my_path->data, "/") != 0);
164 /* well, we simply never found anything worth mentioning the map.
165 PATH is its own default finding, then. */
166 return apr_pstrdup(pool, path);
170 static item_baton_t *
171 make_child_baton(item_baton_t *parent, const char *path, apr_pool_t *pool)
173 item_baton_t *baton;
175 baton = apr_pcalloc(pool, sizeof(*baton));
176 baton->pool = pool;
177 baton->uc = parent->uc;
178 baton->name = svn_path_basename(path, pool);
179 baton->parent = parent;
181 /* Telescope the path based on uc->anchor. */
182 baton->path = svn_path_join(parent->path, baton->name, pool);
184 /* Telescope the path based on uc->dst_path in the exact same way. */
185 baton->path2 = svn_path_join(parent->path2, baton->name, pool);
187 /* Telescope the third path: it's relative, not absolute, to
188 dst_path. Now, we gotta be careful here, because if this
189 operation had a target, and we're it, then we have to use the
190 basename of our source reflection instead of our own. */
191 if ((*baton->uc->target) && (! parent->parent))
192 baton->path3 = svn_path_join(parent->path3, baton->uc->target, pool);
193 else
194 baton->path3 = svn_path_join(parent->path3, baton->name, pool);
196 return baton;
200 /* Get the real filesystem PATH for BATON, and return the value
201 allocated from POOL. This function juggles the craziness of
202 updates, switches, and updates of switched things. */
203 static const char *
204 get_real_fs_path(item_baton_t *baton, apr_pool_t *pool)
206 const char *path = get_from_path_map(baton->uc->pathmap, baton->path, pool);
207 return strcmp(path, baton->path) ? path : baton->path2;
211 static svn_error_t *
212 send_vsn_url(item_baton_t *baton, apr_pool_t *pool)
214 const char *href;
215 const char *path;
216 svn_revnum_t revision;
218 /* Try to use the CR, assuming the path exists in CR. */
219 path = get_real_fs_path(baton, pool);
220 revision = dav_svn__get_safe_cr(baton->uc->rev_root, path, pool);
222 href = dav_svn__build_uri(baton->uc->resource->info->repos,
223 DAV_SVN__BUILD_URI_VERSION,
224 revision, path, 0 /* add_href */, pool);
226 return dav_svn__send_xml(baton->uc->bb, baton->uc->output,
227 "<D:checked-in><D:href>%s</D:href></D:checked-in>"
228 DEBUG_CR, apr_xml_quote_string(pool, href, 1));
232 static svn_error_t *
233 absent_helper(svn_boolean_t is_dir,
234 const char *path,
235 item_baton_t *parent,
236 apr_pool_t *pool)
238 update_ctx_t *uc = parent->uc;
240 if (! uc->resource_walk)
242 const char *elt = apr_psprintf(pool,
243 "<S:absent-%s name=\"%s\"/>" DEBUG_CR,
244 DIR_OR_FILE(is_dir),
245 apr_xml_quote_string
246 (pool,
247 svn_path_basename(path, pool),
248 1));
249 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output, "%s", elt));
252 return SVN_NO_ERROR;
256 static svn_error_t *
257 upd_absent_directory(const char *path, void *parent_baton, apr_pool_t *pool)
259 return absent_helper(TRUE, path, parent_baton, pool);
263 static svn_error_t *
264 upd_absent_file(const char *path, void *parent_baton, apr_pool_t *pool)
266 return absent_helper(FALSE, path, parent_baton, pool);
270 static svn_error_t *
271 add_helper(svn_boolean_t is_dir,
272 const char *path,
273 item_baton_t *parent,
274 const char *copyfrom_path,
275 svn_revnum_t copyfrom_revision,
276 apr_pool_t *pool,
277 void **child_baton)
279 item_baton_t *child;
280 update_ctx_t *uc = parent->uc;
281 const char *bc_url = NULL;
283 child = make_child_baton(parent, path, pool);
284 child->added = TRUE;
286 if (uc->resource_walk)
288 SVN_ERR(dav_svn__send_xml(child->uc->bb, child->uc->output,
289 "<S:resource path=\"%s\">" DEBUG_CR,
290 apr_xml_quote_string(pool, child->path3, 1)));
292 else
294 const char *qname = apr_xml_quote_string(pool, child->name, 1);
295 const char *elt;
296 const char *real_path = get_real_fs_path(child, pool);
298 if (! is_dir)
300 /* files have checksums */
301 unsigned char digest[APR_MD5_DIGESTSIZE];
302 SVN_ERR(svn_fs_file_md5_checksum
303 (digest, uc->rev_root, real_path, pool));
305 child->text_checksum = svn_md5_digest_to_cstring(digest, pool);
307 else
309 /* we send baseline-collection urls when we add a directory */
310 svn_revnum_t revision;
311 revision = dav_svn__get_safe_cr(child->uc->rev_root, real_path,
312 pool);
313 bc_url = dav_svn__build_uri(child->uc->resource->info->repos,
314 DAV_SVN__BUILD_URI_BC,
315 revision, real_path,
316 0 /* add_href */, pool);
318 /* ugh, build_uri ignores the path and just builds the root
319 of the baseline collection. we have to tack the
320 real_path on manually, ignoring its leading slash. */
321 if (real_path && (! svn_path_is_empty(real_path)))
322 bc_url = svn_path_url_add_component(bc_url, real_path+1, pool);
324 /* make sure that the BC_URL is xml attribute safe. */
325 bc_url = apr_xml_quote_string(pool, bc_url, 1);
329 if (copyfrom_path == NULL)
331 if (bc_url)
332 elt = apr_psprintf(pool, "<S:add-%s name=\"%s\" "
333 "bc-url=\"%s\">" DEBUG_CR,
334 DIR_OR_FILE(is_dir), qname, bc_url);
335 else
336 elt = apr_psprintf(pool, "<S:add-%s name=\"%s\">" DEBUG_CR,
337 DIR_OR_FILE(is_dir), qname);
339 else
341 const char *qcopy = apr_xml_quote_string(pool, copyfrom_path, 1);
343 if (bc_url)
344 elt = apr_psprintf(pool, "<S:add-%s name=\"%s\" "
345 "copyfrom-path=\"%s\" copyfrom-rev=\"%ld\" "
346 "bc-url=\"%s\">" DEBUG_CR,
347 DIR_OR_FILE(is_dir),
348 qname, qcopy, copyfrom_revision,
349 bc_url);
350 else
351 elt = apr_psprintf(pool, "<S:add-%s name=\"%s\" "
352 "copyfrom-path=\"%s\""
353 " copyfrom-rev=\"%ld\">" DEBUG_CR,
354 DIR_OR_FILE(is_dir),
355 qname, qcopy, copyfrom_revision);
358 /* Resist the temptation to pass 'elt' as the format string.
359 Because it contains URIs, it might have sequences that look
360 like format string insert placeholders. For example,
361 "this%20dir" is a valid printf() format string that means
362 "this[insert an integer of width 20 here]ir". */
363 SVN_ERR(dav_svn__send_xml(child->uc->bb, child->uc->output, "%s", elt));
366 SVN_ERR(send_vsn_url(child, pool));
368 if (uc->resource_walk)
369 SVN_ERR(dav_svn__send_xml(child->uc->bb, child->uc->output,
370 "</S:resource>" DEBUG_CR));
372 *child_baton = child;
374 return SVN_NO_ERROR;
378 static svn_error_t *
379 open_helper(svn_boolean_t is_dir,
380 const char *path,
381 item_baton_t *parent,
382 svn_revnum_t base_revision,
383 apr_pool_t *pool,
384 void **child_baton)
386 item_baton_t *child = make_child_baton(parent, path, pool);
387 const char *qname = apr_xml_quote_string(pool, child->name, 1);
389 SVN_ERR(dav_svn__send_xml(child->uc->bb, child->uc->output,
390 "<S:open-%s name=\"%s\""
391 " rev=\"%ld\">" DEBUG_CR,
392 DIR_OR_FILE(is_dir), qname, base_revision));
393 SVN_ERR(send_vsn_url(child, pool));
394 *child_baton = child;
395 return SVN_NO_ERROR;
399 static svn_error_t *
400 close_helper(svn_boolean_t is_dir, item_baton_t *baton)
402 int i;
404 if (baton->uc->resource_walk)
405 return SVN_NO_ERROR;
407 /* ### ack! binary names won't float here! */
408 if (baton->removed_props && (! baton->added))
410 const char *qname;
412 for (i = 0; i < baton->removed_props->nelts; i++)
414 /* We already XML-escaped the property name in change_xxx_prop. */
415 qname = APR_ARRAY_IDX(baton->removed_props, i, const char *);
416 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
417 "<S:remove-prop name=\"%s\"/>"
418 DEBUG_CR, qname));
422 if ((! baton->uc->send_all) && baton->changed_props && (! baton->added))
424 /* Tell the client to fetch all the props */
425 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
426 "<S:fetch-props/>" DEBUG_CR));
429 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output, "<S:prop>"));
431 /* Both modern and non-modern clients need the checksum... */
432 if (baton->text_checksum)
434 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
435 "<V:md5-checksum>%s</V:md5-checksum>",
436 baton->text_checksum));
439 /* ...but only non-modern clients want the 3 CR-related properties
440 sent like here, because they can't handle receiving these special
441 props inline like any other prop.
442 ### later on, compress via the 'scattered table' solution as
443 discussed with gstein. -bmcs */
444 if (! baton->uc->send_all)
446 /* ### grrr, these DAV: property names are already #defined in
447 ra_dav.h, and statically defined in liveprops.c. And now
448 they're hardcoded here. Isn't there some header file that both
449 sides of the network can share?? */
451 /* ### special knowledge: svn_repos_dir_delta2 will never send
452 *removals* of the commit-info "entry props". */
453 if (baton->committed_rev)
454 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
455 "<D:version-name>%s</D:version-name>",
456 baton->committed_rev));
458 if (baton->committed_date)
459 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
460 "<D:creationdate>%s</D:creationdate>",
461 baton->committed_date));
463 if (baton->last_author)
464 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
465 "<D:creator-displayname>%s"
466 "</D:creator-displayname>",
467 apr_xml_quote_string(baton->pool,
468 baton->last_author,
469 1)));
473 /* Close unconditionally, because we sent checksum unconditionally. */
474 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output, "</S:prop>\n"));
476 if (baton->added)
477 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
478 "</S:add-%s>" DEBUG_CR,
479 DIR_OR_FILE(is_dir)));
480 else
481 SVN_ERR(dav_svn__send_xml(baton->uc->bb, baton->uc->output,
482 "</S:open-%s>" DEBUG_CR,
483 DIR_OR_FILE(is_dir)));
484 return SVN_NO_ERROR;
488 /* Send the opening tag of the update-report if it hasn't been sent
489 already. */
490 static svn_error_t *
491 maybe_start_update_report(update_ctx_t *uc)
493 if ((! uc->resource_walk) && (! uc->started_update))
495 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
496 DAV_XML_HEADER DEBUG_CR
497 "<S:update-report xmlns:S=\""
498 SVN_XML_NAMESPACE "\" "
499 "xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "
500 "xmlns:D=\"DAV:\" %s>" DEBUG_CR,
501 uc->send_all ? "send-all=\"true\"" : ""));
503 uc->started_update = TRUE;
506 return SVN_NO_ERROR;
510 static svn_error_t *
511 upd_set_target_revision(void *edit_baton,
512 svn_revnum_t target_revision,
513 apr_pool_t *pool)
515 update_ctx_t *uc = edit_baton;
517 SVN_ERR(maybe_start_update_report(uc));
519 if (! uc->resource_walk)
520 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
521 "<S:target-revision rev=\"%ld"
522 "\"/>" DEBUG_CR, target_revision));
524 return SVN_NO_ERROR;
528 static svn_error_t *
529 upd_open_root(void *edit_baton,
530 svn_revnum_t base_revision,
531 apr_pool_t *pool,
532 void **root_baton)
534 update_ctx_t *uc = edit_baton;
535 item_baton_t *b = apr_pcalloc(pool, sizeof(*b));
537 /* note that we create a subpool; the root_baton is passed to the
538 close_directory callback, where we will destroy the pool. */
540 b->uc = uc;
541 b->pool = pool;
542 b->path = uc->anchor;
543 b->path2 = uc->dst_path;
544 b->path3 = "";
546 *root_baton = b;
548 SVN_ERR(maybe_start_update_report(uc));
550 if (uc->resource_walk)
552 const char *qpath = apr_xml_quote_string(pool, b->path3, 1);
553 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
554 "<S:resource path=\"%s\">" DEBUG_CR, qpath));
556 else
558 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
559 "<S:open-directory rev=\"%ld\">"
560 DEBUG_CR, base_revision));
563 /* Only transmit the root directory's Version Resource URL if
564 there's no target. */
565 if (! *uc->target)
566 SVN_ERR(send_vsn_url(b, pool));
568 if (uc->resource_walk)
569 SVN_ERR(dav_svn__send_xml(uc->bb, uc->output,
570 "</S:resource>" DEBUG_CR));
572 return SVN_NO_ERROR;
576 static svn_error_t *
577 upd_delete_entry(const char *path,
578 svn_revnum_t revision,
579 void *parent_baton,
580 apr_pool_t *pool)
582 item_baton_t *parent = parent_baton;
583 const char *qname = apr_xml_quote_string(pool,
584 svn_path_basename(path, pool), 1);
585 return dav_svn__send_xml(parent->uc->bb, parent->uc->output,
586 "<S:delete-entry name=\"%s\"/>" DEBUG_CR, qname);
590 static svn_error_t *
591 upd_add_directory(const char *path,
592 void *parent_baton,
593 const char *copyfrom_path,
594 svn_revnum_t copyfrom_revision,
595 apr_pool_t *pool,
596 void **child_baton)
598 return add_helper(TRUE /* is_dir */,
599 path, parent_baton, copyfrom_path, copyfrom_revision, pool,
600 child_baton);
604 static svn_error_t *
605 upd_open_directory(const char *path,
606 void *parent_baton,
607 svn_revnum_t base_revision,
608 apr_pool_t *pool,
609 void **child_baton)
611 return open_helper(TRUE /* is_dir */,
612 path, parent_baton, base_revision, pool, child_baton);
616 static svn_error_t *
617 upd_change_xxx_prop(void *baton,
618 const char *name,
619 const svn_string_t *value,
620 apr_pool_t *pool)
622 item_baton_t *b = baton;
623 const char *qname;
625 /* Resource walks say nothing about props. */
626 if (b->uc->resource_walk)
627 return SVN_NO_ERROR;
629 /* Else this not a resource walk, so either send props or cache them
630 to send later, depending on whether this is a modern report
631 response or not. */
633 qname = apr_xml_quote_string(b->pool, name, 1);
635 /* apr_xml_quote_string doesn't realloc if there is nothing to
636 quote, so dup the name, but only if necessary. */
637 if (qname == name)
638 qname = apr_pstrdup(b->pool, name);
641 if (b->uc->send_all)
643 if (value)
645 const char *qval;
647 if (svn_xml_is_xml_safe(value->data, value->len))
649 svn_stringbuf_t *tmp = NULL;
650 svn_xml_escape_cdata_string(&tmp, value, pool);
651 qval = tmp->data;
652 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output,
653 "<S:set-prop name=\"%s\">", qname));
655 else
657 qval = svn_base64_encode_string(value, pool)->data;
658 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output,
659 "<S:set-prop name=\"%s\" "
660 "encoding=\"base64\">" DEBUG_CR,
661 qname));
664 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output, "%s", qval));
665 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output,
666 "</S:set-prop>" DEBUG_CR));
668 else /* value is null, so this is a prop removal */
670 SVN_ERR(dav_svn__send_xml(b->uc->bb, b->uc->output,
671 "<S:remove-prop name=\"%s\"/>" DEBUG_CR,
672 qname));
675 else /* don't do inline response, just cache prop names for close_helper */
677 /* For now, store certain entry props, because we'll need to send
678 them later as standard DAV ("D:") props. ### this should go
679 away and we should just tunnel those props on through for the
680 client to deal with. */
681 #define NSLEN (sizeof(SVN_PROP_ENTRY_PREFIX) - 1)
682 if (! strncmp(name, SVN_PROP_ENTRY_PREFIX, NSLEN))
684 if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
686 b->committed_rev = value ?
687 apr_pstrdup(b->pool, value->data) : NULL;
689 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
691 b->committed_date = value ?
692 apr_pstrdup(b->pool, value->data) : NULL;
694 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
696 b->last_author = value ?
697 apr_pstrdup(b->pool, value->data) : NULL;
700 return SVN_NO_ERROR;
702 #undef NSLEN
704 if (value)
706 if (! b->changed_props)
707 b->changed_props = apr_array_make(b->pool, 1, sizeof(name));
709 APR_ARRAY_PUSH(b->changed_props, const char *) = qname;
711 else
713 if (! b->removed_props)
714 b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
716 APR_ARRAY_PUSH(b->removed_props, const char *) = qname;
720 return SVN_NO_ERROR;
724 static svn_error_t *
725 upd_close_directory(void *dir_baton, apr_pool_t *pool)
727 return close_helper(TRUE /* is_dir */, dir_baton);
731 static svn_error_t *
732 upd_add_file(const char *path,
733 void *parent_baton,
734 const char *copyfrom_path,
735 svn_revnum_t copyfrom_revision,
736 apr_pool_t *pool,
737 void **file_baton)
739 return add_helper(FALSE /* is_dir */,
740 path, parent_baton, copyfrom_path, copyfrom_revision, pool,
741 file_baton);
745 static svn_error_t *
746 upd_open_file(const char *path,
747 void *parent_baton,
748 svn_revnum_t base_revision,
749 apr_pool_t *pool,
750 void **file_baton)
752 return open_helper(FALSE /* is_dir */,
753 path, parent_baton, base_revision, pool, file_baton);
757 /* We have our own window handler and baton as a simple wrapper around
758 the real handler (which converts vdelta windows to base64-encoded
759 svndiff data). The wrapper is responsible for sending the opening
760 and closing XML tags around the svndiff data. */
761 struct window_handler_baton
763 svn_boolean_t seen_first_window; /* False until first window seen. */
764 update_ctx_t *uc;
766 /* The _real_ window handler and baton. */
767 svn_txdelta_window_handler_t handler;
768 void *handler_baton;
772 /* This implements 'svn_txdelta_window_handler_t'. */
773 static svn_error_t *
774 window_handler(svn_txdelta_window_t *window, void *baton)
776 struct window_handler_baton *wb = baton;
778 if (! wb->seen_first_window)
780 wb->seen_first_window = TRUE;
781 SVN_ERR(dav_svn__send_xml(wb->uc->bb, wb->uc->output, "<S:txdelta>"));
784 SVN_ERR(wb->handler(window, wb->handler_baton));
786 if (window == NULL)
787 SVN_ERR(dav_svn__send_xml(wb->uc->bb, wb->uc->output, "</S:txdelta>"));
789 return SVN_NO_ERROR;
793 /* This implements 'svn_txdelta_window_handler_t'.
794 During a resource walk, the driver sends an empty window as a
795 boolean indicating that a change happened to this file, but we
796 don't want to send anything over the wire as a result. */
797 static svn_error_t *
798 dummy_window_handler(svn_txdelta_window_t *window, void *baton)
800 return SVN_NO_ERROR;
804 static svn_error_t *
805 upd_apply_textdelta(void *file_baton,
806 const char *base_checksum,
807 apr_pool_t *pool,
808 svn_txdelta_window_handler_t *handler,
809 void **handler_baton)
811 item_baton_t *file = file_baton;
812 struct window_handler_baton *wb;
813 svn_stream_t *base64_stream;
815 /* Store the base checksum and the fact the file's text changed. */
816 file->base_checksum = apr_pstrdup(file->pool, base_checksum);
817 file->text_changed = TRUE;
819 /* If this is a resource walk, or if we're not in "send-all" mode,
820 we don't actually want to transmit text-deltas. */
821 if (file->uc->resource_walk || (! file->uc->send_all))
823 *handler = dummy_window_handler;
824 *handler_baton = NULL;
825 return SVN_NO_ERROR;
828 wb = apr_palloc(file->pool, sizeof(*wb));
829 wb->seen_first_window = FALSE;
830 wb->uc = file->uc;
831 base64_stream = dav_svn__make_base64_output_stream(wb->uc->bb,
832 wb->uc->output,
833 file->pool);
835 svn_txdelta_to_svndiff2(&(wb->handler), &(wb->handler_baton),
836 base64_stream, file->uc->svndiff_version,
837 file->pool);
839 *handler = window_handler;
840 *handler_baton = wb;
842 return SVN_NO_ERROR;
846 static svn_error_t *
847 upd_close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool)
849 item_baton_t *file = file_baton;
851 file->text_checksum = text_checksum ?
852 apr_pstrdup(file->pool, text_checksum) : NULL;
854 /* If we are not in "send all" mode, and this file is not a new
855 addition or didn't otherwise have changed text, tell the client
856 to fetch it. */
857 if ((! file->uc->send_all) && (! file->added) && file->text_changed)
859 const char *elt;
860 elt = apr_psprintf(pool, "<S:fetch-file%s%s%s/>" DEBUG_CR,
861 file->base_checksum ? " base-checksum=\"" : "",
862 file->base_checksum ? file->base_checksum : "",
863 file->base_checksum ? "\"" : "");
864 SVN_ERR(dav_svn__send_xml(file->uc->bb, file->uc->output, "%s", elt));
867 return close_helper(FALSE /* is_dir */, file);
871 static svn_error_t *
872 upd_close_edit(void *edit_baton, apr_pool_t *pool)
874 update_ctx_t *uc = edit_baton;
876 /* Our driver will unconditionally close the update report... So if
877 the report hasn't even been started yet, start it now. */
878 return maybe_start_update_report(uc);
882 /* Return a specific error associated with the contents of TAGNAME
883 being malformed. Use pool for allocations. */
884 static dav_error *
885 malformed_element_error(const char *tagname, apr_pool_t *pool)
887 const char *errstr = apr_pstrcat(pool, "The request's '", tagname,
888 "' element is malformed; there "
889 "is a problem with the client.", NULL);
890 return dav_svn__new_error_tag(pool, HTTP_BAD_REQUEST, 0, errstr,
891 SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
895 dav_error *
896 dav_svn__update_report(const dav_resource *resource,
897 const apr_xml_doc *doc,
898 ap_filter_t *output)
900 svn_delta_editor_t *editor;
901 apr_xml_elem *child;
902 void *rbaton = NULL;
903 update_ctx_t uc = { 0 };
904 svn_revnum_t revnum = SVN_INVALID_REVNUM;
905 svn_revnum_t from_revnum = SVN_INVALID_REVNUM;
906 int ns;
907 int entry_counter = 0;
908 svn_boolean_t entry_is_empty = FALSE;
909 svn_error_t *serr;
910 dav_error *derr = NULL;
911 apr_status_t apr_err;
912 const char *src_path = NULL;
913 const char *dst_path = NULL;
914 const dav_svn_repos *repos = resource->info->repos;
915 const char *target = "";
916 svn_boolean_t text_deltas = TRUE;
917 svn_depth_t requested_depth = svn_depth_unknown;
918 svn_boolean_t saw_depth = FALSE;
919 svn_boolean_t saw_recursive = FALSE;
920 svn_boolean_t resource_walk = FALSE;
921 svn_boolean_t ignore_ancestry = FALSE;
922 svn_boolean_t send_copyfrom_args = FALSE;
923 dav_svn__authz_read_baton arb;
924 apr_pool_t *subpool = svn_pool_create(resource->pool);
926 /* Construct the authz read check baton. */
927 arb.r = resource->info->r;
928 arb.repos = repos;
930 if (resource->info->restype != DAV_SVN_RESTYPE_VCC)
932 return dav_svn__new_error_tag(resource->pool, HTTP_CONFLICT, 0,
933 "This report can only be run against "
934 "a VCC.",
935 SVN_DAV_ERROR_NAMESPACE,
936 SVN_DAV_ERROR_TAG);
939 ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
940 if (ns == -1)
942 return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
943 "The request does not contain the 'svn:' "
944 "namespace, so it is not going to have an "
945 "svn:target-revision element. That element "
946 "is required.",
947 SVN_DAV_ERROR_NAMESPACE,
948 SVN_DAV_ERROR_TAG);
951 /* Look to see if client wants a report with props and textdeltas
952 inline, rather than placeholder tags that tell the client to do
953 further fetches. Modern clients prefer inline. */
955 apr_xml_attr *this_attr;
957 for (this_attr = doc->root->attr; this_attr; this_attr = this_attr->next)
959 if ((strcmp(this_attr->name, "send-all") == 0)
960 && (strcmp(this_attr->value, "true") == 0))
962 uc.send_all = TRUE;
963 break;
968 for (child = doc->root->first_child; child != NULL; child = child->next)
970 /* Note that child->name might not match any of the cases below.
971 Thus, the check for non-empty cdata in each of these cases
972 cannot be moved to the top of the loop, because then it would
973 wrongly catch other elements that do allow empty cdata. */
974 const char *cdata;
976 if (child->ns == ns && strcmp(child->name, "target-revision") == 0)
978 cdata = dav_xml_get_cdata(child, resource->pool, 1);
979 if (! *cdata)
980 return malformed_element_error(child->name, resource->pool);
981 revnum = SVN_STR_TO_REV(cdata);
983 if (child->ns == ns && strcmp(child->name, "src-path") == 0)
985 dav_svn__uri_info this_info;
986 cdata = dav_xml_get_cdata(child, resource->pool, 0);
987 if (! *cdata)
988 return malformed_element_error(child->name, resource->pool);
989 if ((derr = dav_svn__test_canonical(cdata, resource->pool)))
990 return derr;
991 if ((serr = dav_svn__simple_parse_uri(&this_info, resource,
992 cdata, resource->pool)))
993 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
994 "Could not parse 'src-path' URL.",
995 resource->pool);
996 src_path = this_info.repos_path;
998 if (child->ns == ns && strcmp(child->name, "dst-path") == 0)
1000 dav_svn__uri_info this_info;
1001 cdata = dav_xml_get_cdata(child, resource->pool, 0);
1002 if (! *cdata)
1003 return malformed_element_error(child->name, resource->pool);
1004 if ((derr = dav_svn__test_canonical(cdata, resource->pool)))
1005 return derr;
1006 if ((serr = dav_svn__simple_parse_uri(&this_info, resource,
1007 cdata, resource->pool)))
1008 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1009 "Could not parse 'dst-path' URL.",
1010 resource->pool);
1011 dst_path = this_info.repos_path;
1013 if (child->ns == ns && strcmp(child->name, "update-target") == 0)
1015 cdata = dav_xml_get_cdata(child, resource->pool, 0);
1016 if ((derr = dav_svn__test_canonical(cdata, resource->pool)))
1017 return derr;
1018 target = cdata;
1020 if (child->ns == ns && strcmp(child->name, "depth") == 0)
1022 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1023 if (! *cdata)
1024 return malformed_element_error(child->name, resource->pool);
1025 requested_depth = svn_depth_from_word(cdata);
1026 saw_depth = TRUE;
1028 if ((child->ns == ns && strcmp(child->name, "recursive") == 0)
1029 && (! saw_depth))
1031 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1032 if (! *cdata)
1033 return malformed_element_error(child->name, resource->pool);
1034 if (strcmp(cdata, "no") == 0)
1035 requested_depth = svn_depth_files;
1036 else
1037 requested_depth = svn_depth_infinity;
1038 /* Note that even modern, depth-aware clients still transmit
1039 "no" for "recursive" (along with "files" for "depth") in
1040 the svn_depth_files case, and transmit "no" in the
1041 svn_depth_empty case. This is because they don't know if
1042 they're talking to a depth-aware server or not, and they
1043 don't need to know -- all they have to do is transmit
1044 both, and the server will DTRT either way (although in
1045 the svn_depth_empty case, the client will still have some
1046 work to do in ignoring the files that come down).
1048 When both "depth" and "recursive" are sent, we don't
1049 bother to check if they're mutually consistent, we just
1050 let depth dominate. */
1051 saw_recursive = TRUE;
1053 if (child->ns == ns && strcmp(child->name, "ignore-ancestry") == 0)
1055 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1056 if (! *cdata)
1057 return malformed_element_error(child->name, resource->pool);
1058 if (strcmp(cdata, "no") != 0)
1059 ignore_ancestry = TRUE;
1061 if (child->ns == ns && strcmp(child->name, "send-copyfrom-args") == 0)
1063 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1064 if (! *cdata)
1065 return malformed_element_error(child->name, resource->pool);
1066 if (strcmp(cdata, "no") != 0)
1067 send_copyfrom_args = TRUE;
1069 if (child->ns == ns && strcmp(child->name, "resource-walk") == 0)
1071 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1072 if (! *cdata)
1073 return malformed_element_error(child->name, resource->pool);
1074 if (strcmp(cdata, "no") != 0)
1075 resource_walk = TRUE;
1077 if (child->ns == ns && strcmp(child->name, "text-deltas") == 0)
1079 cdata = dav_xml_get_cdata(child, resource->pool, 1);
1080 if (! *cdata)
1081 return malformed_element_error(child->name, resource->pool);
1082 if (strcmp(cdata, "no") == 0)
1083 text_deltas = FALSE;
1087 if (!saw_depth && !saw_recursive && (requested_depth == svn_depth_unknown))
1088 requested_depth = svn_depth_infinity;
1090 /* If the client never sent a <src-path> element, it's old and
1091 sending a style of report that we no longer allow. */
1092 if (! src_path)
1094 return dav_svn__new_error_tag
1095 (resource->pool, HTTP_BAD_REQUEST, 0,
1096 "The request did not contain the '<src-path>' element.\n"
1097 "This may indicate that your client is too old.",
1098 SVN_DAV_ERROR_NAMESPACE,
1099 SVN_DAV_ERROR_TAG);
1102 /* If a revision for this operation was not dictated to us, this
1103 means "update to whatever the current HEAD is now". */
1104 if (revnum == SVN_INVALID_REVNUM)
1106 if ((serr = svn_fs_youngest_rev(&revnum, repos->fs, resource->pool)))
1107 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1108 "Could not determine the youngest "
1109 "revision for the update process.",
1110 resource->pool);
1113 uc.svndiff_version = resource->info->svndiff_version;
1114 uc.resource = resource;
1115 uc.output = output;
1116 uc.anchor = src_path;
1117 uc.target = target;
1118 uc.bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
1119 uc.pathmap = NULL;
1120 if (dst_path) /* we're doing a 'switch' */
1122 if (*target)
1124 /* if the src is split into anchor/target, so must the
1125 telescoping dst_path be. */
1126 uc.dst_path = svn_path_dirname(dst_path, resource->pool);
1128 /* Also, the svn_repos_dir_delta2() is going to preserve our
1129 target's name, so we need a pathmap entry for that. */
1130 if (! uc.pathmap)
1131 uc.pathmap = apr_hash_make(resource->pool);
1132 add_to_path_map(uc.pathmap,
1133 svn_path_join(src_path, target, resource->pool),
1134 dst_path);
1136 else
1138 uc.dst_path = dst_path;
1141 else /* we're doing an update, so src and dst are the same. */
1142 uc.dst_path = uc.anchor;
1144 /* Get the root of the revision we want to update to. This will be used
1145 to generated stable id values. */
1146 if ((serr = svn_fs_revision_root(&uc.rev_root, repos->fs,
1147 revnum, resource->pool)))
1149 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1150 "The revision root could not be created.",
1151 resource->pool);
1154 /* If the client did *not* request 'send-all' mode, then we will be
1155 sending only a "skelta" of the difference, which will not need to
1156 contain actual text deltas. */
1157 if (! uc.send_all)
1158 text_deltas = FALSE;
1160 /* When we call svn_repos_finish_report, it will ultimately run
1161 dir_delta() between REPOS_PATH/TARGET and TARGET_PATH. In the
1162 case of an update or status, these paths should be identical. In
1163 the case of a switch, they should be different. */
1164 editor = svn_delta_default_editor(resource->pool);
1165 editor->set_target_revision = upd_set_target_revision;
1166 editor->open_root = upd_open_root;
1167 editor->delete_entry = upd_delete_entry;
1168 editor->add_directory = upd_add_directory;
1169 editor->open_directory = upd_open_directory;
1170 editor->change_dir_prop = upd_change_xxx_prop;
1171 editor->close_directory = upd_close_directory;
1172 editor->absent_directory = upd_absent_directory;
1173 editor->add_file = upd_add_file;
1174 editor->open_file = upd_open_file;
1175 editor->apply_textdelta = upd_apply_textdelta;
1176 editor->change_file_prop = upd_change_xxx_prop;
1177 editor->close_file = upd_close_file;
1178 editor->absent_file = upd_absent_file;
1179 editor->close_edit = upd_close_edit;
1180 if ((serr = svn_repos_begin_report2(&rbaton, revnum,
1181 repos->repos,
1182 src_path, target,
1183 dst_path,
1184 text_deltas,
1185 requested_depth,
1186 ignore_ancestry,
1187 send_copyfrom_args,
1188 editor, &uc,
1189 dav_svn__authz_read_func(&arb),
1190 &arb,
1191 resource->pool)))
1193 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1194 "The state report gatherer could not be "
1195 "created.",
1196 resource->pool);
1199 /* scan the XML doc for state information */
1200 for (child = doc->root->first_child; child != NULL; child = child->next)
1201 if (child->ns == ns)
1203 /* Clear our subpool. */
1204 svn_pool_clear(subpool);
1206 if (strcmp(child->name, "entry") == 0)
1208 const char *path;
1209 svn_revnum_t rev = SVN_INVALID_REVNUM;
1210 svn_boolean_t saw_rev = FALSE;
1211 const char *linkpath = NULL;
1212 const char *locktoken = NULL;
1213 svn_boolean_t start_empty = FALSE;
1214 apr_xml_attr *this_attr = child->attr;
1215 /* Default to infinity, for old clients that don't send depth. */
1216 svn_depth_t depth = svn_depth_infinity;
1218 entry_counter++;
1220 while (this_attr)
1222 if (strcmp(this_attr->name, "rev") == 0)
1224 rev = SVN_STR_TO_REV(this_attr->value);
1225 saw_rev = TRUE;
1227 else if (strcmp(this_attr->name, "depth") == 0)
1228 depth = svn_depth_from_word(this_attr->value);
1229 else if (strcmp(this_attr->name, "linkpath") == 0)
1230 linkpath = this_attr->value;
1231 else if (strcmp(this_attr->name, "start-empty") == 0)
1232 start_empty = entry_is_empty = TRUE;
1233 else if (strcmp(this_attr->name, "lock-token") == 0)
1234 locktoken = this_attr->value;
1236 this_attr = this_attr->next;
1239 /* we require the `rev' attribute for this to make sense */
1240 if (! saw_rev)
1242 serr = svn_error_create(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1243 NULL, "Missing XML attribute: rev");
1244 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1245 "A failure occurred while "
1246 "recording one of the items of "
1247 "working copy state.",
1248 resource->pool);
1249 goto cleanup;
1252 /* get cdata, stripping whitespace */
1253 path = dav_xml_get_cdata(child, subpool, 0);
1255 /* determine the "from rev" for revision range ops */
1256 if (strcmp(path, "") == 0)
1257 from_revnum = rev;
1259 if (! linkpath)
1260 serr = svn_repos_set_path3(rbaton, path, rev, depth,
1261 start_empty, locktoken, subpool);
1262 else
1263 serr = svn_repos_link_path3(rbaton, path, linkpath, rev, depth,
1264 start_empty, locktoken, subpool);
1265 if (serr != NULL)
1267 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1268 "A failure occurred while "
1269 "recording one of the items of "
1270 "working copy state.",
1271 resource->pool);
1272 goto cleanup;
1275 /* now, add this path to our path map, but only if we are
1276 doing a regular update (not a `switch') */
1277 if (linkpath && (! dst_path))
1279 const char *this_path;
1280 if (! uc.pathmap)
1281 uc.pathmap = apr_hash_make(resource->pool);
1282 this_path = svn_path_join_many(apr_hash_pool_get(uc.pathmap),
1283 src_path, target, path, NULL);
1284 add_to_path_map(uc.pathmap, this_path, linkpath);
1287 else if (strcmp(child->name, "missing") == 0)
1289 /* get cdata, stripping whitespace */
1290 const char *path = dav_xml_get_cdata(child, subpool, 0);
1291 serr = svn_repos_delete_path(rbaton, path, subpool);
1292 if (serr != NULL)
1294 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1295 "A failure occurred while "
1296 "recording one of the (missing) "
1297 "items of working copy state.",
1298 resource->pool);
1299 goto cleanup;
1304 /* Try to deduce what sort of client command is being run, then
1305 make this guess available to apache's logging subsystem. */
1307 const char *action, *spath, *log_depth;
1309 if (requested_depth == svn_depth_unknown)
1310 log_depth = "";
1311 else
1312 log_depth = apr_pstrcat(resource->pool, " depth=",
1313 svn_depth_to_word(requested_depth), NULL);
1315 if (target)
1316 spath = svn_path_join(src_path, target, resource->pool);
1317 else
1318 spath = src_path;
1320 /* If a second path was passed to svn_repos_dir_delta2(), then it
1321 must have been switch, diff, or merge. */
1322 if (dst_path)
1324 /* diff/merge don't ask for inline text-deltas. */
1325 if (!uc.send_all && strcmp(spath, dst_path) == 0)
1326 action = apr_psprintf(resource->pool,
1327 "diff %s r%ld:%ld%s%s",
1328 svn_path_uri_encode(spath, resource->pool),
1329 from_revnum,
1330 revnum, log_depth,
1331 ignore_ancestry ? " ignore-ancestry" : "");
1332 else
1333 action = apr_psprintf(resource->pool,
1334 "%s %s@%ld %s@%ld%s%s",
1335 (uc.send_all ? "switch" : "diff"),
1336 svn_path_uri_encode(spath, resource->pool),
1337 from_revnum,
1338 svn_path_uri_encode(dst_path, resource->pool),
1339 revnum, log_depth,
1340 /* ignore-ancestry only applies to merge, and
1341 we use uc.send_all to know if this is a
1342 diff/merge or not. */
1343 (!uc.send_all && ignore_ancestry
1344 ? " ignore-ancestry" : ""));
1347 /* Otherwise, it must be checkout, export, update, or status -u. */
1348 else
1350 /* svn_client_checkout() creates a single root directory, then
1351 reports it (and it alone) to the server as being empty. */
1352 if (entry_counter == 1 && entry_is_empty)
1353 action = apr_psprintf(resource->pool,
1354 "checkout-or-export %s r%ld%s",
1355 svn_path_uri_encode(spath, resource->pool),
1356 revnum,
1357 log_depth);
1358 else
1360 if (text_deltas)
1361 action = apr_psprintf(resource->pool,
1362 "update %s r%ld%s%s",
1363 svn_path_uri_encode(spath,
1364 resource->pool),
1365 revnum,
1366 log_depth,
1367 (send_copyfrom_args
1368 ? " send-copyfrom-args" : ""));
1369 else
1370 action = apr_psprintf(resource->pool,
1371 "status %s r%ld%s",
1372 svn_path_uri_encode(spath,
1373 resource->pool),
1374 revnum,
1375 log_depth);
1379 dav_svn__operational_log(resource->info, action);
1382 /* this will complete the report, and then drive our editor to generate
1383 the response to the client. */
1384 serr = svn_repos_finish_report(rbaton, resource->pool);
1385 if (serr)
1387 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1388 "A failure occurred while "
1389 "driving the update report editor",
1390 resource->pool);
1391 goto cleanup;
1394 /* We're finished with the report baton. Note that so we don't try
1395 to abort this report later. */
1396 rbaton = NULL;
1398 /* ### Temporarily disable resource_walks for single-file switch
1399 operations. It isn't strictly necessary. */
1400 if (dst_path && resource_walk)
1402 /* Sanity check: if we switched a file, we can't do a resource
1403 walk. dir_delta would choke if we pass a filepath as the
1404 'target'. Also, there's no need to do the walk, since the
1405 new vsn-rsc-url was already in the earlier part of the report. */
1406 svn_node_kind_t dst_kind;
1407 if ((serr = svn_fs_check_path(&dst_kind, uc.rev_root, dst_path,
1408 resource->pool)))
1410 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1411 "Failed checking destination path kind",
1412 resource->pool);
1413 goto cleanup;
1415 if (dst_kind != svn_node_dir)
1416 resource_walk = FALSE;
1419 /* The potential "resource walk" part of the update-report. */
1420 if (dst_path && resource_walk) /* this was a 'switch' operation */
1422 /* send a second embedded <S:resource-walk> tree that contains
1423 the new vsn-rsc-urls for the switched dir. this walk
1424 contains essentially nothing but <add> tags. */
1425 svn_fs_root_t *zero_root;
1426 serr = svn_fs_revision_root(&zero_root, repos->fs, 0,
1427 resource->pool);
1428 if (serr)
1430 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1431 "Failed to find the revision root",
1432 resource->pool);
1433 goto cleanup;
1436 serr = dav_svn__send_xml(uc.bb, uc.output,
1437 "<S:resource-walk>" DEBUG_CR);
1438 if (serr)
1440 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1441 "Unable to begin resource walk",
1442 resource->pool);
1443 goto cleanup;
1446 uc.resource_walk = TRUE;
1448 /* Compare subtree DST_PATH within a pristine revision to
1449 revision 0. This should result in nothing but 'add' calls
1450 to the editor. */
1451 serr = svn_repos_dir_delta2(zero_root, "", target,
1452 uc.rev_root, dst_path,
1453 /* re-use the editor */
1454 editor, &uc,
1455 dav_svn__authz_read_func(&arb),
1456 &arb, FALSE /* text-deltas */,
1457 requested_depth,
1458 TRUE /* entryprops */,
1459 FALSE /* ignore-ancestry */,
1460 resource->pool);
1462 if (serr)
1464 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1465 "Resource walk failed.",
1466 resource->pool);
1467 goto cleanup;
1470 serr = dav_svn__send_xml(uc.bb, uc.output,
1471 "</S:resource-walk>" DEBUG_CR);
1472 if (serr)
1474 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1475 "Unable to complete resource walk.",
1476 resource->pool);
1477 goto cleanup;
1481 /* Close the report body, unless some error prevented it from being
1482 started in the first place. */
1483 if (uc.started_update)
1485 if ((serr = dav_svn__send_xml(uc.bb, uc.output,
1486 "</S:update-report>" DEBUG_CR)))
1488 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1489 "Unable to complete update report.",
1490 resource->pool);
1491 goto cleanup;
1495 cleanup:
1497 /* Flush the contents of the brigade (returning an error only if we
1498 don't already have one). */
1499 if ((! derr) && ((apr_err = ap_fflush(output, uc.bb))))
1500 derr = dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
1501 HTTP_INTERNAL_SERVER_ERROR,
1502 "Error flushing brigade.",
1503 resource->pool);
1505 /* if an error was produced EITHER by the dir_delta drive or the
1506 resource-walker... */
1507 if (derr)
1509 if (rbaton)
1510 svn_error_clear(svn_repos_abort_report(rbaton, resource->pool));
1511 return derr;
1514 /* Destroy our subpool. */
1515 svn_pool_destroy(subpool);
1517 return NULL;