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>
28 #include "svn_repos.h"
31 #include "svn_pools.h"
32 #include "svn_props.h"
34 #include "svn_base64.h"
35 #include "private/svn_dav_protocol.h"
41 dav_svn__attach_auto_revprops(svn_fs_txn_t
*txn
,
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
,
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
),
71 /* Helper: attach an auto-generated svn:log property to a txn within
72 an auto-checked-out working resource. */
74 set_auto_revprops(dav_resource
*resource
)
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
,
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. ",
95 open_txn(svn_fs_txn_t
**ptxn
,
102 serr
= svn_fs_open_txn(ptxn
, fs
, txn_name
, pool
);
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",
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 "
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? */
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
,
177 apr_text_append(resource
->pool
, option
,
178 "</D:activity-collection-set>");
187 versionable(const dav_resource
*resource
)
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
;
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. */
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
,
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. */
254 dav_svn__checkout(dav_resource
*resource
,
259 apr_array_header_t
*activities
,
260 dav_resource
**working_resource
)
262 const char *txn_name
;
264 apr_status_t apr_err
;
266 dav_svn__uri_info parse
;
268 /* Auto-Versioning Stuff */
271 dav_resource
*res
; /* ignored */
272 const char *uuid_buf
;
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
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.) */
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
,
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
,
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
);
309 return dav_svn__convert_err(svn_error_create(apr_err
, 0, NULL
),
310 HTTP_INTERNAL_SERVER_ERROR
,
311 "Error fetching pool userdata.",
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
,
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
);
335 return dav_svn__convert_err(svn_error_create(apr_err
, 0, NULL
),
336 HTTP_INTERNAL_SERVER_ERROR
,
337 "Error setting pool userdata.",
341 if (! shared_txn_name
)
343 shared_txn_name
= dav_svn__get_txn(resource
->info
->repos
,
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
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
);
374 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
375 "Could not open a (transaction) root "
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
,
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
,
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
,
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 "
417 SVN_DAV_ERROR_NAMESPACE
,
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 "
427 SVN_DAV_ERROR_NAMESPACE
,
431 serr
= dav_svn__simple_parse_uri(&parse
, resource
,
432 APR_ARRAY_IDX(activities
, 0, const char *),
436 /* ### is BAD_REQUEST proper? */
437 return dav_svn__convert_err(serr
, HTTP_CONFLICT
,
438 "The activity href could not be parsed "
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
,
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
,
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
,
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.",
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 "
491 SVN_DAV_ERROR_NAMESPACE
,
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. */
505 /* standard Version Resource */
508 svn_fs_root_t
*txn_root
;
509 svn_revnum_t txn_created_rev
;
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
)
518 serr
= svn_fs_txn_root(&txn_root
, txn
, resource
->pool
);
521 /* ### correct HTTP error? */
522 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
523 "Could not open the transaction tree.",
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
533 serr
= svn_fs_node_created_rev(&txn_created_rev
,
534 txn_root
, resource
->info
->repos_path
,
538 /* ### correct HTTP error? */
539 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
540 "Could not get created-rev of "
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
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,
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
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
,
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
,
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
,
610 svn_error_clear(serr
);
613 if ((serr
= svn_fs_node_id(&url_noderev_id
,
614 resource
->info
->root
.root
,
615 resource
->info
->repos_path
,
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
,
624 svn_error_clear(serr
);
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
,
638 *working_resource
= dav_svn__create_working_resource(resource
,
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
,
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
,
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. */
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
708 cleanup_deltify(void *data
)
710 struct cleanup_deltify_baton
*cdb
= data
;
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
);
724 ap_log_perror(APLOG_MARK
, APLOG_ERR
, err
->apr_err
, cdb
->pool
,
725 "cleanup_deltify: error opening repository '%s'",
727 svn_error_clear(err
);
731 err
= svn_fs_deltify_revision(svn_repos_fs(repos
),
732 cdb
->revision
, subpool
);
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
);
743 svn_pool_destroy(subpool
);
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. */
758 register_deltification_cleanup(svn_repos_t
*repos
,
759 svn_revnum_t revision
,
762 struct cleanup_deltify_baton
*cdb
= apr_palloc(pool
, sizeof(*cdb
));
764 cdb
->repos_path
= svn_repos_path(repos
, pool
);
765 cdb
->revision
= revision
;
768 apr_pool_cleanup_register(pool
, cdb
, cleanup_deltify
, apr_pool_cleanup_null
);
773 dav_svn__checkin(dav_resource
*resource
,
774 int keep_checked_out
,
775 dav_resource
**version_resource
)
779 apr_status_t apr_err
;
781 const char *shared_activity
;
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
,
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
);
805 return dav_svn__convert_err(svn_error_create(apr_err
, 0, NULL
),
806 HTTP_INTERNAL_SERVER_ERROR
,
807 "Error fetching pool userdata.",
809 shared_activity
= data
;
811 /* Try to commit the txn if it exists. */
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
,
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");
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
);
842 serr
= svn_repos_fs_commit_txn(&conflict_msg
,
843 resource
->info
->repos
->repos
,
845 resource
->info
->root
.txn
,
851 svn_error_clear(svn_fs_abort_txn(resource
->info
->root
.txn
,
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.",
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
,
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
,
882 resource
->info
->r
->connection
->pool
);
884 /* If caller wants it, return the new VR that was created by
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
,
893 err
= dav_svn__create_version_resource(version_resource
, uri
,
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
);
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
) {
926 *reports
= dav_svn__reports_list
;
932 report_label_header_allowed(const apr_xml_doc
*doc
)
939 deliver_report(request_rec
*r
,
940 const dav_resource
*resource
,
941 const apr_xml_doc
*doc
,
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
,
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
));
1015 make_activity(dav_resource
*resource
)
1017 const char *activity_id
= resource
->info
->root
.activity_id
;
1018 const char *txn_name
;
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
,
1032 err
= dav_svn__create_activity(resource
->info
->repos
, &txn_name
,
1037 err
= dav_svn__store_activity(resource
->info
->repos
, activity_id
, txn_name
);
1041 /* everything is happy. update the resource */
1042 resource
->info
->root
.txn_name
= txn_name
;
1043 resource
->exists
= 1;
1049 dav_svn__build_lock_hash(apr_hash_t
**locks
,
1051 const char *path_prefix
,
1054 apr_status_t apr_err
;
1057 apr_xml_doc
*doc
= NULL
;
1058 apr_xml_elem
*child
, *lockchild
;
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
1065 apr_err
= apr_pool_userdata_get(&data
, "svn-request-body", r
->pool
);
1067 return dav_svn__convert_err(svn_error_create(apr_err
, 0, NULL
),
1068 HTTP_INTERNAL_SERVER_ERROR
,
1069 "Error fetching pool userdata.",
1075 return SVN_NO_ERROR
;
1079 ns
= dav_svn__find_ns(doc
->namespaces
, SVN_XML_NAMESPACE
);
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. */
1086 return SVN_NO_ERROR
;
1089 if ((doc
->root
->ns
== ns
)
1090 && (strcmp(doc
->root
->name
, "lock-token-list") == 0))
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
)
1103 if (strcmp(child
->name
, "lock-token-list") == 0)
1108 /* Did we find what we were looking for? */
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)
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
)))
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
);
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
);
1157 return SVN_NO_ERROR
;
1162 dav_svn__push_locks(dav_resource
*resource
,
1166 svn_fs_access_t
*fsaccess
;
1167 apr_hash_index_t
*hi
;
1170 serr
= svn_fs_get_access(&fsaccess
, resource
->info
->repos
->fs
);
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
,
1181 for (hi
= apr_hash_first(pool
, locks
); hi
; hi
= apr_hash_next(hi
))
1185 apr_hash_this(hi
, NULL
, NULL
, &val
);
1188 serr
= svn_fs_access_add_lock_token(fsaccess
, token
);
1190 return dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
1191 "Error pushing token into filesystem.",
1199 /* Helper for merge(). Free every lock in LOCKS. The locks
1200 live in REPOS. Log any errors for REQUEST. Use POOL for temporary
1202 static svn_error_t
*
1203 release_locks(apr_hash_t
*locks
,
1208 apr_hash_index_t
*hi
;
1211 apr_pool_t
*subpool
= svn_pool_create(pool
);
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
;
1238 merge(dav_resource
*target
,
1239 dav_resource
*source
,
1242 apr_xml_elem
*prop_elem
,
1243 ap_filter_t
*output
)
1248 const char *conflict
;
1250 char *post_commit_err
= NULL
;
1251 svn_revnum_t new_rev
;
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
,
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
,
1284 if (apr_hash_count(locks
))
1286 err
= dav_svn__push_locks(source
, locks
, pool
);
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
)
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
))
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 "
1326 msg
= "An error occurred while committing the transaction.";
1328 return dav_svn__convert_err(serr
, HTTP_CONFLICT
, msg
, pool
);
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
,
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
1351 err
= dav_svn__store_activity(source
->info
->repos
,
1352 source
->info
->root
.activity_id
, "");
1356 /* Check the dav_resource->info area for information about the
1357 special X-SVN-Options: header that may have come in the http
1359 if (source
->info
->svn_client_options
!= NULL
)
1361 /* The client might want us to release all locks sent in the
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
);
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
= {
1397 report_label_header_allowed
,
1400 NULL
, /* add_label */
1401 NULL
, /* remove_label */
1402 NULL
, /* can_be_workspace */
1403 NULL
, /* make_workspace */