2
* version.c: mod_dav_svn versioning provider functions for Subversion
4
* ====================================================================
5
* Copyright (c) 2000-2004 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
* ====================================================================
24
#include <apr_tables.h>
29
#include "svn_repos.h"
32
#include "svn_pools.h"
33
#include "svn_props.h"
35
#include "svn_base64.h"
40
/* ### should move these report names to a public header to share with
41
### the client (and third parties). */
42
static const dav_report_elem avail_reports[] = {
43
{ SVN_XML_NAMESPACE, "update-report" },
44
{ SVN_XML_NAMESPACE, "log-report" },
45
{ SVN_XML_NAMESPACE, "dated-rev-report" },
46
{ SVN_XML_NAMESPACE, "get-locations" },
47
{ SVN_XML_NAMESPACE, "file-revs-report" },
48
{ SVN_XML_NAMESPACE, "get-locks-report" },
52
/* declare these static functions early, so we can use them anywhere. */
53
static dav_error *dav_svn_make_activity(dav_resource *resource);
56
svn_error_t *dav_svn_attach_auto_revprops(svn_fs_txn_t *txn,
64
logmsg = apr_psprintf(pool,
65
"Autoversioning commit: a non-deltaV client made "
66
"a change to\n%s", fs_path);
68
logval = svn_string_create(logmsg, pool);
69
if ((serr = svn_repos_fs_change_txn_prop(txn, SVN_PROP_REVISION_LOG, logval,
73
/* Notate that this revision was created by autoversioning. (Tools
74
like post-commit email scripts might not care to send an email
75
for every autoversioning change.) */
76
if ((serr = svn_repos_fs_change_txn_prop(txn,
77
SVN_PROP_REVISION_AUTOVERSIONED,
78
svn_string_create("*", pool),
86
/* Helper: attach an auto-generated svn:log property to a txn within
87
an auto-checked-out working resource. */
88
static dav_error *set_auto_revprops(dav_resource *resource)
92
if (! (resource->type == DAV_RESOURCE_TYPE_WORKING
93
&& resource->info->auto_checked_out))
94
return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
95
"Set_auto_revprops called on invalid resource.");
97
if ((serr = dav_svn_attach_auto_revprops(resource->info->root.txn,
98
resource->info->repos_path,
100
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
101
"Error setting a revision property "
102
" on auto-checked-out resource's txn. ",
108
static dav_error *open_txn(svn_fs_txn_t **ptxn, svn_fs_t *fs,
109
const char *txn_name, apr_pool_t *pool)
113
serr = svn_fs_open_txn(ptxn, fs, txn_name, pool);
116
if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
118
/* ### correct HTTP error? */
119
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
120
"The transaction specified by the "
121
"activity does not exist",
125
/* ### correct HTTP error? */
126
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
127
"There was a problem opening the "
128
"transaction specified by this "
136
static void dav_svn_get_vsn_options(apr_pool_t *p, apr_text_header *phdr)
138
/* Note: we append pieces with care for Web Folders's 63-char limit
139
on the DAV: header */
141
apr_text_append(p, phdr,
142
"version-control,checkout,working-resource");
143
apr_text_append(p, phdr,
144
"merge,baseline,activity,version-controlled-collection");
146
/* ### fork-control? */
149
static dav_error *dav_svn_get_option(const dav_resource *resource,
150
const apr_xml_elem *elem,
151
apr_text_header *option)
153
/* ### DAV:version-history-collection-set */
155
if (elem->ns == APR_XML_NS_DAV_ID)
157
if (strcmp(elem->name, "activity-collection-set") == 0)
159
apr_text_append(resource->pool, option,
160
"<D:activity-collection-set>");
161
apr_text_append(resource->pool, option,
162
dav_svn_build_uri(resource->info->repos,
163
DAV_SVN_BUILD_URI_ACT_COLLECTION,
164
SVN_INVALID_REVNUM, NULL,
165
1 /* add_href */, resource->pool));
166
apr_text_append(resource->pool, option,
167
"</D:activity-collection-set>");
174
static int dav_svn_versionable(const dav_resource *resource)
179
static dav_auto_version dav_svn_auto_versionable(const dav_resource *resource)
181
/* The svn client attempts to proppatch a baseline when changing
182
unversioned revision props. Thus we allow baselines to be
183
"auto-checked-out" by mod_dav. See issue #916. */
184
if (resource->type == DAV_RESOURCE_TYPE_VERSION
185
&& resource->baselined)
186
return DAV_AUTO_VERSION_ALWAYS;
188
/* No other autoversioning is allowed unless the SVNAutoversioning
189
directive is used. */
190
if (resource->info->repos->autoversioning)
192
/* This allows a straight-out PUT on a public file or collection
193
VCR. mod_dav's auto-versioning subsystem will check to see if
194
it's possible to auto-checkout a regular resource. */
195
if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
196
return DAV_AUTO_VERSION_ALWAYS;
198
/* mod_dav's auto-versioning subsystem will also check to see if
199
it's possible to auto-checkin a working resource that was
200
auto-checked-out. We *only* allow auto-versioning on a working
201
resource if it was auto-checked-out. */
202
if (resource->type == DAV_RESOURCE_TYPE_WORKING
203
&& resource->info->auto_checked_out)
204
return DAV_AUTO_VERSION_ALWAYS;
207
/* Default: whatever it is, assume it's not auto-versionable */
208
return DAV_AUTO_VERSION_NEVER;
211
static dav_error *dav_svn_vsn_control(dav_resource *resource,
214
/* All mod_dav_svn resources are versioned objects; so it doesn't
215
make sense to call vsn_control on a resource that exists . */
216
if (resource->exists)
217
return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0,
218
"vsn_control called on already-versioned resource.");
220
/* Only allow a NULL target, which means an create an 'empty' VCR. */
222
return dav_new_error_tag(resource->pool, HTTP_NOT_IMPLEMENTED,
223
SVN_ERR_UNSUPPORTED_FEATURE,
224
"vsn_control called with non-null target.",
225
SVN_DAV_ERROR_NAMESPACE,
228
/* This is kind of silly. The docstring for this callback says it's
229
supposed to "put a resource under version control". But in
230
Subversion, all REGULAR resources (bc's or public URIs) are
231
already under version control. So we don't need to do a thing to
232
the resource, just return. */
236
dav_error *dav_svn_checkout(dav_resource *resource,
238
int is_unreserved, int is_fork_ok,
240
apr_array_header_t *activities,
241
dav_resource **working_resource)
243
const char *txn_name;
245
apr_status_t apr_err;
247
dav_svn_uri_info parse;
249
/* Auto-Versioning Stuff */
252
dav_resource *res; /* ignored */
254
char uuid_buf[APR_UUID_FORMATTED_LENGTH + 1];
256
const char *shared_activity, *shared_txn_name = NULL;
258
/* Baselines can be auto-checked-out -- grudgingly -- so we can
259
allow clients to proppatch unversioned rev props. See issue
261
if ((resource->type == DAV_RESOURCE_TYPE_VERSION)
262
&& resource->baselined)
263
/* ### We're violating deltaV big time here, by allowing a
264
dav_auto_checkout() on something that mod_dav assumes is a
265
VCR, not a VR. Anyway, mod_dav thinks we're checking out the
266
resource 'in place', so that no working resource is returned.
267
(It passes NULL as **working_resource.) */
270
if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
271
return dav_new_error_tag(resource->pool, HTTP_METHOD_NOT_ALLOWED,
272
SVN_ERR_UNSUPPORTED_FEATURE,
273
"auto-checkout attempted on non-regular "
274
"version-controlled resource.",
275
SVN_DAV_ERROR_NAMESPACE,
278
if (resource->baselined)
279
return dav_new_error_tag(resource->pool, HTTP_METHOD_NOT_ALLOWED,
280
SVN_ERR_UNSUPPORTED_FEATURE,
281
"auto-checkout attempted on baseline "
282
"collection, which is not supported.",
283
SVN_DAV_ERROR_NAMESPACE,
286
/* See if the shared activity already exists. */
287
apr_err = apr_pool_userdata_get(&data,
288
DAV_SVN_AUTOVERSIONING_ACTIVITY,
289
resource->info->r->pool);
291
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
292
HTTP_INTERNAL_SERVER_ERROR,
293
"Error fetching pool userdata.",
295
shared_activity = data;
297
if (! shared_activity)
299
/* Build a shared activity for all auto-checked-out resources. */
301
apr_uuid_format(uuid_buf, &uuid);
302
shared_activity = apr_pstrdup(resource->info->r->pool, uuid_buf);
304
derr = dav_svn_create_activity(resource->info->repos,
306
resource->info->r->pool);
307
if (derr) return derr;
309
derr = dav_svn_store_activity(resource->info->repos,
310
shared_activity, shared_txn_name);
311
if (derr) return derr;
313
/* Save the shared activity in r->pool for others to use. */
314
apr_err = apr_pool_userdata_set(shared_activity,
315
DAV_SVN_AUTOVERSIONING_ACTIVITY,
316
NULL, resource->info->r->pool);
318
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
319
HTTP_INTERNAL_SERVER_ERROR,
320
"Error setting pool userdata.",
324
if (! shared_txn_name)
326
shared_txn_name = dav_svn_get_txn(resource->info->repos,
328
if (! shared_txn_name)
329
return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
330
"Cannot look up a txn_name by activity");
333
/* Tweak the VCR in-place, making it into a WR. (Ignore the
334
NULL return value.) */
335
res = dav_svn_create_working_resource(resource,
336
shared_activity, shared_txn_name,
337
TRUE /* tweak in place */);
339
/* Remember that this resource was auto-checked-out, so that
340
dav_svn_auto_versionable allows us to do an auto-checkin and
341
dav_svn_can_be_activity will allow this resource to be an
343
resource->info->auto_checked_out = TRUE;
345
/* The txn and txn_root must be open and ready to go in the
346
resource's root object. Normally prep_resource() will do
347
this automatically on a WR's root object. We're
348
converting a VCR to WR forcibly, so it's now our job to
349
make sure it happens. */
350
derr = open_txn(&resource->info->root.txn, resource->info->repos->fs,
351
resource->info->root.txn_name, resource->pool);
352
if (derr) return derr;
354
serr = svn_fs_txn_root(&resource->info->root.root,
355
resource->info->root.txn, resource->pool);
357
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
358
"Could not open a (transaction) root "
363
/* end of Auto-Versioning Stuff */
365
if (resource->type != DAV_RESOURCE_TYPE_VERSION)
367
return dav_new_error_tag(resource->pool, HTTP_METHOD_NOT_ALLOWED,
368
SVN_ERR_UNSUPPORTED_FEATURE,
369
"CHECKOUT can only be performed on a version "
370
"resource [at this time].",
371
SVN_DAV_ERROR_NAMESPACE,
376
return dav_new_error_tag(resource->pool, HTTP_NOT_IMPLEMENTED,
377
SVN_ERR_UNSUPPORTED_FEATURE,
378
"CHECKOUT can not create an activity at this "
379
"time. Use MKACTIVITY first.",
380
SVN_DAV_ERROR_NAMESPACE,
385
return dav_new_error_tag(resource->pool, HTTP_NOT_IMPLEMENTED,
386
SVN_ERR_UNSUPPORTED_FEATURE,
387
"Unreserved checkouts are not yet available. "
388
"A version history may not be checked out more "
389
"than once, into a specific activity.",
390
SVN_DAV_ERROR_NAMESPACE,
393
if (activities == NULL)
395
return dav_new_error_tag(resource->pool, HTTP_CONFLICT,
396
SVN_ERR_INCOMPLETE_DATA,
397
"An activity must be provided for checkout.",
398
SVN_DAV_ERROR_NAMESPACE,
401
/* assert: nelts > 0. the below check effectively means > 1. */
402
if (activities->nelts != 1)
404
return dav_new_error_tag(resource->pool, HTTP_CONFLICT,
405
SVN_ERR_INCORRECT_PARAMS,
406
"Only one activity may be specified within the "
408
SVN_DAV_ERROR_NAMESPACE,
412
serr = dav_svn_simple_parse_uri(&parse, resource,
413
APR_ARRAY_IDX(activities, 0, const char *),
417
/* ### is BAD_REQUEST proper? */
418
return dav_svn_convert_err(serr, HTTP_CONFLICT,
419
"The activity href could not be parsed "
423
if (parse.activity_id == NULL)
425
return dav_new_error_tag(resource->pool, HTTP_CONFLICT,
426
SVN_ERR_INCORRECT_PARAMS,
427
"The provided href is not an activity URI.",
428
SVN_DAV_ERROR_NAMESPACE,
432
if ((txn_name = dav_svn_get_txn(resource->info->repos,
433
parse.activity_id)) == NULL)
435
return dav_new_error_tag(resource->pool, HTTP_CONFLICT,
436
SVN_ERR_APMOD_ACTIVITY_NOT_FOUND,
437
"The specified activity does not exist.",
438
SVN_DAV_ERROR_NAMESPACE,
442
/* verify the specified version resource is the "latest", thus allowing
443
changes to be made. */
444
if (resource->baselined || resource->info->root.rev == SVN_INVALID_REVNUM)
446
/* a Baseline, or a standard Version Resource which was accessed
447
via a Label against a VCR within a Baseline Collection. */
448
/* ### at the moment, this branch is only reached for baselines */
450
svn_revnum_t youngest;
452
/* make sure the baseline being checked out is the latest */
453
serr = svn_fs_youngest_rev(&youngest, resource->info->repos->fs,
457
/* ### correct HTTP error? */
458
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
459
"Could not determine the youngest "
460
"revision for verification against "
461
"the baseline being checked out.",
465
if (resource->info->root.rev != youngest)
467
return dav_new_error_tag(resource->pool, HTTP_CONFLICT,
468
SVN_ERR_APMOD_BAD_BASELINE,
469
"The specified baseline is not the latest "
470
"baseline, so it may not be checked out.",
471
SVN_DAV_ERROR_NAMESPACE,
475
/* ### hmm. what if the transaction root's revision is different
476
### from this baseline? i.e. somebody created a new revision while
477
### we are processing this commit.
479
### first question: what does the client *do* with a working
480
### baseline? knowing that, and how it maps to our backend, then
481
### we can figure out what to do here. */
485
/* standard Version Resource */
488
svn_fs_root_t *txn_root;
489
svn_revnum_t txn_created_rev;
492
/* open the specified transaction so that we can verify this version
493
resource corresponds to the current/latest in the transaction. */
494
if ((err = open_txn(&txn, resource->info->repos->fs, txn_name,
495
resource->pool)) != NULL)
498
serr = svn_fs_txn_root(&txn_root, txn, resource->pool);
501
/* ### correct HTTP error? */
502
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
503
"Could not open the transaction tree.",
507
/* assert: repos_path != NULL (for this type of resource) */
510
/* Out-of-dateness check: compare the created-rev of the item
511
in the txn against the created-rev of the version resource
513
serr = svn_fs_node_created_rev(&txn_created_rev,
514
txn_root, resource->info->repos_path,
518
/* ### correct HTTP error? */
519
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
520
"Could not get created-rev of "
525
/* If txn_created_rev is invalid, that means it's already
526
mutable in the txn... which means it has already passed this
527
out-of-dateness check. (Usually, this happens when looking
528
at a parent directory of an already checked-out
531
Now, we come down to it. If the created revision of the node
532
in the transaction is different from the revision parsed from
533
the version resource URL, we're in a bit of a quandry, and
534
one of a few things could be true.
536
- The client is trying to modify an old (out of date)
537
revision of the resource. This is, of course,
540
- The client is trying to modify a *newer* revision. If the
541
version resource is *newer* than the transaction root, then
542
the client started a commit, a new revision was created
543
within the repository, the client fetched the new resource
544
from that new revision, changed it (or merged in a prior
545
change), and then attempted to incorporate that into the
546
commit that was initially started. We could copy that new
547
node into our transaction and then modify it, but why
548
bother? We can stop the commit, and everything will be
549
fine again if the user simply restarts it (because we'll
550
use that new revision as the transaction root, thus
551
incorporating the new resource, which they will then
554
- The path/revision that client is wishing to edit and the
555
path/revision in the current transaction are actually the
556
same node, and thus this created-rev comparison didn't
557
really solidify anything after all. :-)
560
if (SVN_IS_VALID_REVNUM( txn_created_rev ))
564
if (resource->info->root.rev < txn_created_rev)
566
/* The item being modified is older than the one in the
567
transaction. The client is out of date. */
570
else if (resource->info->root.rev > txn_created_rev)
572
/* The item being modified is being accessed via a newer
573
revision than the one in the transaction. We'll
574
check to see if they are still the same node, and if
575
not, return an error. */
576
const svn_fs_id_t *url_noderev_id, *txn_noderev_id;
578
if ((serr = svn_fs_node_id(&txn_noderev_id, txn_root,
579
resource->info->repos_path,
582
err = dav_new_error_tag
583
(resource->pool, HTTP_CONFLICT, serr->apr_err,
584
"Unable to fetch the node revision id of the version "
585
"resource within the transaction.",
586
SVN_DAV_ERROR_NAMESPACE,
588
svn_error_clear(serr);
591
if ((serr = svn_fs_node_id(&url_noderev_id,
592
resource->info->root.root,
593
resource->info->repos_path,
596
err = dav_new_error_tag
597
(resource->pool, HTTP_CONFLICT, serr->apr_err,
598
"Unable to fetch the node revision id of the version "
599
"resource within the revision.",
600
SVN_DAV_ERROR_NAMESPACE,
602
svn_error_clear(serr);
605
if (svn_fs_compare_ids(url_noderev_id, txn_noderev_id) != 0)
613
return dav_new_error_tag
614
(resource->pool, HTTP_CONFLICT, SVN_ERR_FS_CONFLICT,
615
"The version resource does not correspond to the resource "
616
"within the transaction. Either the requested version "
617
"resource is out of date (needs to be updated), or the "
618
"requested version resource is newer than the transaction "
619
"root (restart the commit).",
620
SVN_DAV_ERROR_NAMESPACE,
624
/* ### some debugging code */
627
msg = apr_psprintf(resource->pool,
628
"created-rev mismatch: r=%ld, t=%ld",
629
resource->info->root.rev, txn_created_rev);
631
return dav_new_error_tag(resource->pool, HTTP_CONFLICT,
632
SVN_ERR_FS_CONFLICT, msg,
633
SVN_DAV_ERROR_NAMESPACE,
639
*working_resource = dav_svn_create_working_resource(resource,
646
static dav_error *dav_svn_uncheckout(dav_resource *resource)
648
if (resource->type != DAV_RESOURCE_TYPE_WORKING)
649
return dav_new_error_tag(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
650
SVN_ERR_UNSUPPORTED_FEATURE,
651
"UNCHECKOUT called on non-working resource.",
652
SVN_DAV_ERROR_NAMESPACE,
655
/* Try to abort the txn if it exists; but don't try too hard. :-) */
656
if (resource->info->root.txn)
657
svn_error_clear(svn_fs_abort_txn(resource->info->root.txn,
660
/* Attempt to destroy the shared activity. */
661
if (resource->info->root.activity_id)
663
dav_svn_delete_activity(resource->info->repos,
664
resource->info->root.activity_id);
665
apr_pool_userdata_set(NULL, DAV_SVN_AUTOVERSIONING_ACTIVITY,
666
NULL, resource->info->r->pool);
669
resource->info->root.txn_name = NULL;
670
resource->info->root.txn = NULL;
672
/* We're no longer checked out. */
673
resource->info->auto_checked_out = FALSE;
675
/* Convert the working resource back into a regular one, in-place. */
676
return dav_svn_working_to_regular_resource(resource);
680
/* Closure object for cleanup_deltify. */
681
struct cleanup_deltify_baton
683
/* The repository in which to deltify. We use a path instead of an
684
object, because it's difficult to obtain a repos or fs object
685
with the right lifetime guarantees. */
686
const char *repos_path;
688
/* The revision number against which to deltify. */
689
svn_revnum_t revision;
691
/* The pool to use for all temporary allocation while working. This
692
may or may not be the same as the pool on which the cleanup is
693
registered, but obviously it must have a lifetime at least as
694
long as that pool. */
699
/* APR pool cleanup function to deltify against a just-committed
700
revision. DATA is a 'struct cleanup_deltify_baton *'.
702
If any errors occur, log them in the httpd server error log, but
703
return APR_SUCCESS no matter what, as this is a pool cleanup
704
function and deltification is not a matter of correctness
706
static apr_status_t cleanup_deltify(void *data)
708
struct cleanup_deltify_baton *cdb = data;
712
/* It's okay to allocate in the pool that's being cleaned up, and
713
it's also okay to register new cleanups against that pool. But
714
if you create subpools of it, you must make sure to destroy them
715
at the end of the cleanup. So we do all our work in this
716
subpool, then destroy it before exiting. */
717
apr_pool_t *subpool = svn_pool_create(cdb->pool);
719
err = svn_repos_open(&repos, cdb->repos_path, subpool);
722
ap_log_perror(APLOG_MARK, APLOG_ERR, err->apr_err, cdb->pool,
723
"cleanup_deltify: error opening repository '%s'",
725
svn_error_clear(err);
729
err = svn_fs_deltify_revision(svn_repos_fs(repos),
730
cdb->revision, subpool);
733
ap_log_perror(APLOG_MARK, APLOG_ERR, err->apr_err, cdb->pool,
734
"cleanup_deltify: error deltifying against revision %ld"
735
" in repository '%s'",
736
cdb->revision, cdb->repos_path);
737
svn_error_clear(err);
741
svn_pool_destroy(subpool);
747
/* Register the cleanup_deltify function on POOL, which should be the
748
connection pool for the request. This way the time needed for
749
deltification won't delay the response to the client.
751
REPOS is the repository in which deltify, and REVISION is the
752
revision against which to deltify. POOL is both the pool on which
753
to register the cleanup function and the pool that will be used for
754
temporary allocations while deltifying. */
755
static void register_deltification_cleanup(svn_repos_t *repos,
756
svn_revnum_t revision,
759
struct cleanup_deltify_baton *cdb = apr_palloc(pool, sizeof(*cdb));
761
cdb->repos_path = svn_repos_path(repos, pool);
762
cdb->revision = revision;
765
apr_pool_cleanup_register(pool, cdb, cleanup_deltify, apr_pool_cleanup_null);
769
dav_error *dav_svn_checkin(dav_resource *resource,
770
int keep_checked_out,
771
dav_resource **version_resource)
775
apr_status_t apr_err;
777
const char *shared_activity;
780
/* ### mod_dav has a flawed architecture, in the sense that it first
781
tries to auto-checkin the modified resource, then attempts to
782
auto-checkin the parent resource (if the parent resource was
783
auto-checked-out). Instead, the provider should be in charge:
784
mod_dav should provide a *set* of resources that need
785
auto-checkin, and the provider can decide how to do it. (One
786
txn? Many txns? Etc.) */
788
if (resource->type != DAV_RESOURCE_TYPE_WORKING)
789
return dav_new_error_tag(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
790
SVN_ERR_UNSUPPORTED_FEATURE,
791
"CHECKIN called on non-working resource.",
792
SVN_DAV_ERROR_NAMESPACE,
795
/* If the global autoversioning activity still exists, that means
796
nobody's committed it yet. */
797
apr_err = apr_pool_userdata_get(&data,
798
DAV_SVN_AUTOVERSIONING_ACTIVITY,
799
resource->info->r->pool);
801
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
802
HTTP_INTERNAL_SERVER_ERROR,
803
"Error fetching pool userdata.",
805
shared_activity = data;
807
/* Try to commit the txn if it exists. */
809
&& (strcmp(shared_activity, resource->info->root.activity_id) == 0))
811
const char *shared_txn_name;
812
const char *conflict_msg;
813
svn_revnum_t new_rev;
815
shared_txn_name = dav_svn_get_txn(resource->info->repos,
817
if (! shared_txn_name)
818
return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
819
"Cannot look up a txn_name by activity");
822
if (resource->info->root.txn_name
823
&& (strcmp(shared_txn_name, resource->info->root.txn_name) != 0))
824
return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
825
"Internal txn_name doesn't match"
826
" autoversioning transaction.");
828
if (! resource->info->root.txn)
829
/* should already be open by dav_svn_checkout */
830
return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
831
"Autoversioning txn isn't open "
832
"when it should be.");
834
err = set_auto_revprops(resource);
838
serr = svn_repos_fs_commit_txn(&conflict_msg,
839
resource->info->repos->repos,
841
resource->info->root.txn,
847
svn_error_clear(svn_fs_abort_txn(resource->info->root.txn,
850
if (serr->apr_err == SVN_ERR_FS_CONFLICT)
852
msg = apr_psprintf(resource->pool,
853
"A conflict occurred during the CHECKIN "
854
"processing. The problem occurred with "
855
"the \"%s\" resource.",
859
msg = "An error occurred while committing the transaction.";
861
/* Attempt to destroy the shared activity. */
862
dav_svn_delete_activity(resource->info->repos, shared_activity);
863
apr_pool_userdata_set(NULL, DAV_SVN_AUTOVERSIONING_ACTIVITY,
864
NULL, resource->info->r->pool);
866
return dav_svn_convert_err(serr, HTTP_CONFLICT, msg,
870
/* Attempt to destroy the shared activity. */
871
dav_svn_delete_activity(resource->info->repos, shared_activity);
872
apr_pool_userdata_set(NULL, DAV_SVN_AUTOVERSIONING_ACTIVITY,
873
NULL, resource->info->r->pool);
875
/* Commit was successful, so schedule deltification. */
876
register_deltification_cleanup(resource->info->repos->repos,
878
resource->info->r->connection->pool);
880
/* If caller wants it, return the new VR that was created by
882
if (version_resource)
884
uri = dav_svn_build_uri(resource->info->repos,
885
DAV_SVN_BUILD_URI_VERSION,
886
new_rev, resource->info->repos_path,
889
err = dav_svn_create_version_resource(version_resource, uri,
894
} /* end of commit stuff */
896
/* The shared activity was either nonexistent to begin with, or it's
897
been committed and is only now nonexistent. The resource needs
898
to forget about it. */
899
resource->info->root.txn_name = NULL;
900
resource->info->root.txn = NULL;
902
/* Convert the working resource back into an regular one. */
903
if (! keep_checked_out)
905
resource->info->auto_checked_out = FALSE;
906
return dav_svn_working_to_regular_resource(resource);
912
static dav_error *dav_svn_avail_reports(const dav_resource *resource,
913
const dav_report_elem **reports)
915
/* ### further restrict to the public space? */
916
if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
921
*reports = avail_reports;
925
static int dav_svn_report_label_header_allowed(const apr_xml_doc *doc)
930
/* Respond to a S:dated-rev-report request. The request contains a
931
* DAV:creationdate element giving the requested date; the response
932
* contains a DAV:version-name element giving the most recent revision
933
* as of that date. */
934
static dav_error * dav_svn__drev_report(const dav_resource *resource,
935
const apr_xml_doc *doc,
940
apr_time_t tm = (apr_time_t) -1;
942
apr_bucket_brigade *bb;
944
apr_status_t apr_err;
945
dav_error *derr = NULL;
947
/* Find the DAV:creationdate element and get the requested time from it. */
948
ns = dav_svn_find_ns(doc->namespaces, "DAV:");
951
for (child = doc->root->first_child; child != NULL; child = child->next)
953
if (child->ns != ns || strcmp(child->name, "creationdate") != 0)
955
/* If this fails, we'll notice below, so ignore any error for now. */
957
(svn_time_from_cstring(&tm, dav_xml_get_cdata(child,
963
if (tm == (apr_time_t) -1)
965
return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0,
966
"The request does not contain a valid "
967
"'DAV:creationdate' element.");
970
/* Do the actual work of finding the revision by date. */
971
if ((err = svn_repos_dated_revision(&rev, resource->info->repos->repos, tm,
972
resource->pool)) != SVN_NO_ERROR)
974
svn_error_clear (err);
975
return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
976
"Could not access revision times.");
979
bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
980
apr_err = ap_fprintf(output, bb,
981
DAV_XML_HEADER DEBUG_CR
982
"<S:dated-rev-report xmlns:S=\"" SVN_XML_NAMESPACE "\" "
983
"xmlns:D=\"DAV:\">" DEBUG_CR
984
"<D:version-name>%ld</D:version-name>"
985
"</S:dated-rev-report>", rev);
987
derr = dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
988
HTTP_INTERNAL_SERVER_ERROR,
989
"Error writing REPORT response.",
992
/* Flush the contents of the brigade (returning an error only if we
993
don't already have one). */
994
if (((apr_err = ap_fflush(output, bb))) && (! derr))
995
derr = dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
996
HTTP_INTERNAL_SERVER_ERROR,
997
"Error flushing brigade.",
1004
/* Respond to a get-locks-report request. See description of this
1005
report in libsvn_ra_dav/fetch.c. */
1006
static dav_error * dav_svn__get_locks_report(const dav_resource *resource,
1007
const apr_xml_doc *doc,
1008
ap_filter_t *output)
1010
apr_bucket_brigade *bb;
1012
apr_status_t apr_err;
1014
dav_svn_authz_read_baton arb;
1015
apr_hash_index_t *hi;
1016
apr_pool_t *subpool;
1018
/* The request URI should be a public one representing an fs path. */
1019
if ((! resource->info->repos_path)
1020
|| (! resource->info->repos->repos))
1021
return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0,
1022
"get-locks-report run on resource which doesn't "
1023
"represent a path within a repository.");
1025
arb.r = resource->info->r;
1026
arb.repos = resource->info->repos;
1028
/* Fetch the locks, but allow authz_read checks to happen on each. */
1029
if ((err = svn_repos_fs_get_locks(&locks,
1030
resource->info->repos->repos,
1031
resource->info->repos_path,
1032
dav_svn_authz_read, &arb,
1033
resource->pool)) != SVN_NO_ERROR)
1034
return dav_svn_convert_err(err, HTTP_INTERNAL_SERVER_ERROR,
1035
err->message, resource->pool);
1037
bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
1039
/* start sending report */
1040
apr_err = ap_fprintf(output, bb,
1041
DAV_XML_HEADER DEBUG_CR
1042
"<S:get-locks-report xmlns:S=\"" SVN_XML_NAMESPACE "\" "
1043
"xmlns:D=\"DAV:\">" DEBUG_CR);
1045
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1046
HTTP_INTERNAL_SERVER_ERROR,
1047
"Error writing REPORT response.",
1050
/* stream the locks */
1051
subpool = svn_pool_create(resource->pool);
1052
for (hi = apr_hash_first(resource->pool, locks); hi; hi = apr_hash_next(hi))
1056
const svn_lock_t *lock;
1057
const char *path_quoted, *token_quoted;
1058
const char *creation_str, *expiration_str;
1059
const char *owner_to_send, *comment_to_send;
1060
svn_boolean_t owner_base64 = FALSE, comment_base64 = FALSE;
1062
svn_pool_clear(subpool);
1063
apr_hash_this(hi, &key, NULL, &val);
1066
path_quoted = apr_xml_quote_string(subpool, lock->path, 1);
1067
token_quoted = apr_xml_quote_string(subpool, lock->token, 1);
1068
creation_str = svn_time_to_cstring(lock->creation_date, subpool);
1070
apr_err = ap_fprintf(output, bb,
1072
"<S:path>%s</S:path>" DEBUG_CR
1073
"<S:token>%s</S:token>" DEBUG_CR
1074
"<S:creationdate>%s</S:creationdate>" DEBUG_CR,
1075
path_quoted, token_quoted, creation_str);
1077
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1078
HTTP_INTERNAL_SERVER_ERROR,
1079
"Error writing REPORT response.",
1082
if (lock->expiration_date)
1084
expiration_str = svn_time_to_cstring(lock->expiration_date, subpool);
1085
apr_err = ap_fprintf(output, bb,
1086
"<S:expirationdate>%s</S:expirationdate>"
1087
DEBUG_CR, expiration_str);
1089
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1090
HTTP_INTERNAL_SERVER_ERROR,
1091
"Error writing REPORT response.",
1095
if (svn_xml_is_xml_safe(lock->owner, strlen(lock->owner)))
1097
owner_to_send = apr_xml_quote_string(subpool, lock->owner, 1);
1101
svn_string_t owner_string;
1102
const svn_string_t *encoded_owner;
1104
owner_string.data = lock->owner;
1105
owner_string.len = strlen(lock->owner);
1106
encoded_owner = svn_base64_encode_string(&owner_string, subpool);
1107
owner_to_send = encoded_owner->data;
1108
owner_base64 = TRUE;
1111
apr_err = ap_fprintf(output, bb,
1112
"<S:owner %s>%s</S:owner>" DEBUG_CR,
1113
owner_base64 ? "encoding=\"base64\"" : "",
1116
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1117
HTTP_INTERNAL_SERVER_ERROR,
1118
"Error writing REPORT response.",
1123
if (svn_xml_is_xml_safe(lock->comment, strlen(lock->comment)))
1125
comment_to_send = apr_xml_quote_string(subpool,
1130
svn_string_t comment_string;
1131
const svn_string_t *encoded_comment;
1133
comment_string.data = lock->comment;
1134
comment_string.len = strlen(lock->comment);
1135
encoded_comment = svn_base64_encode_string(&comment_string,
1137
comment_to_send = encoded_comment->data;
1138
comment_base64 = TRUE;
1141
apr_err = ap_fprintf(output, bb,
1142
"<S:comment %s>%s</S:comment>" DEBUG_CR,
1143
comment_base64 ? "encoding=\"base64\"" : "",
1146
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1147
HTTP_INTERNAL_SERVER_ERROR,
1148
"Error writing REPORT response.",
1152
apr_err = ap_fprintf(output, bb, "</S:lock>" DEBUG_CR);
1154
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1155
HTTP_INTERNAL_SERVER_ERROR,
1156
"Error writing REPORT response.",
1158
} /* end of hash loop */
1159
svn_pool_destroy(subpool);
1161
/* finish the report */
1162
apr_err = ap_fprintf(output, bb, "</S:get-locks-report>" DEBUG_CR);
1164
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1165
HTTP_INTERNAL_SERVER_ERROR,
1166
"Error writing REPORT response.",
1169
/* Flush the contents of the brigade (returning an error only if we
1170
don't already have one). */
1171
if ((apr_err = ap_fflush(output, bb)))
1172
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1173
HTTP_INTERNAL_SERVER_ERROR,
1174
"Error flushing brigade.",
1182
static apr_status_t send_get_locations_report(ap_filter_t *output,
1183
apr_bucket_brigade *bb,
1184
const dav_resource *resource,
1185
apr_hash_t *fs_locations)
1187
apr_hash_index_t *hi;
1189
apr_status_t apr_err;
1191
pool = resource->pool;
1193
apr_err = ap_fprintf(output, bb, DAV_XML_HEADER DEBUG_CR
1194
"<S:get-locations-report xmlns:S=\"" SVN_XML_NAMESPACE
1195
"\" xmlns:D=\"DAV:\">" DEBUG_CR);
1199
for (hi = apr_hash_first(pool, fs_locations); hi; hi = apr_hash_next (hi))
1203
const char *path_quoted;
1205
apr_hash_this(hi, &key, NULL, &value);
1206
path_quoted = apr_xml_quote_string(pool, value, 1);
1207
apr_err = ap_fprintf(output, bb, "<S:location "
1208
"rev=\"%ld\" path=\"%s\"/>" DEBUG_CR,
1209
*(svn_revnum_t *)key, path_quoted);
1213
return ap_fprintf(output, bb, "</S:get-locations-report>" DEBUG_CR);
1216
dav_error *dav_svn__get_locations_report(const dav_resource *resource,
1217
const apr_xml_doc *doc,
1218
ap_filter_t *output)
1221
dav_error *derr = NULL;
1222
apr_status_t apr_err;
1223
apr_bucket_brigade *bb;
1224
dav_svn_authz_read_baton arb;
1226
/* The parameters to do the operation on. */
1227
const char *relative_path = NULL;
1228
const char *abs_path;
1229
svn_revnum_t peg_revision = SVN_INVALID_REVNUM;
1230
apr_array_header_t *location_revisions;
1232
/* XML Parsing Variables */
1234
apr_xml_elem *child;
1236
apr_hash_t *fs_locations;
1238
location_revisions = apr_array_make(resource->pool, 0,
1239
sizeof(svn_revnum_t));
1242
ns = dav_svn_find_ns(doc->namespaces, SVN_XML_NAMESPACE);
1245
return dav_new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
1246
"The request does not contain the 'svn:' "
1247
"namespace, so it is not going to have certain "
1248
"required elements.",
1249
SVN_DAV_ERROR_NAMESPACE,
1253
/* Gather the parameters. */
1254
for (child = doc->root->first_child; child != NULL; child = child->next)
1256
/* If this element isn't one of ours, then skip it. */
1257
if (child->ns != ns)
1260
if (strcmp(child->name, "peg-revision") == 0)
1261
peg_revision = SVN_STR_TO_REV(dav_xml_get_cdata(child,
1262
resource->pool, 1));
1263
else if (strcmp(child->name, "location-revision") == 0)
1265
svn_revnum_t revision
1266
= SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1));
1267
APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
1269
else if (strcmp(child->name, "path") == 0)
1271
relative_path = dav_xml_get_cdata(child, resource->pool, 0);
1272
if ((derr = dav_svn__test_canonical(relative_path, resource->pool)))
1277
/* Now we should have the parameters ready - let's
1278
check if they are all present. */
1279
if (! (relative_path && SVN_IS_VALID_REVNUM(peg_revision)))
1281
return dav_new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
1282
"Not all parameters passed.",
1283
SVN_DAV_ERROR_NAMESPACE,
1287
/* Append the relative path to the base FS path to get an absolute
1289
abs_path = svn_path_join(resource->info->repos_path, relative_path,
1292
/* Build an authz read baton */
1293
arb.r = resource->info->r;
1294
arb.repos = resource->info->repos;
1296
serr = svn_repos_trace_node_locations(resource->info->repos->fs,
1297
&fs_locations, abs_path, peg_revision,
1299
dav_svn_authz_read, &arb,
1304
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1305
serr->message, resource->pool);
1308
bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
1310
apr_err = send_get_locations_report(output, bb, resource, fs_locations);
1313
derr = dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1314
HTTP_INTERNAL_SERVER_ERROR,
1315
"Error writing REPORT response.",
1318
/* Flush the contents of the brigade (returning an error only if we
1319
don't already have one). */
1320
if (((apr_err = ap_fflush(output, bb))) && (! derr))
1321
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1322
HTTP_INTERNAL_SERVER_ERROR,
1323
"Error flushing brigade.",
1329
static dav_error *dav_svn_deliver_report(request_rec *r,
1330
const dav_resource *resource,
1331
const apr_xml_doc *doc,
1332
ap_filter_t *output)
1334
int ns = dav_svn_find_ns(doc->namespaces, SVN_XML_NAMESPACE);
1336
if (doc->root->ns == ns)
1338
/* ### note that these report names should have symbols... */
1340
if (strcmp(doc->root->name, "update-report") == 0)
1342
return dav_svn__update_report(resource, doc, output);
1344
else if (strcmp(doc->root->name, "log-report") == 0)
1346
return dav_svn__log_report(resource, doc, output);
1348
else if (strcmp(doc->root->name, "dated-rev-report") == 0)
1350
return dav_svn__drev_report(resource, doc, output);
1352
else if (strcmp(doc->root->name, "get-locations") == 0)
1354
return dav_svn__get_locations_report(resource, doc, output);
1356
else if (strcmp(doc->root->name, "file-revs-report") == 0)
1358
return dav_svn__file_revs_report(resource, doc, output);
1360
else if (strcmp(doc->root->name, "get-locks-report") == 0)
1362
return dav_svn__get_locks_report(resource, doc, output);
1365
/* NOTE: if you add a report, don't forget to add it to the
1366
* avail_reports[] array at the top of this file.
1370
/* ### what is a good error for an unknown report? */
1371
return dav_new_error_tag(resource->pool, HTTP_NOT_IMPLEMENTED,
1372
SVN_ERR_UNSUPPORTED_FEATURE,
1373
"The requested report is unknown.",
1374
SVN_DAV_ERROR_NAMESPACE,
1378
static int dav_svn_can_be_activity(const dav_resource *resource)
1380
/* If our resource is marked as auto_checked_out'd, then we allow this to
1381
* be an activity URL. Otherwise, it must be a real activity URL that
1382
* doesn't already exist.
1384
return (resource->info->auto_checked_out == TRUE ||
1385
(resource->type == DAV_RESOURCE_TYPE_ACTIVITY &&
1386
!resource->exists));
1389
static dav_error *dav_svn_make_activity(dav_resource *resource)
1391
const char *activity_id = resource->info->root.activity_id;
1392
const char *txn_name;
1395
/* sanity check: make sure the resource is a valid activity, in
1396
case an older mod_dav doesn't do the check for us. */
1397
if (! dav_svn_can_be_activity(resource))
1398
return dav_new_error_tag(resource->pool, HTTP_FORBIDDEN,
1399
SVN_ERR_APMOD_MALFORMED_URI,
1400
"Activities cannot be created at that location; "
1401
"query the DAV:activity-collection-set property.",
1402
SVN_DAV_ERROR_NAMESPACE,
1405
err = dav_svn_create_activity(resource->info->repos, &txn_name,
1410
err = dav_svn_store_activity(resource->info->repos, activity_id, txn_name);
1414
/* everything is happy. update the resource */
1415
resource->info->root.txn_name = txn_name;
1416
resource->exists = 1;
1422
dav_error *dav_svn__build_lock_hash(apr_hash_t **locks,
1424
const char *path_prefix,
1427
apr_status_t apr_err;
1430
apr_xml_doc *doc = NULL;
1431
apr_xml_elem *child, *lockchild;
1433
apr_hash_t *hash = apr_hash_make(pool);
1435
/* Grab the request body out of r->pool, as it contains all of the
1436
lock tokens. It should have been stashed already by our custom
1438
apr_err = apr_pool_userdata_get(&data, "svn-request-body", r->pool);
1440
return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
1441
HTTP_INTERNAL_SERVER_ERROR,
1442
"Error fetching pool userdata.",
1448
return SVN_NO_ERROR;
1452
ns = dav_svn_find_ns(doc->namespaces, SVN_XML_NAMESPACE);
1455
/* If there's no svn: namespace in the body, then there are
1456
definitely no lock-tokens to harvest. This is likely a
1457
request from an old client. */
1459
return SVN_NO_ERROR;
1462
if ((doc->root->ns == ns)
1463
&& (strcmp(doc->root->name, "lock-token-list") == 0))
1469
/* Search doc's children until we find the <lock-token-list>. */
1470
for (child = doc->root->first_child; child != NULL; child = child->next)
1472
/* if this element isn't one of ours, then skip it */
1473
if (child->ns != ns)
1476
if (strcmp(child->name, "lock-token-list") == 0)
1481
/* Then look for N different <lock> structures within. */
1482
for (lockchild = child->first_child; lockchild != NULL;
1483
lockchild = lockchild->next)
1485
const char *lockpath = NULL, *locktoken = NULL;
1486
apr_xml_elem *lfchild;
1488
if (strcmp(lockchild->name, "lock") != 0)
1491
for (lfchild = lockchild->first_child; lfchild != NULL;
1492
lfchild = lfchild->next)
1494
if (strcmp(lfchild->name, "lock-path") == 0)
1496
const char *cdata = dav_xml_get_cdata(lfchild, pool, 0);
1497
if ((derr = dav_svn__test_canonical(cdata, pool)))
1500
/* Create an absolute fs-path */
1501
lockpath = svn_path_join(path_prefix, cdata, pool);
1502
if (lockpath && locktoken)
1504
apr_hash_set(hash, lockpath, APR_HASH_KEY_STRING, locktoken);
1509
else if (strcmp(lfchild->name, "lock-token") == 0)
1511
locktoken = dav_xml_get_cdata(lfchild, pool, 1);
1512
if (lockpath && *locktoken)
1514
apr_hash_set(hash, lockpath, APR_HASH_KEY_STRING, locktoken);
1523
return SVN_NO_ERROR;
1528
dav_error *dav_svn__push_locks(dav_resource *resource,
1532
svn_fs_access_t *fsaccess;
1533
apr_hash_index_t *hi;
1536
serr = svn_fs_get_access (&fsaccess, resource->info->repos->fs);
1539
/* If an authenticated username was attached to the request,
1540
then dav_svn_get_resource() should have already noticed and
1541
created an fs_access_t in the filesystem. */
1542
const char *new_msg = "Lock token(s) in request, but no username.";
1543
svn_error_t *sanitized_error = svn_error_create(serr->apr_err,
1545
ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, resource->info->r,
1546
"%s", serr->message);
1547
svn_error_clear(serr);
1548
return dav_svn_convert_err (sanitized_error, HTTP_BAD_REQUEST,
1549
apr_psprintf(pool, new_msg), pool);
1552
for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1556
apr_hash_this(hi, NULL, NULL, &val);
1559
serr = svn_fs_access_add_lock_token (fsaccess, token);
1561
return dav_svn_convert_err (serr, HTTP_INTERNAL_SERVER_ERROR,
1562
"Error pushing token into filesystem.",
1570
/* Helper for dav_svn_merge(). Free every lock in LOCKS. The locks
1571
live in REPOS. Log any errors for REQUEST. Use POOL for temporary
1573
static svn_error_t *release_locks(apr_hash_t *locks,
1578
apr_hash_index_t *hi;
1581
apr_pool_t *subpool = svn_pool_create(pool);
1584
for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1586
svn_pool_clear(subpool);
1587
apr_hash_this(hi, &key, NULL, &val);
1589
/* The lock may be stolen or broken sometime between
1590
svn_fs_commit_txn() and this post-commit cleanup. So ignore
1591
any errors from this command; just free as many locks as we can. */
1592
err = svn_repos_fs_unlock(repos, key, val, FALSE, subpool);
1594
if (err) /* If we got an error, just log it and move along. */
1595
ap_log_rerror(APLOG_MARK, APLOG_ERR, err->apr_err, r,
1596
"%s", err->message);
1598
svn_error_clear(err);
1601
svn_pool_destroy(subpool);
1603
return SVN_NO_ERROR;
1608
static dav_error *dav_svn_merge(dav_resource *target, dav_resource *source,
1609
int no_auto_merge, int no_checkout,
1610
apr_xml_elem *prop_elem,
1611
ap_filter_t *output)
1616
const char *conflict;
1618
svn_revnum_t new_rev;
1620
svn_boolean_t disable_merge_response = FALSE;
1622
/* We'll use the target's pool for our operation. We happen to know that
1623
it matches the request pool, which (should) have the proper lifetime. */
1624
pool = target->pool;
1626
/* ### what to verify on the target? */
1628
/* ### anything else for the source? */
1629
if (source->type != DAV_RESOURCE_TYPE_ACTIVITY)
1631
return dav_new_error_tag(pool, HTTP_METHOD_NOT_ALLOWED,
1632
SVN_ERR_INCORRECT_PARAMS,
1633
"MERGE can only be performed using an activity "
1634
"as the source [at this time].",
1635
SVN_DAV_ERROR_NAMESPACE,
1639
/* Before attempting the final commit, we need to push any incoming
1640
lock-tokens into the filesystem's access_t. Normally they come
1641
in via 'If:' header, and dav_svn_get_resource() automatically
1642
notices them and does this work for us. In the case of MERGE,
1643
however, svn clients are sending them in the request body. */
1645
err = dav_svn__build_lock_hash(&locks, target->info->r,
1646
target->info->repos_path,
1651
if (apr_hash_count(locks))
1653
err = dav_svn__push_locks(source, locks, pool);
1658
/* We will ignore no_auto_merge and no_checkout. We can't do those, but the
1659
client has no way to assert that we *should* do them. This should be fine
1660
because, presumably, the client has no way to do the various checkouts
1661
and things that would necessitate an auto-merge or checkout during the
1662
MERGE processing. */
1664
/* open the transaction that we're going to commit. */
1665
if ((err = open_txn(&txn, source->info->repos->fs,
1666
source->info->root.txn_name, pool)) != NULL)
1669
/* all righty... commit the bugger. */
1670
serr = svn_repos_fs_commit_txn(&conflict, source->info->repos->repos,
1671
&new_rev, txn, pool);
1673
/* If the error was just a post-commit hook failure, we ignore it.
1674
Otherwise, we deal with it.
1675
### TODO: Figure out if the MERGE response can grow a means by
1676
which to marshal back both the success of the commit (and its
1677
commit info) and the failure of the post-commit hook. */
1678
if (serr && (serr->apr_err != SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
1681
svn_error_clear(svn_fs_abort_txn(txn, pool));
1683
if (serr->apr_err == SVN_ERR_FS_CONFLICT)
1685
/* ### we need to convert the conflict path into a URI */
1686
msg = apr_psprintf(pool,
1687
"A conflict occurred during the MERGE "
1688
"processing. The problem occurred with the "
1693
msg = "An error occurred while committing the transaction.";
1695
return dav_svn_convert_err(serr, HTTP_CONFLICT, msg, pool);
1698
svn_error_clear(serr);
1700
/* Commit was successful, so schedule deltification. */
1701
register_deltification_cleanup(source->info->repos->repos, new_rev,
1702
source->info->r->connection->pool);
1704
/* Since the commit was successful, the txn ID is no longer valid.
1705
Store an empty txn ID in the activity database so that when the
1706
client deletes the activity, we don't try to open and abort the
1708
err = dav_svn_store_activity(source->info->repos,
1709
source->info->root.activity_id, "");
1713
/* Check the dav_resource->info area for information about the
1714
special X-SVN-Options: header that may have come in the http
1716
if (source->info->svn_client_options != NULL)
1718
/* The client might want us to release all locks sent in the
1720
if ((NULL != (ap_strstr_c(source->info->svn_client_options,
1721
SVN_DAV_OPTION_RELEASE_LOCKS)))
1722
&& apr_hash_count(locks))
1724
serr = release_locks(locks, source->info->repos->repos,
1725
source->info->r, pool);
1727
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1728
"Error releasing locks", pool);
1731
/* The client might want us to disable the merge response altogether. */
1732
if (NULL != (ap_strstr_c(source->info->svn_client_options,
1733
SVN_DAV_OPTION_NO_MERGE_RESPONSE)))
1734
disable_merge_response = TRUE;
1737
/* process the response for the new revision. */
1738
return dav_svn__merge_response(output, source->info->repos, new_rev,
1739
prop_elem, disable_merge_response, pool);
1742
const dav_hooks_vsn dav_svn_hooks_vsn = {
1743
dav_svn_get_vsn_options,
1745
dav_svn_versionable,
1746
dav_svn_auto_versionable,
1747
dav_svn_vsn_control,
1751
dav_svn_avail_reports,
1752
dav_svn_report_label_header_allowed,
1753
dav_svn_deliver_report,
1755
NULL, /* add_label */
1756
NULL, /* remove_label */
1757
NULL, /* can_be_workspace */
1758
NULL, /* make_workspace */
1759
dav_svn_can_be_activity,
1760
dav_svn_make_activity,