Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / mod_dav_svn / version.c
blobcbc4d1e33b55f33239188dbdf56770aab858ac7f
1 /*
2 * version.c: mod_dav_svn versioning provider functions for Subversion
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_tables.h>
20 #include <apr_uuid.h>
22 #include <httpd.h>
23 #include <http_log.h>
24 #include <mod_dav.h>
26 #include "svn_fs.h"
27 #include "svn_xml.h"
28 #include "svn_repos.h"
29 #include "svn_dav.h"
30 #include "svn_time.h"
31 #include "svn_pools.h"
32 #include "svn_props.h"
33 #include "svn_dav.h"
34 #include "svn_base64.h"
35 #include "private/svn_dav_protocol.h"
37 #include "dav_svn.h"
40 svn_error_t *
41 dav_svn__attach_auto_revprops(svn_fs_txn_t *txn,
42 const char *fs_path,
43 apr_pool_t *pool)
45 const char *logmsg;
46 svn_string_t *logval;
47 svn_error_t *serr;
49 logmsg = apr_psprintf(pool,
50 "Autoversioning commit: a non-deltaV client made "
51 "a change to\n%s", fs_path);
53 logval = svn_string_create(logmsg, pool);
54 if ((serr = svn_repos_fs_change_txn_prop(txn, SVN_PROP_REVISION_LOG, logval,
55 pool)))
56 return serr;
58 /* Notate that this revision was created by autoversioning. (Tools
59 like post-commit email scripts might not care to send an email
60 for every autoversioning change.) */
61 if ((serr = svn_repos_fs_change_txn_prop(txn,
62 SVN_PROP_REVISION_AUTOVERSIONED,
63 svn_string_create("*", pool),
64 pool)))
65 return serr;
67 return SVN_NO_ERROR;
71 /* Helper: attach an auto-generated svn:log property to a txn within
72 an auto-checked-out working resource. */
73 static dav_error *
74 set_auto_revprops(dav_resource *resource)
76 svn_error_t *serr;
78 if (! (resource->type == DAV_RESOURCE_TYPE_WORKING
79 && resource->info->auto_checked_out))
80 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
81 "Set_auto_revprops called on invalid resource.");
83 if ((serr = dav_svn__attach_auto_revprops(resource->info->root.txn,
84 resource->info->repos_path,
85 resource->pool)))
86 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
87 "Error setting a revision property "
88 " on auto-checked-out resource's txn. ",
89 resource->pool);
90 return NULL;
94 static dav_error *
95 open_txn(svn_fs_txn_t **ptxn,
96 svn_fs_t *fs,
97 const char *txn_name,
98 apr_pool_t *pool)
100 svn_error_t *serr;
102 serr = svn_fs_open_txn(ptxn, fs, txn_name, pool);
103 if (serr != NULL)
105 if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
107 /* ### correct HTTP error? */
108 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
109 "The transaction specified by the "
110 "activity does not exist",
111 pool);
114 /* ### correct HTTP error? */
115 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
116 "There was a problem opening the "
117 "transaction specified by this "
118 "activity.",
119 pool);
122 return NULL;
126 static void
127 get_vsn_options(apr_pool_t *p, apr_text_header *phdr)
129 /* Note: we append pieces with care for Web Folders's 63-char limit
130 on the DAV: header */
132 apr_text_append(p, phdr,
133 "version-control,checkout,working-resource");
134 apr_text_append(p, phdr,
135 "merge,baseline,activity,version-controlled-collection");
136 /* Send SVN_RA_CAPABILITY_* capabilities. */
137 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_DEPTH);
138 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
139 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY);
140 /* Mergeinfo is a special case: here we merely say that the server
141 * knows how to handle mergeinfo -- whether the repository does too
142 * is a separate matter.
144 * Think of it as offering the client an early out: if the server
145 * can't do merge-tracking, there's no point finding out of the
146 * repository can. But if the server can, it may be worth expending
147 * an extra round trip to find out if the repository can too (the
148 * extra round trip being necessary because, sadly, we don't have
149 * access to the repository yet here, so we can only announce the
150 * server capability and remain agnostic about the repository).
152 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_MERGEINFO);
154 /* ### fork-control? */
158 static dav_error *
159 get_option(const dav_resource *resource,
160 const apr_xml_elem *elem,
161 apr_text_header *option)
163 /* ### DAV:version-history-collection-set */
165 if (elem->ns == APR_XML_NS_DAV_ID)
167 if (strcmp(elem->name, "activity-collection-set") == 0)
169 apr_text_append(resource->pool, option,
170 "<D:activity-collection-set>");
171 apr_text_append(resource->pool, option,
172 dav_svn__build_uri(resource->info->repos,
173 DAV_SVN__BUILD_URI_ACT_COLLECTION,
174 SVN_INVALID_REVNUM, NULL,
175 1 /* add_href */,
176 resource->pool));
177 apr_text_append(resource->pool, option,
178 "</D:activity-collection-set>");
182 return NULL;
186 static int
187 versionable(const dav_resource *resource)
189 return 0;
193 static dav_auto_version
194 auto_versionable(const dav_resource *resource)
196 /* The svn client attempts to proppatch a baseline when changing
197 unversioned revision props. Thus we allow baselines to be
198 "auto-checked-out" by mod_dav. See issue #916. */
199 if (resource->type == DAV_RESOURCE_TYPE_VERSION
200 && resource->baselined)
201 return DAV_AUTO_VERSION_ALWAYS;
203 /* No other autoversioning is allowed unless the SVNAutoversioning
204 directive is used. */
205 if (resource->info->repos->autoversioning)
207 /* This allows a straight-out PUT on a public file or collection
208 VCR. mod_dav's auto-versioning subsystem will check to see if
209 it's possible to auto-checkout a regular resource. */
210 if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
211 return DAV_AUTO_VERSION_ALWAYS;
213 /* mod_dav's auto-versioning subsystem will also check to see if
214 it's possible to auto-checkin a working resource that was
215 auto-checked-out. We *only* allow auto-versioning on a working
216 resource if it was auto-checked-out. */
217 if (resource->type == DAV_RESOURCE_TYPE_WORKING
218 && resource->info->auto_checked_out)
219 return DAV_AUTO_VERSION_ALWAYS;
222 /* Default: whatever it is, assume it's not auto-versionable */
223 return DAV_AUTO_VERSION_NEVER;
227 static dav_error *
228 vsn_control(dav_resource *resource, const char *target)
230 /* All mod_dav_svn resources are versioned objects; so it doesn't
231 make sense to call vsn_control on a resource that exists . */
232 if (resource->exists)
233 return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0,
234 "vsn_control called on already-versioned resource.");
236 /* Only allow a NULL target, which means an create an 'empty' VCR. */
237 if (target != NULL)
238 return dav_svn__new_error_tag(resource->pool, HTTP_NOT_IMPLEMENTED,
239 SVN_ERR_UNSUPPORTED_FEATURE,
240 "vsn_control called with non-null target.",
241 SVN_DAV_ERROR_NAMESPACE,
242 SVN_DAV_ERROR_TAG);
244 /* This is kind of silly. The docstring for this callback says it's
245 supposed to "put a resource under version control". But in
246 Subversion, all REGULAR resources (bc's or public URIs) are
247 already under version control. So we don't need to do a thing to
248 the resource, just return. */
249 return NULL;
253 dav_error *
254 dav_svn__checkout(dav_resource *resource,
255 int auto_checkout,
256 int is_unreserved,
257 int is_fork_ok,
258 int create_activity,
259 apr_array_header_t *activities,
260 dav_resource **working_resource)
262 const char *txn_name;
263 svn_error_t *serr;
264 apr_status_t apr_err;
265 dav_error *derr;
266 dav_svn__uri_info parse;
268 /* Auto-Versioning Stuff */
269 if (auto_checkout)
271 dav_resource *res; /* ignored */
272 const char *uuid_buf;
273 void *data;
274 const char *shared_activity, *shared_txn_name = NULL;
276 /* Baselines can be auto-checked-out -- grudgingly -- so we can
277 allow clients to proppatch unversioned rev props. See issue
278 #916. */
279 if ((resource->type == DAV_RESOURCE_TYPE_VERSION)
280 && resource->baselined)
281 /* ### We're violating deltaV big time here, by allowing a
282 dav_auto_checkout() on something that mod_dav assumes is a
283 VCR, not a VR. Anyway, mod_dav thinks we're checking out the
284 resource 'in place', so that no working resource is returned.
285 (It passes NULL as **working_resource.) */
286 return NULL;
288 if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
289 return dav_svn__new_error_tag(resource->pool, HTTP_METHOD_NOT_ALLOWED,
290 SVN_ERR_UNSUPPORTED_FEATURE,
291 "auto-checkout attempted on non-regular "
292 "version-controlled resource.",
293 SVN_DAV_ERROR_NAMESPACE,
294 SVN_DAV_ERROR_TAG);
296 if (resource->baselined)
297 return dav_svn__new_error_tag(resource->pool, HTTP_METHOD_NOT_ALLOWED,
298 SVN_ERR_UNSUPPORTED_FEATURE,
299 "auto-checkout attempted on baseline "
300 "collection, which is not supported.",
301 SVN_DAV_ERROR_NAMESPACE,
302 SVN_DAV_ERROR_TAG);
304 /* See if the shared activity already exists. */
305 apr_err = apr_pool_userdata_get(&data,
306 DAV_SVN__AUTOVERSIONING_ACTIVITY,
307 resource->info->r->pool);
308 if (apr_err)
309 return dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
310 HTTP_INTERNAL_SERVER_ERROR,
311 "Error fetching pool userdata.",
312 resource->pool);
313 shared_activity = data;
315 if (! shared_activity)
317 /* Build a shared activity for all auto-checked-out resources. */
318 uuid_buf = svn_uuid_generate(resource->info->r->pool);
319 shared_activity = apr_pstrdup(resource->info->r->pool, uuid_buf);
321 derr = dav_svn__create_activity(resource->info->repos,
322 &shared_txn_name,
323 resource->info->r->pool);
324 if (derr) return derr;
326 derr = dav_svn__store_activity(resource->info->repos,
327 shared_activity, shared_txn_name);
328 if (derr) return derr;
330 /* Save the shared activity in r->pool for others to use. */
331 apr_err = apr_pool_userdata_set(shared_activity,
332 DAV_SVN__AUTOVERSIONING_ACTIVITY,
333 NULL, resource->info->r->pool);
334 if (apr_err)
335 return dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
336 HTTP_INTERNAL_SERVER_ERROR,
337 "Error setting pool userdata.",
338 resource->pool);
341 if (! shared_txn_name)
343 shared_txn_name = dav_svn__get_txn(resource->info->repos,
344 shared_activity);
345 if (! shared_txn_name)
346 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
347 "Cannot look up a txn_name by activity");
350 /* Tweak the VCR in-place, making it into a WR. (Ignore the
351 NULL return value.) */
352 res = dav_svn__create_working_resource(resource,
353 shared_activity, shared_txn_name,
354 TRUE /* tweak in place */);
356 /* Remember that this resource was auto-checked-out, so that
357 auto_versionable allows us to do an auto-checkin and
358 can_be_activity will allow this resource to be an
359 activity. */
360 resource->info->auto_checked_out = TRUE;
362 /* The txn and txn_root must be open and ready to go in the
363 resource's root object. Normally prep_resource() will do
364 this automatically on a WR's root object. We're
365 converting a VCR to WR forcibly, so it's now our job to
366 make sure it happens. */
367 derr = open_txn(&resource->info->root.txn, resource->info->repos->fs,
368 resource->info->root.txn_name, resource->pool);
369 if (derr) return derr;
371 serr = svn_fs_txn_root(&resource->info->root.root,
372 resource->info->root.txn, resource->pool);
373 if (serr != NULL)
374 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
375 "Could not open a (transaction) root "
376 "in the repository",
377 resource->pool);
378 return NULL;
380 /* end of Auto-Versioning Stuff */
382 if (resource->type != DAV_RESOURCE_TYPE_VERSION)
384 return dav_svn__new_error_tag(resource->pool, HTTP_METHOD_NOT_ALLOWED,
385 SVN_ERR_UNSUPPORTED_FEATURE,
386 "CHECKOUT can only be performed on a "
387 "version resource [at this time].",
388 SVN_DAV_ERROR_NAMESPACE,
389 SVN_DAV_ERROR_TAG);
391 if (create_activity)
393 return dav_svn__new_error_tag(resource->pool, HTTP_NOT_IMPLEMENTED,
394 SVN_ERR_UNSUPPORTED_FEATURE,
395 "CHECKOUT can not create an activity at "
396 "this time. Use MKACTIVITY first.",
397 SVN_DAV_ERROR_NAMESPACE,
398 SVN_DAV_ERROR_TAG);
400 if (is_unreserved)
402 return dav_svn__new_error_tag(resource->pool, HTTP_NOT_IMPLEMENTED,
403 SVN_ERR_UNSUPPORTED_FEATURE,
404 "Unreserved checkouts are not yet "
405 "available. A version history may not be "
406 "checked out more than once, into a "
407 "specific activity.",
408 SVN_DAV_ERROR_NAMESPACE,
409 SVN_DAV_ERROR_TAG);
411 if (activities == NULL)
413 return dav_svn__new_error_tag(resource->pool, HTTP_CONFLICT,
414 SVN_ERR_INCOMPLETE_DATA,
415 "An activity must be provided for "
416 "checkout.",
417 SVN_DAV_ERROR_NAMESPACE,
418 SVN_DAV_ERROR_TAG);
420 /* assert: nelts > 0. the below check effectively means > 1. */
421 if (activities->nelts != 1)
423 return dav_svn__new_error_tag(resource->pool, HTTP_CONFLICT,
424 SVN_ERR_INCORRECT_PARAMS,
425 "Only one activity may be specified within "
426 "the CHECKOUT.",
427 SVN_DAV_ERROR_NAMESPACE,
428 SVN_DAV_ERROR_TAG);
431 serr = dav_svn__simple_parse_uri(&parse, resource,
432 APR_ARRAY_IDX(activities, 0, const char *),
433 resource->pool);
434 if (serr != NULL)
436 /* ### is BAD_REQUEST proper? */
437 return dav_svn__convert_err(serr, HTTP_CONFLICT,
438 "The activity href could not be parsed "
439 "properly.",
440 resource->pool);
442 if (parse.activity_id == NULL)
444 return dav_svn__new_error_tag(resource->pool, HTTP_CONFLICT,
445 SVN_ERR_INCORRECT_PARAMS,
446 "The provided href is not an activity URI.",
447 SVN_DAV_ERROR_NAMESPACE,
448 SVN_DAV_ERROR_TAG);
451 if ((txn_name = dav_svn__get_txn(resource->info->repos,
452 parse.activity_id)) == NULL)
454 return dav_svn__new_error_tag(resource->pool, HTTP_CONFLICT,
455 SVN_ERR_APMOD_ACTIVITY_NOT_FOUND,
456 "The specified activity does not exist.",
457 SVN_DAV_ERROR_NAMESPACE,
458 SVN_DAV_ERROR_TAG);
461 /* verify the specified version resource is the "latest", thus allowing
462 changes to be made. */
463 if (resource->baselined || resource->info->root.rev == SVN_INVALID_REVNUM)
465 /* a Baseline, or a standard Version Resource which was accessed
466 via a Label against a VCR within a Baseline Collection. */
467 /* ### at the moment, this branch is only reached for baselines */
469 svn_revnum_t youngest;
471 /* make sure the baseline being checked out is the latest */
472 serr = svn_fs_youngest_rev(&youngest, resource->info->repos->fs,
473 resource->pool);
474 if (serr != NULL)
476 /* ### correct HTTP error? */
477 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
478 "Could not determine the youngest "
479 "revision for verification against "
480 "the baseline being checked out.",
481 resource->pool);
484 if (resource->info->root.rev != youngest)
486 return dav_svn__new_error_tag(resource->pool, HTTP_CONFLICT,
487 SVN_ERR_APMOD_BAD_BASELINE,
488 "The specified baseline is not the "
489 "latest baseline, so it may not be "
490 "checked out.",
491 SVN_DAV_ERROR_NAMESPACE,
492 SVN_DAV_ERROR_TAG);
495 /* ### hmm. what if the transaction root's revision is different
496 ### from this baseline? i.e. somebody created a new revision while
497 ### we are processing this commit.
499 ### first question: what does the client *do* with a working
500 ### baseline? knowing that, and how it maps to our backend, then
501 ### we can figure out what to do here. */
503 else
505 /* standard Version Resource */
507 svn_fs_txn_t *txn;
508 svn_fs_root_t *txn_root;
509 svn_revnum_t txn_created_rev;
510 dav_error *err;
512 /* open the specified transaction so that we can verify this version
513 resource corresponds to the current/latest in the transaction. */
514 if ((err = open_txn(&txn, resource->info->repos->fs, txn_name,
515 resource->pool)) != NULL)
516 return err;
518 serr = svn_fs_txn_root(&txn_root, txn, resource->pool);
519 if (serr != NULL)
521 /* ### correct HTTP error? */
522 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
523 "Could not open the transaction tree.",
524 resource->pool);
527 /* assert: repos_path != NULL (for this type of resource) */
530 /* Out-of-dateness check: compare the created-rev of the item
531 in the txn against the created-rev of the version resource
532 being changed. */
533 serr = svn_fs_node_created_rev(&txn_created_rev,
534 txn_root, resource->info->repos_path,
535 resource->pool);
536 if (serr != NULL)
538 /* ### correct HTTP error? */
539 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
540 "Could not get created-rev of "
541 "transaction node.",
542 resource->pool);
545 /* If txn_created_rev is invalid, that means it's already
546 mutable in the txn... which means it has already passed this
547 out-of-dateness check. (Usually, this happens when looking
548 at a parent directory of an already checked-out
549 resource.)
551 Now, we come down to it. If the created revision of the node
552 in the transaction is different from the revision parsed from
553 the version resource URL, we're in a bit of a quandry, and
554 one of a few things could be true.
556 - The client is trying to modify an old (out-of-date)
557 revision of the resource. This is, of course,
558 unacceptable!
560 - The client is trying to modify a *newer* revision. If the
561 version resource is *newer* than the transaction root, then
562 the client started a commit, a new revision was created
563 within the repository, the client fetched the new resource
564 from that new revision, changed it (or merged in a prior
565 change), and then attempted to incorporate that into the
566 commit that was initially started. We could copy that new
567 node into our transaction and then modify it, but why
568 bother? We can stop the commit, and everything will be
569 fine again if the user simply restarts it (because we'll
570 use that new revision as the transaction root, thus
571 incorporating the new resource, which they will then
572 modify).
574 - The path/revision that client is wishing to edit and the
575 path/revision in the current transaction are actually the
576 same node, and thus this created-rev comparison didn't
577 really solidify anything after all. :-)
580 if (SVN_IS_VALID_REVNUM( txn_created_rev ))
582 if (resource->info->root.rev < txn_created_rev)
584 /* The item being modified is older than the one in the
585 transaction. The client is out of date. */
586 return dav_svn__new_error_tag
587 (resource->pool, HTTP_CONFLICT, SVN_ERR_FS_CONFLICT,
588 "resource out of date; try updating",
589 SVN_DAV_ERROR_NAMESPACE,
590 SVN_DAV_ERROR_TAG);
592 else if (resource->info->root.rev > txn_created_rev)
594 /* The item being modified is being accessed via a newer
595 revision than the one in the transaction. We'll
596 check to see if they are still the same node, and if
597 not, return an error. */
598 const svn_fs_id_t *url_noderev_id, *txn_noderev_id;
600 if ((serr = svn_fs_node_id(&txn_noderev_id, txn_root,
601 resource->info->repos_path,
602 resource->pool)))
604 err = dav_svn__new_error_tag
605 (resource->pool, HTTP_CONFLICT, serr->apr_err,
606 "Unable to fetch the node revision id of the version "
607 "resource within the transaction.",
608 SVN_DAV_ERROR_NAMESPACE,
609 SVN_DAV_ERROR_TAG);
610 svn_error_clear(serr);
611 return err;
613 if ((serr = svn_fs_node_id(&url_noderev_id,
614 resource->info->root.root,
615 resource->info->repos_path,
616 resource->pool)))
618 err = dav_svn__new_error_tag
619 (resource->pool, HTTP_CONFLICT, serr->apr_err,
620 "Unable to fetch the node revision id of the version "
621 "resource within the revision.",
622 SVN_DAV_ERROR_NAMESPACE,
623 SVN_DAV_ERROR_TAG);
624 svn_error_clear(serr);
625 return err;
627 if (svn_fs_compare_ids(url_noderev_id, txn_noderev_id) != 0)
629 return dav_svn__new_error_tag
630 (resource->pool, HTTP_CONFLICT, SVN_ERR_FS_CONFLICT,
631 "version resource newer than txn (restart the commit)",
632 SVN_DAV_ERROR_NAMESPACE,
633 SVN_DAV_ERROR_TAG);
638 *working_resource = dav_svn__create_working_resource(resource,
639 parse.activity_id,
640 txn_name,
641 FALSE);
642 return NULL;
646 static dav_error *
647 uncheckout(dav_resource *resource)
649 if (resource->type != DAV_RESOURCE_TYPE_WORKING)
650 return dav_svn__new_error_tag(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
651 SVN_ERR_UNSUPPORTED_FEATURE,
652 "UNCHECKOUT called on non-working resource.",
653 SVN_DAV_ERROR_NAMESPACE,
654 SVN_DAV_ERROR_TAG);
656 /* Try to abort the txn if it exists; but don't try too hard. :-) */
657 if (resource->info->root.txn)
658 svn_error_clear(svn_fs_abort_txn(resource->info->root.txn,
659 resource->pool));
661 /* Attempt to destroy the shared activity. */
662 if (resource->info->root.activity_id)
664 dav_svn__delete_activity(resource->info->repos,
665 resource->info->root.activity_id);
666 apr_pool_userdata_set(NULL, DAV_SVN__AUTOVERSIONING_ACTIVITY,
667 NULL, resource->info->r->pool);
670 resource->info->root.txn_name = NULL;
671 resource->info->root.txn = NULL;
673 /* We're no longer checked out. */
674 resource->info->auto_checked_out = FALSE;
676 /* Convert the working resource back into a regular one, in-place. */
677 return dav_svn__working_to_regular_resource(resource);
681 /* Closure object for cleanup_deltify. */
682 struct cleanup_deltify_baton
684 /* The repository in which to deltify. We use a path instead of an
685 object, because it's difficult to obtain a repos or fs object
686 with the right lifetime guarantees. */
687 const char *repos_path;
689 /* The revision number against which to deltify. */
690 svn_revnum_t revision;
692 /* The pool to use for all temporary allocation while working. This
693 may or may not be the same as the pool on which the cleanup is
694 registered, but obviously it must have a lifetime at least as
695 long as that pool. */
696 apr_pool_t *pool;
700 /* APR pool cleanup function to deltify against a just-committed
701 revision. DATA is a 'struct cleanup_deltify_baton *'.
703 If any errors occur, log them in the httpd server error log, but
704 return APR_SUCCESS no matter what, as this is a pool cleanup
705 function and deltification is not a matter of correctness
706 anyway. */
707 static apr_status_t
708 cleanup_deltify(void *data)
710 struct cleanup_deltify_baton *cdb = data;
711 svn_repos_t *repos;
712 svn_error_t *err;
714 /* It's okay to allocate in the pool that's being cleaned up, and
715 it's also okay to register new cleanups against that pool. But
716 if you create subpools of it, you must make sure to destroy them
717 at the end of the cleanup. So we do all our work in this
718 subpool, then destroy it before exiting. */
719 apr_pool_t *subpool = svn_pool_create(cdb->pool);
721 err = svn_repos_open(&repos, cdb->repos_path, subpool);
722 if (err)
724 ap_log_perror(APLOG_MARK, APLOG_ERR, err->apr_err, cdb->pool,
725 "cleanup_deltify: error opening repository '%s'",
726 cdb->repos_path);
727 svn_error_clear(err);
728 goto cleanup;
731 err = svn_fs_deltify_revision(svn_repos_fs(repos),
732 cdb->revision, subpool);
733 if (err)
735 ap_log_perror(APLOG_MARK, APLOG_ERR, err->apr_err, cdb->pool,
736 "cleanup_deltify: error deltifying against revision %ld"
737 " in repository '%s'",
738 cdb->revision, cdb->repos_path);
739 svn_error_clear(err);
742 cleanup:
743 svn_pool_destroy(subpool);
745 return APR_SUCCESS;
749 /* Register the cleanup_deltify function on POOL, which should be the
750 connection pool for the request. This way the time needed for
751 deltification won't delay the response to the client.
753 REPOS is the repository in which deltify, and REVISION is the
754 revision against which to deltify. POOL is both the pool on which
755 to register the cleanup function and the pool that will be used for
756 temporary allocations while deltifying. */
757 static void
758 register_deltification_cleanup(svn_repos_t *repos,
759 svn_revnum_t revision,
760 apr_pool_t *pool)
762 struct cleanup_deltify_baton *cdb = apr_palloc(pool, sizeof(*cdb));
764 cdb->repos_path = svn_repos_path(repos, pool);
765 cdb->revision = revision;
766 cdb->pool = pool;
768 apr_pool_cleanup_register(pool, cdb, cleanup_deltify, apr_pool_cleanup_null);
772 dav_error *
773 dav_svn__checkin(dav_resource *resource,
774 int keep_checked_out,
775 dav_resource **version_resource)
777 svn_error_t *serr;
778 dav_error *err;
779 apr_status_t apr_err;
780 const char *uri;
781 const char *shared_activity;
782 void *data;
784 /* ### mod_dav has a flawed architecture, in the sense that it first
785 tries to auto-checkin the modified resource, then attempts to
786 auto-checkin the parent resource (if the parent resource was
787 auto-checked-out). Instead, the provider should be in charge:
788 mod_dav should provide a *set* of resources that need
789 auto-checkin, and the provider can decide how to do it. (One
790 txn? Many txns? Etc.) */
792 if (resource->type != DAV_RESOURCE_TYPE_WORKING)
793 return dav_svn__new_error_tag(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
794 SVN_ERR_UNSUPPORTED_FEATURE,
795 "CHECKIN called on non-working resource.",
796 SVN_DAV_ERROR_NAMESPACE,
797 SVN_DAV_ERROR_TAG);
799 /* If the global autoversioning activity still exists, that means
800 nobody's committed it yet. */
801 apr_err = apr_pool_userdata_get(&data,
802 DAV_SVN__AUTOVERSIONING_ACTIVITY,
803 resource->info->r->pool);
804 if (apr_err)
805 return dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
806 HTTP_INTERNAL_SERVER_ERROR,
807 "Error fetching pool userdata.",
808 resource->pool);
809 shared_activity = data;
811 /* Try to commit the txn if it exists. */
812 if (shared_activity
813 && (strcmp(shared_activity, resource->info->root.activity_id) == 0))
815 const char *shared_txn_name;
816 const char *conflict_msg;
817 svn_revnum_t new_rev;
819 shared_txn_name = dav_svn__get_txn(resource->info->repos,
820 shared_activity);
821 if (! shared_txn_name)
822 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
823 "Cannot look up a txn_name by activity");
825 /* Sanity checks */
826 if (resource->info->root.txn_name
827 && (strcmp(shared_txn_name, resource->info->root.txn_name) != 0))
828 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
829 "Internal txn_name doesn't match"
830 " autoversioning transaction.");
832 if (! resource->info->root.txn)
833 /* should already be open by checkout */
834 return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
835 "Autoversioning txn isn't open "
836 "when it should be.");
838 err = set_auto_revprops(resource);
839 if (err)
840 return err;
842 serr = svn_repos_fs_commit_txn(&conflict_msg,
843 resource->info->repos->repos,
844 &new_rev,
845 resource->info->root.txn,
846 resource->pool);
848 if (serr != NULL)
850 const char *msg;
851 svn_error_clear(svn_fs_abort_txn(resource->info->root.txn,
852 resource->pool));
854 if (serr->apr_err == SVN_ERR_FS_CONFLICT)
856 msg = apr_psprintf(resource->pool,
857 "A conflict occurred during the CHECKIN "
858 "processing. The problem occurred with "
859 "the \"%s\" resource.",
860 conflict_msg);
862 else
863 msg = "An error occurred while committing the transaction.";
865 /* Attempt to destroy the shared activity. */
866 dav_svn__delete_activity(resource->info->repos, shared_activity);
867 apr_pool_userdata_set(NULL, DAV_SVN__AUTOVERSIONING_ACTIVITY,
868 NULL, resource->info->r->pool);
870 return dav_svn__convert_err(serr, HTTP_CONFLICT, msg,
871 resource->pool);
874 /* Attempt to destroy the shared activity. */
875 dav_svn__delete_activity(resource->info->repos, shared_activity);
876 apr_pool_userdata_set(NULL, DAV_SVN__AUTOVERSIONING_ACTIVITY,
877 NULL, resource->info->r->pool);
879 /* Commit was successful, so schedule deltification. */
880 register_deltification_cleanup(resource->info->repos->repos,
881 new_rev,
882 resource->info->r->connection->pool);
884 /* If caller wants it, return the new VR that was created by
885 the checkin. */
886 if (version_resource)
888 uri = dav_svn__build_uri(resource->info->repos,
889 DAV_SVN__BUILD_URI_VERSION,
890 new_rev, resource->info->repos_path,
891 0, resource->pool);
893 err = dav_svn__create_version_resource(version_resource, uri,
894 resource->pool);
895 if (err)
896 return err;
898 } /* end of commit stuff */
900 /* The shared activity was either nonexistent to begin with, or it's
901 been committed and is only now nonexistent. The resource needs
902 to forget about it. */
903 resource->info->root.txn_name = NULL;
904 resource->info->root.txn = NULL;
906 /* Convert the working resource back into an regular one. */
907 if (! keep_checked_out)
909 resource->info->auto_checked_out = FALSE;
910 return dav_svn__working_to_regular_resource(resource);
913 return NULL;
917 static dav_error *
918 avail_reports(const dav_resource *resource, const dav_report_elem **reports)
920 /* ### further restrict to the public space? */
921 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
922 *reports = NULL;
923 return NULL;
926 *reports = dav_svn__reports_list;
927 return NULL;
931 static int
932 report_label_header_allowed(const apr_xml_doc *doc)
934 return 0;
938 static dav_error *
939 deliver_report(request_rec *r,
940 const dav_resource *resource,
941 const apr_xml_doc *doc,
942 ap_filter_t *output)
944 int ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
946 if (doc->root->ns == ns)
948 /* ### note that these report names should have symbols... */
950 if (strcmp(doc->root->name, "update-report") == 0)
952 return dav_svn__update_report(resource, doc, output);
954 else if (strcmp(doc->root->name, "log-report") == 0)
956 return dav_svn__log_report(resource, doc, output);
958 else if (strcmp(doc->root->name, "dated-rev-report") == 0)
960 return dav_svn__dated_rev_report(resource, doc, output);
962 else if (strcmp(doc->root->name, "get-locations") == 0)
964 return dav_svn__get_locations_report(resource, doc, output);
966 else if (strcmp(doc->root->name, "get-location-segments") == 0)
968 return dav_svn__get_location_segments_report(resource, doc, output);
970 else if (strcmp(doc->root->name, "file-revs-report") == 0)
972 return dav_svn__file_revs_report(resource, doc, output);
974 else if (strcmp(doc->root->name, "get-locks-report") == 0)
976 return dav_svn__get_locks_report(resource, doc, output);
978 else if (strcmp(doc->root->name, "replay-report") == 0)
980 return dav_svn__replay_report(resource, doc, output);
982 else if (strcmp(doc->root->name, SVN_DAV__MERGEINFO_REPORT) == 0)
984 return dav_svn__get_mergeinfo_report(resource, doc, output);
987 /* NOTE: if you add a report, don't forget to add it to the
988 * dav_svn__reports_list[] array.
992 /* ### what is a good error for an unknown report? */
993 return dav_svn__new_error_tag(resource->pool, HTTP_NOT_IMPLEMENTED,
994 SVN_ERR_UNSUPPORTED_FEATURE,
995 "The requested report is unknown.",
996 SVN_DAV_ERROR_NAMESPACE,
997 SVN_DAV_ERROR_TAG);
1001 static int
1002 can_be_activity(const dav_resource *resource)
1004 /* If our resource is marked as auto_checked_out'd, then we allow this to
1005 * be an activity URL. Otherwise, it must be a real activity URL that
1006 * doesn't already exist.
1008 return (resource->info->auto_checked_out == TRUE ||
1009 (resource->type == DAV_RESOURCE_TYPE_ACTIVITY &&
1010 !resource->exists));
1014 static dav_error *
1015 make_activity(dav_resource *resource)
1017 const char *activity_id = resource->info->root.activity_id;
1018 const char *txn_name;
1019 dav_error *err;
1021 /* sanity check: make sure the resource is a valid activity, in
1022 case an older mod_dav doesn't do the check for us. */
1023 if (! can_be_activity(resource))
1024 return dav_svn__new_error_tag(resource->pool, HTTP_FORBIDDEN,
1025 SVN_ERR_APMOD_MALFORMED_URI,
1026 "Activities cannot be created at that "
1027 "location; query the "
1028 "DAV:activity-collection-set property.",
1029 SVN_DAV_ERROR_NAMESPACE,
1030 SVN_DAV_ERROR_TAG);
1032 err = dav_svn__create_activity(resource->info->repos, &txn_name,
1033 resource->pool);
1034 if (err != NULL)
1035 return err;
1037 err = dav_svn__store_activity(resource->info->repos, activity_id, txn_name);
1038 if (err != NULL)
1039 return err;
1041 /* everything is happy. update the resource */
1042 resource->info->root.txn_name = txn_name;
1043 resource->exists = 1;
1044 return NULL;
1048 dav_error *
1049 dav_svn__build_lock_hash(apr_hash_t **locks,
1050 request_rec *r,
1051 const char *path_prefix,
1052 apr_pool_t *pool)
1054 apr_status_t apr_err;
1055 dav_error *derr;
1056 void *data = NULL;
1057 apr_xml_doc *doc = NULL;
1058 apr_xml_elem *child, *lockchild;
1059 int ns;
1060 apr_hash_t *hash = apr_hash_make(pool);
1062 /* Grab the request body out of r->pool, as it contains all of the
1063 lock tokens. It should have been stashed already by our custom
1064 input filter. */
1065 apr_err = apr_pool_userdata_get(&data, "svn-request-body", r->pool);
1066 if (apr_err)
1067 return dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
1068 HTTP_INTERNAL_SERVER_ERROR,
1069 "Error fetching pool userdata.",
1070 pool);
1071 doc = data;
1072 if (! doc)
1074 *locks = hash;
1075 return SVN_NO_ERROR;
1078 /* Sanity check. */
1079 ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
1080 if (ns == -1)
1082 /* If there's no svn: namespace in the body, then there are
1083 definitely no lock-tokens to harvest. This is likely a
1084 request from an old client. */
1085 *locks = hash;
1086 return SVN_NO_ERROR;
1089 if ((doc->root->ns == ns)
1090 && (strcmp(doc->root->name, "lock-token-list") == 0))
1092 child = doc->root;
1094 else
1096 /* Search doc's children until we find the <lock-token-list>. */
1097 for (child = doc->root->first_child; child != NULL; child = child->next)
1099 /* if this element isn't one of ours, then skip it */
1100 if (child->ns != ns)
1101 continue;
1103 if (strcmp(child->name, "lock-token-list") == 0)
1104 break;
1108 /* Did we find what we were looking for? */
1109 if (! child)
1111 *locks = hash;
1112 return SVN_NO_ERROR;
1115 /* Then look for N different <lock> structures within. */
1116 for (lockchild = child->first_child; lockchild != NULL;
1117 lockchild = lockchild->next)
1119 const char *lockpath = NULL, *locktoken = NULL;
1120 apr_xml_elem *lfchild;
1122 if (strcmp(lockchild->name, "lock") != 0)
1123 continue;
1125 for (lfchild = lockchild->first_child; lfchild != NULL;
1126 lfchild = lfchild->next)
1128 if (strcmp(lfchild->name, "lock-path") == 0)
1130 const char *cdata = dav_xml_get_cdata(lfchild, pool, 0);
1131 if ((derr = dav_svn__test_canonical(cdata, pool)))
1132 return derr;
1134 /* Create an absolute fs-path */
1135 lockpath = svn_path_join(path_prefix, cdata, pool);
1136 if (lockpath && locktoken)
1138 apr_hash_set(hash, lockpath, APR_HASH_KEY_STRING, locktoken);
1139 lockpath = NULL;
1140 locktoken = NULL;
1143 else if (strcmp(lfchild->name, "lock-token") == 0)
1145 locktoken = dav_xml_get_cdata(lfchild, pool, 1);
1146 if (lockpath && *locktoken)
1148 apr_hash_set(hash, lockpath, APR_HASH_KEY_STRING, locktoken);
1149 lockpath = NULL;
1150 locktoken = NULL;
1156 *locks = hash;
1157 return SVN_NO_ERROR;
1161 dav_error *
1162 dav_svn__push_locks(dav_resource *resource,
1163 apr_hash_t *locks,
1164 apr_pool_t *pool)
1166 svn_fs_access_t *fsaccess;
1167 apr_hash_index_t *hi;
1168 svn_error_t *serr;
1170 serr = svn_fs_get_access(&fsaccess, resource->info->repos->fs);
1171 if (serr)
1173 /* If an authenticated user name was attached to the request,
1174 then dav_svn_get_resource() should have already noticed and
1175 created an fs_access_t in the filesystem. */
1176 return dav_svn__sanitize_error(serr, "Lock token(s) in request, but "
1177 "missing an user name", HTTP_BAD_REQUEST,
1178 resource->info->r);
1181 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1183 const char *token;
1184 void *val;
1185 apr_hash_this(hi, NULL, NULL, &val);
1186 token = val;
1188 serr = svn_fs_access_add_lock_token(fsaccess, token);
1189 if (serr)
1190 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1191 "Error pushing token into filesystem.",
1192 pool);
1195 return NULL;
1199 /* Helper for merge(). Free every lock in LOCKS. The locks
1200 live in REPOS. Log any errors for REQUEST. Use POOL for temporary
1201 work.*/
1202 static svn_error_t *
1203 release_locks(apr_hash_t *locks,
1204 svn_repos_t *repos,
1205 request_rec *r,
1206 apr_pool_t *pool)
1208 apr_hash_index_t *hi;
1209 const void *key;
1210 void *val;
1211 apr_pool_t *subpool = svn_pool_create(pool);
1212 svn_error_t *err;
1214 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1216 svn_pool_clear(subpool);
1217 apr_hash_this(hi, &key, NULL, &val);
1219 /* The lock may be stolen or broken sometime between
1220 svn_fs_commit_txn() and this post-commit cleanup. So ignore
1221 any errors from this command; just free as many locks as we can. */
1222 err = svn_repos_fs_unlock(repos, key, val, FALSE, subpool);
1224 if (err) /* If we got an error, just log it and move along. */
1225 ap_log_rerror(APLOG_MARK, APLOG_ERR, err->apr_err, r,
1226 "%s", err->message);
1228 svn_error_clear(err);
1231 svn_pool_destroy(subpool);
1233 return SVN_NO_ERROR;
1237 static dav_error *
1238 merge(dav_resource *target,
1239 dav_resource *source,
1240 int no_auto_merge,
1241 int no_checkout,
1242 apr_xml_elem *prop_elem,
1243 ap_filter_t *output)
1245 apr_pool_t *pool;
1246 dav_error *err;
1247 svn_fs_txn_t *txn;
1248 const char *conflict;
1249 svn_error_t *serr;
1250 char *post_commit_err = NULL;
1251 svn_revnum_t new_rev;
1252 apr_hash_t *locks;
1253 svn_boolean_t disable_merge_response = FALSE;
1255 /* We'll use the target's pool for our operation. We happen to know that
1256 it matches the request pool, which (should) have the proper lifetime. */
1257 pool = target->pool;
1259 /* ### what to verify on the target? */
1261 /* ### anything else for the source? */
1262 if (source->type != DAV_RESOURCE_TYPE_ACTIVITY)
1264 return dav_svn__new_error_tag(pool, HTTP_METHOD_NOT_ALLOWED,
1265 SVN_ERR_INCORRECT_PARAMS,
1266 "MERGE can only be performed using an "
1267 "activity as the source [at this time].",
1268 SVN_DAV_ERROR_NAMESPACE,
1269 SVN_DAV_ERROR_TAG);
1272 /* Before attempting the final commit, we need to push any incoming
1273 lock-tokens into the filesystem's access_t. Normally they come
1274 in via 'If:' header, and dav_svn_get_resource() automatically
1275 notices them and does this work for us. In the case of MERGE,
1276 however, svn clients are sending them in the request body. */
1278 err = dav_svn__build_lock_hash(&locks, target->info->r,
1279 target->info->repos_path,
1280 pool);
1281 if (err != NULL)
1282 return err;
1284 if (apr_hash_count(locks))
1286 err = dav_svn__push_locks(source, locks, pool);
1287 if (err != NULL)
1288 return err;
1291 /* We will ignore no_auto_merge and no_checkout. We can't do those, but the
1292 client has no way to assert that we *should* do them. This should be fine
1293 because, presumably, the client has no way to do the various checkouts
1294 and things that would necessitate an auto-merge or checkout during the
1295 MERGE processing. */
1297 /* open the transaction that we're going to commit. */
1298 if ((err = open_txn(&txn, source->info->repos->fs,
1299 source->info->root.txn_name, pool)) != NULL)
1300 return err;
1302 /* all righty... commit the bugger. */
1303 serr = svn_repos_fs_commit_txn(&conflict, source->info->repos->repos,
1304 &new_rev, txn, pool);
1306 /* If the error was just a post-commit hook failure, we ignore it.
1307 Otherwise, we deal with it.
1308 ### TODO: Figure out if the MERGE response can grow a means by
1309 which to marshal back both the success of the commit (and its
1310 commit info) and the failure of the post-commit hook. */
1311 if (serr && (serr->apr_err != SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
1313 const char *msg;
1314 svn_error_clear(svn_fs_abort_txn(txn, pool));
1316 if (serr->apr_err == SVN_ERR_FS_CONFLICT)
1318 /* ### we need to convert the conflict path into a URI */
1319 msg = apr_psprintf(pool,
1320 "A conflict occurred during the MERGE "
1321 "processing. The problem occurred with the "
1322 "\"%s\" resource.",
1323 conflict);
1325 else
1326 msg = "An error occurred while committing the transaction.";
1328 return dav_svn__convert_err(serr, HTTP_CONFLICT, msg, pool);
1330 else if (serr)
1332 if (serr->child && serr->child->message)
1333 post_commit_err = apr_pstrdup(pool, serr->child->message);
1334 svn_error_clear(serr);
1337 /* Commit was successful, so schedule deltification. */
1338 register_deltification_cleanup(source->info->repos->repos, new_rev,
1339 source->info->r->connection->pool);
1341 /* We've detected a 'high level' svn action to log. */
1342 dav_svn__operational_log(target->info,
1343 apr_psprintf(target->info->r->pool,
1344 "commit r%ld",
1345 new_rev));
1347 /* Since the commit was successful, the txn ID is no longer valid.
1348 Store an empty txn ID in the activity database so that when the
1349 client deletes the activity, we don't try to open and abort the
1350 transaction. */
1351 err = dav_svn__store_activity(source->info->repos,
1352 source->info->root.activity_id, "");
1353 if (err != NULL)
1354 return err;
1356 /* Check the dav_resource->info area for information about the
1357 special X-SVN-Options: header that may have come in the http
1358 request. */
1359 if (source->info->svn_client_options != NULL)
1361 /* The client might want us to release all locks sent in the
1362 MERGE request. */
1363 if ((NULL != (ap_strstr_c(source->info->svn_client_options,
1364 SVN_DAV_OPTION_RELEASE_LOCKS)))
1365 && apr_hash_count(locks))
1367 serr = release_locks(locks, source->info->repos->repos,
1368 source->info->r, pool);
1369 if (serr != NULL)
1370 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1371 "Error releasing locks", pool);
1374 /* The client might want us to disable the merge response altogether. */
1375 if (NULL != (ap_strstr_c(source->info->svn_client_options,
1376 SVN_DAV_OPTION_NO_MERGE_RESPONSE)))
1377 disable_merge_response = TRUE;
1380 /* process the response for the new revision. */
1381 return dav_svn__merge_response(output, source->info->repos, new_rev,
1382 post_commit_err, prop_elem,
1383 disable_merge_response, pool);
1387 const dav_hooks_vsn dav_svn__hooks_vsn = {
1388 get_vsn_options,
1389 get_option,
1390 versionable,
1391 auto_versionable,
1392 vsn_control,
1393 dav_svn__checkout,
1394 uncheckout,
1395 dav_svn__checkin,
1396 avail_reports,
1397 report_label_header_allowed,
1398 deliver_report,
1399 NULL, /* update */
1400 NULL, /* add_label */
1401 NULL, /* remove_label */
1402 NULL, /* can_be_workspace */
1403 NULL, /* make_workspace */
1404 can_be_activity,
1405 make_activity,
1406 merge,