~svn/ubuntu/oneiric/subversion/ppa

« back to all changes in this revision

Viewing changes to subversion/mod_dav_svn/version.c

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-12-05 01:26:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051205012614-qom4xfypgtsqc2xq
Tags: 1.2.3dfsg1-3ubuntu1
Merge with the final Debian release of 1.2.3dfsg1-3, bringing in
fixes to the clean target, better documentation of the libdb4.3
upgrade and build fixes to work with swig1.3_1.3.27.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * version.c: mod_dav_svn versioning provider functions for Subversion
 
3
 *
 
4
 * ====================================================================
 
5
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 
6
 *
 
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.
 
12
 *
 
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
 * ====================================================================
 
17
 */
 
18
 
 
19
 
 
20
 
 
21
#include <httpd.h>
 
22
#include <http_log.h>
 
23
#include <mod_dav.h>
 
24
#include <apr_tables.h>
 
25
#include <apr_uuid.h>
 
26
 
 
27
#include "svn_fs.h"
 
28
#include "svn_xml.h"
 
29
#include "svn_repos.h"
 
30
#include "svn_dav.h"
 
31
#include "svn_time.h"
 
32
#include "svn_pools.h"
 
33
#include "svn_props.h"
 
34
#include "svn_dav.h"
 
35
#include "svn_base64.h"
 
36
 
 
37
#include "dav_svn.h"
 
38
 
 
39
 
 
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" },
 
49
  { NULL },
 
50
};
 
51
 
 
52
/* declare these static functions early, so we can use them anywhere. */
 
53
static dav_error *dav_svn_make_activity(dav_resource *resource);
 
54
 
 
55
 
 
56
svn_error_t *dav_svn_attach_auto_revprops(svn_fs_txn_t *txn,
 
57
                                          const char *fs_path,
 
58
                                          apr_pool_t *pool)
 
59
{
 
60
  const char *logmsg;
 
61
  svn_string_t *logval;
 
62
  svn_error_t *serr;
 
63
 
 
64
  logmsg = apr_psprintf(pool,  
 
65
                        "Autoversioning commit:  a non-deltaV client made "
 
66
                        "a change to\n%s", fs_path);
 
67
 
 
68
  logval = svn_string_create(logmsg, pool);
 
69
  if ((serr = svn_repos_fs_change_txn_prop(txn, SVN_PROP_REVISION_LOG, logval,
 
70
                                           pool)))
 
71
    return serr;
 
72
 
 
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),
 
79
                                           pool)))
 
80
    return serr;
 
81
 
 
82
  return SVN_NO_ERROR;
 
83
}
 
84
 
 
85
 
 
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)
 
89
{
 
90
  svn_error_t *serr;
 
91
 
 
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.");
 
96
 
 
97
  if ((serr = dav_svn_attach_auto_revprops(resource->info->root.txn,
 
98
                                           resource->info->repos_path,
 
99
                                           resource->pool)))
 
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. ",
 
103
                               resource->pool);
 
104
  return NULL;
 
105
}
 
106
 
 
107
 
 
108
static dav_error *open_txn(svn_fs_txn_t **ptxn, svn_fs_t *fs,
 
109
                           const char *txn_name, apr_pool_t *pool)
 
110
{
 
111
  svn_error_t *serr;
 
112
 
 
113
  serr = svn_fs_open_txn(ptxn, fs, txn_name, pool);
 
114
  if (serr != NULL)
 
115
    {
 
116
      if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
 
117
        {
 
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",
 
122
                                     pool);
 
123
        }
 
124
 
 
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 "
 
129
                                 "activity.",
 
130
                                 pool);
 
131
    }
 
132
 
 
133
  return NULL;
 
134
}
 
135
 
 
136
static void dav_svn_get_vsn_options(apr_pool_t *p, apr_text_header *phdr)
 
137
{
 
138
  /* Note: we append pieces with care for Web Folders's 63-char limit
 
139
     on the DAV: header */
 
140
 
 
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");
 
145
 
 
146
  /* ### fork-control? */
 
147
}
 
148
 
 
149
static dav_error *dav_svn_get_option(const dav_resource *resource,
 
150
                                     const apr_xml_elem *elem,
 
151
                                     apr_text_header *option)
 
152
{
 
153
  /* ### DAV:version-history-collection-set */
 
154
 
 
155
  if (elem->ns == APR_XML_NS_DAV_ID)
 
156
    {
 
157
      if (strcmp(elem->name, "activity-collection-set") == 0)
 
158
        {
 
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>");
 
168
        }
 
169
    }
 
170
 
 
171
  return NULL;
 
172
}
 
173
 
 
174
static int dav_svn_versionable(const dav_resource *resource)
 
175
{
 
176
  return 0;
 
177
}
 
178
 
 
179
static dav_auto_version dav_svn_auto_versionable(const dav_resource *resource)
 
180
{
 
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;
 
187
 
 
188
  /* No other autoversioning is allowed unless the SVNAutoversioning
 
189
     directive is used. */
 
190
  if (resource->info->repos->autoversioning)
 
191
    {
 
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;
 
197
 
 
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;
 
205
    }
 
206
 
 
207
  /* Default:  whatever it is, assume it's not auto-versionable */
 
208
  return DAV_AUTO_VERSION_NEVER;
 
209
}
 
210
 
 
211
static dav_error *dav_svn_vsn_control(dav_resource *resource,
 
212
                                      const char *target)
 
213
{
 
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.");
 
219
 
 
220
  /* Only allow a NULL target, which means an create an 'empty' VCR. */
 
221
  if (target != NULL)
 
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,
 
226
                             SVN_DAV_ERROR_TAG);
 
227
 
 
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. */
 
233
  return NULL;
 
234
}
 
235
 
 
236
dav_error *dav_svn_checkout(dav_resource *resource,
 
237
                            int auto_checkout,
 
238
                            int is_unreserved, int is_fork_ok,
 
239
                            int create_activity,
 
240
                            apr_array_header_t *activities,
 
241
                            dav_resource **working_resource)
 
242
{
 
243
  const char *txn_name;
 
244
  svn_error_t *serr;
 
245
  apr_status_t apr_err;
 
246
  dav_error *derr;
 
247
  dav_svn_uri_info parse;
 
248
 
 
249
  /* Auto-Versioning Stuff */
 
250
  if (auto_checkout)
 
251
    {
 
252
      dav_resource *res; /* ignored */
 
253
      apr_uuid_t uuid;
 
254
      char uuid_buf[APR_UUID_FORMATTED_LENGTH + 1];
 
255
      void *data;
 
256
      const char *shared_activity, *shared_txn_name = NULL;
 
257
 
 
258
      /* Baselines can be auto-checked-out -- grudgingly -- so we can
 
259
         allow clients to proppatch unversioned rev props.  See issue
 
260
         #916. */
 
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.)  */
 
268
        return NULL;
 
269
 
 
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,
 
276
                                 SVN_DAV_ERROR_TAG);
 
277
 
 
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,
 
284
                                 SVN_DAV_ERROR_TAG);
 
285
 
 
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);
 
290
      if (apr_err)
 
291
        return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
292
                                   HTTP_INTERNAL_SERVER_ERROR,
 
293
                                   "Error fetching pool userdata.",
 
294
                                   resource->pool);
 
295
      shared_activity = data;
 
296
 
 
297
      if (! shared_activity)
 
298
        {
 
299
          /* Build a shared activity for all auto-checked-out resources. */
 
300
          apr_uuid_get(&uuid);
 
301
          apr_uuid_format(uuid_buf, &uuid);
 
302
          shared_activity = apr_pstrdup(resource->info->r->pool, uuid_buf);
 
303
 
 
304
          derr = dav_svn_create_activity(resource->info->repos,
 
305
                                         &shared_txn_name,
 
306
                                         resource->info->r->pool);
 
307
          if (derr) return derr;
 
308
 
 
309
          derr = dav_svn_store_activity(resource->info->repos,
 
310
                                        shared_activity, shared_txn_name);
 
311
          if (derr) return derr;
 
312
 
 
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);
 
317
          if (apr_err)
 
318
            return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
319
                                       HTTP_INTERNAL_SERVER_ERROR,
 
320
                                       "Error setting pool userdata.",
 
321
                                       resource->pool);
 
322
        }
 
323
 
 
324
      if (! shared_txn_name)
 
325
        {                       
 
326
          shared_txn_name = dav_svn_get_txn(resource->info->repos,
 
327
                                            shared_activity);
 
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");
 
331
        }
 
332
 
 
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 */);
 
338
 
 
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
 
342
         activity. */
 
343
      resource->info->auto_checked_out = TRUE;
 
344
        
 
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;
 
353
      
 
354
      serr = svn_fs_txn_root(&resource->info->root.root,
 
355
                             resource->info->root.txn, resource->pool);
 
356
      if (serr != NULL)
 
357
        return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
 
358
                                   "Could not open a (transaction) root "
 
359
                                   "in the repository",
 
360
                                   resource->pool);
 
361
      return NULL;
 
362
    }
 
363
  /* end of Auto-Versioning Stuff */
 
364
 
 
365
  if (resource->type != DAV_RESOURCE_TYPE_VERSION)
 
366
    {
 
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,
 
372
                               SVN_DAV_ERROR_TAG);
 
373
    }
 
374
  if (create_activity)
 
375
    {
 
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,
 
381
                               SVN_DAV_ERROR_TAG);
 
382
    }
 
383
  if (is_unreserved)
 
384
    {
 
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,
 
391
                               SVN_DAV_ERROR_TAG);
 
392
    }
 
393
  if (activities == NULL)
 
394
    {
 
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,
 
399
                               SVN_DAV_ERROR_TAG);
 
400
    }
 
401
  /* assert: nelts > 0.  the below check effectively means > 1. */
 
402
  if (activities->nelts != 1)
 
403
    {
 
404
      return dav_new_error_tag(resource->pool, HTTP_CONFLICT,
 
405
                               SVN_ERR_INCORRECT_PARAMS,
 
406
                               "Only one activity may be specified within the "
 
407
                               "CHECKOUT.",
 
408
                               SVN_DAV_ERROR_NAMESPACE,
 
409
                               SVN_DAV_ERROR_TAG);
 
410
    }
 
411
 
 
412
  serr = dav_svn_simple_parse_uri(&parse, resource,
 
413
                                  APR_ARRAY_IDX(activities, 0, const char *),
 
414
                                  resource->pool);
 
415
  if (serr != NULL)
 
416
    {
 
417
      /* ### is BAD_REQUEST proper? */
 
418
      return dav_svn_convert_err(serr, HTTP_CONFLICT,
 
419
                                 "The activity href could not be parsed "
 
420
                                 "properly.",
 
421
                                 resource->pool);
 
422
    }
 
423
  if (parse.activity_id == NULL)
 
424
    {
 
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,
 
429
                               SVN_DAV_ERROR_TAG);
 
430
    }
 
431
 
 
432
  if ((txn_name = dav_svn_get_txn(resource->info->repos,
 
433
                                  parse.activity_id)) == NULL)
 
434
    {
 
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,
 
439
                               SVN_DAV_ERROR_TAG);
 
440
    }
 
441
 
 
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)
 
445
    {
 
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 */
 
449
 
 
450
      svn_revnum_t youngest;
 
451
 
 
452
      /* make sure the baseline being checked out is the latest */
 
453
      serr = svn_fs_youngest_rev(&youngest, resource->info->repos->fs,
 
454
                                 resource->pool);
 
455
      if (serr != NULL)
 
456
        {
 
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.",
 
462
                                     resource->pool);
 
463
        }
 
464
 
 
465
      if (resource->info->root.rev != youngest)
 
466
        {
 
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,
 
472
                                   SVN_DAV_ERROR_TAG);
 
473
        }
 
474
 
 
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.
 
478
         ###
 
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. */
 
482
    }
 
483
  else
 
484
    {
 
485
      /* standard Version Resource */
 
486
 
 
487
      svn_fs_txn_t *txn;
 
488
      svn_fs_root_t *txn_root;
 
489
      svn_revnum_t txn_created_rev;
 
490
      dav_error *err;
 
491
 
 
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)
 
496
        return err;
 
497
 
 
498
      serr = svn_fs_txn_root(&txn_root, txn, resource->pool);
 
499
      if (serr != NULL)
 
500
        {
 
501
          /* ### correct HTTP error? */
 
502
          return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
 
503
                                     "Could not open the transaction tree.",
 
504
                                     resource->pool);
 
505
        }
 
506
 
 
507
      /* assert: repos_path != NULL (for this type of resource) */
 
508
 
 
509
 
 
510
      /* Out-of-dateness check:  compare the created-rev of the item
 
511
         in the txn against the created-rev of the version resource
 
512
         being changed. */
 
513
      serr = svn_fs_node_created_rev(&txn_created_rev,
 
514
                                     txn_root, resource->info->repos_path,
 
515
                                     resource->pool);
 
516
      if (serr != NULL)
 
517
        {
 
518
          /* ### correct HTTP error? */
 
519
          return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
 
520
                                     "Could not get created-rev of "
 
521
                                     "transaction node.",
 
522
                                     resource->pool);
 
523
        }
 
524
 
 
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
 
529
         resource.)  
 
530
 
 
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.
 
535
 
 
536
         - The client is trying to modify an old (out of date)
 
537
           revision of the resource.  This is, of course,
 
538
           unacceptable!
 
539
 
 
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
 
552
           modify).
 
553
             
 
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. :-)
 
558
      */
 
559
 
 
560
      if (SVN_IS_VALID_REVNUM( txn_created_rev ))
 
561
        {
 
562
          int errorful = 0;
 
563
 
 
564
          if (resource->info->root.rev < txn_created_rev)
 
565
            {
 
566
              /* The item being modified is older than the one in the
 
567
                 transaction.  The client is out of date.  */
 
568
              errorful = 1;
 
569
            }
 
570
          else if (resource->info->root.rev > txn_created_rev)
 
571
            {
 
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;
 
577
 
 
578
              if ((serr = svn_fs_node_id(&txn_noderev_id, txn_root, 
 
579
                                         resource->info->repos_path,
 
580
                                         resource->pool)))
 
581
                {
 
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,
 
587
                     SVN_DAV_ERROR_TAG);
 
588
                  svn_error_clear(serr);
 
589
                  return err;
 
590
                }
 
591
              if ((serr = svn_fs_node_id(&url_noderev_id,
 
592
                                         resource->info->root.root,
 
593
                                         resource->info->repos_path,
 
594
                                         resource->pool)))
 
595
                {
 
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,
 
601
                     SVN_DAV_ERROR_TAG);
 
602
                  svn_error_clear(serr);
 
603
                  return err;
 
604
                }
 
605
              if (svn_fs_compare_ids(url_noderev_id, txn_noderev_id) != 0)
 
606
                {
 
607
                  errorful = 1;
 
608
                }
 
609
            }
 
610
          if (errorful)
 
611
            {
 
612
#if 1
 
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,
 
621
                 SVN_DAV_ERROR_TAG);
 
622
 
 
623
#else
 
624
              /* ### some debugging code */
 
625
              const char *msg;
 
626
              
 
627
              msg = apr_psprintf(resource->pool, 
 
628
                                 "created-rev mismatch: r=%ld, t=%ld",
 
629
                                 resource->info->root.rev, txn_created_rev);
 
630
              
 
631
              return dav_new_error_tag(resource->pool, HTTP_CONFLICT, 
 
632
                                       SVN_ERR_FS_CONFLICT, msg,
 
633
                                       SVN_DAV_ERROR_NAMESPACE,
 
634
                                       SVN_DAV_ERROR_TAG);
 
635
#endif
 
636
            }
 
637
        }
 
638
    }
 
639
  *working_resource = dav_svn_create_working_resource(resource,
 
640
                                                      parse.activity_id,
 
641
                                                      txn_name,
 
642
                                                      FALSE);
 
643
  return NULL;
 
644
}
 
645
 
 
646
static dav_error *dav_svn_uncheckout(dav_resource *resource)
 
647
{
 
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,
 
653
                             SVN_DAV_ERROR_TAG);
 
654
 
 
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,
 
658
                                     resource->pool));
 
659
 
 
660
  /* Attempt to destroy the shared activity. */
 
661
  if (resource->info->root.activity_id)
 
662
    {
 
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);
 
667
    }
 
668
 
 
669
  resource->info->root.txn_name = NULL;
 
670
  resource->info->root.txn = NULL;
 
671
 
 
672
  /* We're no longer checked out. */
 
673
  resource->info->auto_checked_out = FALSE;
 
674
 
 
675
  /* Convert the working resource back into a regular one, in-place. */
 
676
  return dav_svn_working_to_regular_resource(resource);
 
677
}
 
678
 
 
679
 
 
680
/* Closure object for cleanup_deltify. */
 
681
struct cleanup_deltify_baton
 
682
{
 
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;
 
687
 
 
688
  /* The revision number against which to deltify. */
 
689
  svn_revnum_t revision;
 
690
 
 
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. */
 
695
  apr_pool_t *pool;
 
696
};
 
697
 
 
698
 
 
699
/* APR pool cleanup function to deltify against a just-committed
 
700
   revision.  DATA is a 'struct cleanup_deltify_baton *'.
 
701
 
 
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
 
705
   anyway. */
 
706
static apr_status_t cleanup_deltify(void *data)
 
707
{
 
708
  struct cleanup_deltify_baton *cdb = data;
 
709
  svn_repos_t *repos;
 
710
  svn_error_t *err;
 
711
 
 
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);
 
718
 
 
719
  err = svn_repos_open(&repos, cdb->repos_path, subpool);
 
720
  if (err)
 
721
    {
 
722
      ap_log_perror(APLOG_MARK, APLOG_ERR, err->apr_err, cdb->pool,
 
723
                    "cleanup_deltify: error opening repository '%s'",
 
724
                    cdb->repos_path);
 
725
      svn_error_clear(err);
 
726
      goto cleanup;
 
727
    }
 
728
 
 
729
  err = svn_fs_deltify_revision(svn_repos_fs(repos),
 
730
                                cdb->revision, subpool);
 
731
  if (err)
 
732
    {
 
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);
 
738
    }
 
739
 
 
740
 cleanup:
 
741
  svn_pool_destroy(subpool);
 
742
 
 
743
  return APR_SUCCESS;
 
744
}
 
745
 
 
746
 
 
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.
 
750
 
 
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,
 
757
                                           apr_pool_t *pool)
 
758
{
 
759
  struct cleanup_deltify_baton *cdb = apr_palloc(pool, sizeof(*cdb));
 
760
  
 
761
  cdb->repos_path = svn_repos_path(repos, pool);
 
762
  cdb->revision = revision;
 
763
  cdb->pool = pool;
 
764
  
 
765
  apr_pool_cleanup_register(pool, cdb, cleanup_deltify, apr_pool_cleanup_null);
 
766
}
 
767
 
 
768
 
 
769
dav_error *dav_svn_checkin(dav_resource *resource,
 
770
                           int keep_checked_out,
 
771
                           dav_resource **version_resource)
 
772
{
 
773
  svn_error_t *serr;
 
774
  dav_error *err;
 
775
  apr_status_t apr_err;
 
776
  const char *uri;
 
777
  const char *shared_activity;
 
778
  void *data;
 
779
 
 
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.) */
 
787
 
 
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,
 
793
                             SVN_DAV_ERROR_TAG);
 
794
 
 
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);
 
800
  if (apr_err)
 
801
    return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
802
                               HTTP_INTERNAL_SERVER_ERROR,
 
803
                               "Error fetching pool userdata.",
 
804
                               resource->pool);
 
805
  shared_activity = data;
 
806
 
 
807
  /* Try to commit the txn if it exists. */
 
808
  if (shared_activity
 
809
      && (strcmp(shared_activity, resource->info->root.activity_id) == 0))
 
810
    {
 
811
      const char *shared_txn_name;
 
812
      const char *conflict_msg;
 
813
      svn_revnum_t new_rev;
 
814
 
 
815
      shared_txn_name = dav_svn_get_txn(resource->info->repos,
 
816
                                        shared_activity);
 
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");
 
820
 
 
821
      /* Sanity checks */
 
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.");
 
827
     
 
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.");
 
833
      
 
834
      err = set_auto_revprops(resource);
 
835
      if (err)
 
836
        return err;
 
837
      
 
838
      serr = svn_repos_fs_commit_txn(&conflict_msg,
 
839
                                     resource->info->repos->repos,
 
840
                                     &new_rev, 
 
841
                                     resource->info->root.txn,
 
842
                                     resource->pool);
 
843
 
 
844
      if (serr != NULL)
 
845
        {
 
846
          const char *msg;
 
847
          svn_error_clear(svn_fs_abort_txn(resource->info->root.txn,
 
848
                                           resource->pool));
 
849
          
 
850
          if (serr->apr_err == SVN_ERR_FS_CONFLICT)
 
851
            {
 
852
              msg = apr_psprintf(resource->pool,
 
853
                                 "A conflict occurred during the CHECKIN "
 
854
                                 "processing. The problem occurred with  "
 
855
                                 "the \"%s\" resource.",
 
856
                                 conflict_msg);
 
857
            }
 
858
          else
 
859
            msg = "An error occurred while committing the transaction.";
 
860
 
 
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);
 
865
          
 
866
          return dav_svn_convert_err(serr, HTTP_CONFLICT, msg,
 
867
                                     resource->pool);
 
868
        }
 
869
 
 
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);
 
874
            
 
875
      /* Commit was successful, so schedule deltification. */
 
876
      register_deltification_cleanup(resource->info->repos->repos,
 
877
                                     new_rev,
 
878
                                     resource->info->r->connection->pool);
 
879
      
 
880
      /* If caller wants it, return the new VR that was created by
 
881
         the checkin. */
 
882
      if (version_resource)
 
883
        {
 
884
          uri = dav_svn_build_uri(resource->info->repos,
 
885
                                  DAV_SVN_BUILD_URI_VERSION,
 
886
                                  new_rev, resource->info->repos_path,
 
887
                                  0, resource->pool);
 
888
          
 
889
          err = dav_svn_create_version_resource(version_resource, uri,
 
890
                                                resource->pool);
 
891
          if (err)
 
892
            return err;
 
893
        }
 
894
    } /* end of commit stuff */
 
895
  
 
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;
 
901
 
 
902
  /* Convert the working resource back into an regular one. */
 
903
  if (! keep_checked_out)
 
904
    {
 
905
      resource->info->auto_checked_out = FALSE;
 
906
      return dav_svn_working_to_regular_resource(resource);
 
907
    } 
 
908
 
 
909
  return NULL;
 
910
}
 
911
 
 
912
static dav_error *dav_svn_avail_reports(const dav_resource *resource,
 
913
                                        const dav_report_elem **reports)
 
914
{
 
915
  /* ### further restrict to the public space? */
 
916
  if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
 
917
    *reports = NULL;
 
918
    return NULL;
 
919
  }
 
920
 
 
921
  *reports = avail_reports;
 
922
  return NULL;
 
923
}
 
924
 
 
925
static int dav_svn_report_label_header_allowed(const apr_xml_doc *doc)
 
926
{
 
927
  return 0;
 
928
}
 
929
 
 
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,
 
936
                                        ap_filter_t *output)
 
937
{
 
938
  apr_xml_elem *child;
 
939
  int ns;
 
940
  apr_time_t tm = (apr_time_t) -1;
 
941
  svn_revnum_t rev;
 
942
  apr_bucket_brigade *bb;
 
943
  svn_error_t *err;
 
944
  apr_status_t apr_err;
 
945
  dav_error *derr = NULL;
 
946
 
 
947
  /* Find the DAV:creationdate element and get the requested time from it. */
 
948
  ns = dav_svn_find_ns(doc->namespaces, "DAV:");
 
949
  if (ns != -1)
 
950
    {
 
951
      for (child = doc->root->first_child; child != NULL; child = child->next)
 
952
        {
 
953
          if (child->ns != ns || strcmp(child->name, "creationdate") != 0)
 
954
            continue;
 
955
          /* If this fails, we'll notice below, so ignore any error for now. */
 
956
          svn_error_clear
 
957
            (svn_time_from_cstring(&tm, dav_xml_get_cdata(child,
 
958
                                                          resource->pool, 1),
 
959
                                   resource->pool));
 
960
        }
 
961
    }
 
962
 
 
963
  if (tm == (apr_time_t) -1)
 
964
    {
 
965
      return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0,
 
966
                           "The request does not contain a valid "
 
967
                           "'DAV:creationdate' element.");
 
968
    }
 
969
 
 
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)
 
973
    {
 
974
      svn_error_clear (err);
 
975
      return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
 
976
                           "Could not access revision times.");
 
977
    }
 
978
 
 
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);
 
986
  if (apr_err)
 
987
    derr = dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
988
                               HTTP_INTERNAL_SERVER_ERROR,
 
989
                               "Error writing REPORT response.",
 
990
                               resource->pool);
 
991
 
 
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.",
 
998
                               resource->pool);
 
999
 
 
1000
  return derr;
 
1001
}
 
1002
 
 
1003
 
 
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)
 
1009
{
 
1010
  apr_bucket_brigade *bb;
 
1011
  svn_error_t *err;
 
1012
  apr_status_t apr_err;
 
1013
  apr_hash_t *locks;
 
1014
  dav_svn_authz_read_baton arb;
 
1015
  apr_hash_index_t *hi;
 
1016
  apr_pool_t *subpool;
 
1017
 
 
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.");
 
1024
 
 
1025
  arb.r = resource->info->r;
 
1026
  arb.repos = resource->info->repos;
 
1027
 
 
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);      
 
1036
 
 
1037
  bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
 
1038
 
 
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);
 
1044
  if (apr_err)
 
1045
    return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1046
                               HTTP_INTERNAL_SERVER_ERROR,
 
1047
                               "Error writing REPORT response.",
 
1048
                               resource->pool);
 
1049
 
 
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))
 
1053
    {
 
1054
      const void *key;
 
1055
      void *val;
 
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;
 
1061
 
 
1062
      svn_pool_clear(subpool);
 
1063
      apr_hash_this(hi, &key, NULL, &val);
 
1064
      lock = val;
 
1065
      
 
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);
 
1069
 
 
1070
      apr_err = ap_fprintf(output, bb,
 
1071
                           "<S:lock>" DEBUG_CR
 
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);
 
1076
      if (apr_err)
 
1077
        return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1078
                                   HTTP_INTERNAL_SERVER_ERROR,
 
1079
                                   "Error writing REPORT response.",
 
1080
                                   resource->pool);
 
1081
      
 
1082
      if (lock->expiration_date)
 
1083
        {
 
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);
 
1088
          if (apr_err)
 
1089
            return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1090
                                       HTTP_INTERNAL_SERVER_ERROR,
 
1091
                                       "Error writing REPORT response.",
 
1092
                                       resource->pool);
 
1093
        }
 
1094
 
 
1095
      if (svn_xml_is_xml_safe(lock->owner, strlen(lock->owner)))
 
1096
        {
 
1097
          owner_to_send = apr_xml_quote_string(subpool, lock->owner, 1);
 
1098
        }
 
1099
      else
 
1100
        {
 
1101
          svn_string_t owner_string;
 
1102
          const svn_string_t *encoded_owner;
 
1103
 
 
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;
 
1109
        }
 
1110
          
 
1111
      apr_err = ap_fprintf(output, bb,
 
1112
                           "<S:owner %s>%s</S:owner>" DEBUG_CR,
 
1113
                           owner_base64 ? "encoding=\"base64\"" : "",
 
1114
                           owner_to_send);
 
1115
      if (apr_err)
 
1116
        return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1117
                                   HTTP_INTERNAL_SERVER_ERROR,
 
1118
                                   "Error writing REPORT response.",
 
1119
                                   resource->pool);          
 
1120
 
 
1121
      if (lock->comment)
 
1122
        {
 
1123
          if (svn_xml_is_xml_safe(lock->comment, strlen(lock->comment)))
 
1124
            {
 
1125
              comment_to_send = apr_xml_quote_string(subpool,
 
1126
                                                     lock->comment, 1);
 
1127
            }
 
1128
          else
 
1129
            {
 
1130
              svn_string_t comment_string;
 
1131
              const svn_string_t *encoded_comment;
 
1132
              
 
1133
              comment_string.data = lock->comment;
 
1134
              comment_string.len = strlen(lock->comment);         
 
1135
              encoded_comment = svn_base64_encode_string(&comment_string,
 
1136
                                                         subpool);
 
1137
              comment_to_send = encoded_comment->data;
 
1138
              comment_base64 = TRUE;
 
1139
            }
 
1140
 
 
1141
          apr_err = ap_fprintf(output, bb,
 
1142
                               "<S:comment %s>%s</S:comment>" DEBUG_CR,
 
1143
                               comment_base64 ? "encoding=\"base64\"" : "",
 
1144
                               comment_to_send);
 
1145
          if (apr_err)
 
1146
            return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1147
                                       HTTP_INTERNAL_SERVER_ERROR,
 
1148
                                       "Error writing REPORT response.",
 
1149
                                       resource->pool);
 
1150
        }
 
1151
          
 
1152
      apr_err = ap_fprintf(output, bb, "</S:lock>" DEBUG_CR);
 
1153
      if (apr_err)
 
1154
        return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1155
                                   HTTP_INTERNAL_SERVER_ERROR,
 
1156
                                   "Error writing REPORT response.",
 
1157
                                   resource->pool);
 
1158
    } /* end of hash loop */
 
1159
  svn_pool_destroy(subpool);
 
1160
 
 
1161
  /* finish the report */
 
1162
  apr_err = ap_fprintf(output, bb, "</S:get-locks-report>" DEBUG_CR);
 
1163
  if (apr_err)
 
1164
    return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1165
                               HTTP_INTERNAL_SERVER_ERROR,
 
1166
                               "Error writing REPORT response.",
 
1167
                               resource->pool);
 
1168
 
 
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.",
 
1175
                               resource->pool);
 
1176
 
 
1177
  return NULL;
 
1178
}
 
1179
 
 
1180
 
 
1181
 
 
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)
 
1186
{
 
1187
  apr_hash_index_t *hi;
 
1188
  apr_pool_t *pool;
 
1189
  apr_status_t apr_err;
 
1190
 
 
1191
  pool = resource->pool;
 
1192
 
 
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);
 
1196
  if (apr_err)
 
1197
    return apr_err;
 
1198
 
 
1199
  for (hi = apr_hash_first(pool, fs_locations); hi; hi = apr_hash_next (hi))
 
1200
    {
 
1201
      const void *key;
 
1202
      void *value;
 
1203
      const char *path_quoted;
 
1204
 
 
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);
 
1210
      if (apr_err)
 
1211
        return apr_err;
 
1212
    }
 
1213
  return ap_fprintf(output, bb, "</S:get-locations-report>" DEBUG_CR);
 
1214
}
 
1215
 
 
1216
dav_error *dav_svn__get_locations_report(const dav_resource *resource,
 
1217
                                         const apr_xml_doc *doc,
 
1218
                                         ap_filter_t *output)
 
1219
{
 
1220
  svn_error_t *serr;
 
1221
  dav_error *derr = NULL;
 
1222
  apr_status_t apr_err;
 
1223
  apr_bucket_brigade *bb;
 
1224
  dav_svn_authz_read_baton arb;
 
1225
 
 
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;
 
1231
 
 
1232
  /* XML Parsing Variables */
 
1233
  int ns;
 
1234
  apr_xml_elem *child;
 
1235
 
 
1236
  apr_hash_t *fs_locations;
 
1237
 
 
1238
  location_revisions = apr_array_make(resource->pool, 0,
 
1239
                                      sizeof(svn_revnum_t));
 
1240
 
 
1241
  /* Sanity check. */
 
1242
  ns = dav_svn_find_ns(doc->namespaces, SVN_XML_NAMESPACE);
 
1243
  if (ns == -1)
 
1244
    {
 
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,
 
1250
                               SVN_DAV_ERROR_TAG);
 
1251
    }
 
1252
 
 
1253
  /* Gather the parameters. */
 
1254
  for (child = doc->root->first_child; child != NULL; child = child->next)
 
1255
    {
 
1256
      /* If this element isn't one of ours, then skip it. */
 
1257
      if (child->ns != ns)
 
1258
        continue;
 
1259
 
 
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)
 
1264
        {
 
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;
 
1268
        }
 
1269
      else if (strcmp(child->name, "path") == 0)
 
1270
        {
 
1271
          relative_path = dav_xml_get_cdata(child, resource->pool, 0);
 
1272
          if ((derr = dav_svn__test_canonical(relative_path, resource->pool)))
 
1273
            return derr;
 
1274
        }
 
1275
    }
 
1276
 
 
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)))
 
1280
    {
 
1281
      return dav_new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
 
1282
                               "Not all parameters passed.",
 
1283
                               SVN_DAV_ERROR_NAMESPACE,
 
1284
                               SVN_DAV_ERROR_TAG);       
 
1285
    }
 
1286
 
 
1287
  /* Append the relative path to the base FS path to get an absolute
 
1288
     repository path. */
 
1289
  abs_path = svn_path_join(resource->info->repos_path, relative_path,
 
1290
                           resource->pool);
 
1291
 
 
1292
  /* Build an authz read baton */
 
1293
  arb.r = resource->info->r;
 
1294
  arb.repos = resource->info->repos;
 
1295
 
 
1296
  serr = svn_repos_trace_node_locations(resource->info->repos->fs,
 
1297
                                        &fs_locations, abs_path, peg_revision,
 
1298
                                        location_revisions,
 
1299
                                        dav_svn_authz_read, &arb,
 
1300
                                        resource->pool);
 
1301
 
 
1302
  if (serr)
 
1303
    {
 
1304
      return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
 
1305
                                 serr->message, resource->pool);
 
1306
    }
 
1307
 
 
1308
  bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
 
1309
 
 
1310
  apr_err = send_get_locations_report(output, bb, resource, fs_locations);
 
1311
 
 
1312
  if (apr_err)
 
1313
    derr = dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1314
                               HTTP_INTERNAL_SERVER_ERROR,
 
1315
                               "Error writing REPORT response.",
 
1316
                               resource->pool);
 
1317
 
 
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.",
 
1324
                               resource->pool);
 
1325
 
 
1326
  return derr;
 
1327
}
 
1328
 
 
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)
 
1333
{
 
1334
  int ns = dav_svn_find_ns(doc->namespaces, SVN_XML_NAMESPACE);
 
1335
 
 
1336
  if (doc->root->ns == ns)
 
1337
    {
 
1338
      /* ### note that these report names should have symbols... */
 
1339
 
 
1340
      if (strcmp(doc->root->name, "update-report") == 0)
 
1341
        {
 
1342
          return dav_svn__update_report(resource, doc, output);
 
1343
        }
 
1344
      else if (strcmp(doc->root->name, "log-report") == 0)
 
1345
        {
 
1346
          return dav_svn__log_report(resource, doc, output);
 
1347
        }
 
1348
      else if (strcmp(doc->root->name, "dated-rev-report") == 0)
 
1349
        {
 
1350
          return dav_svn__drev_report(resource, doc, output);
 
1351
        }
 
1352
      else if (strcmp(doc->root->name, "get-locations") == 0)
 
1353
        {
 
1354
          return dav_svn__get_locations_report(resource, doc, output);
 
1355
        }
 
1356
      else if (strcmp(doc->root->name, "file-revs-report") == 0)
 
1357
        {
 
1358
          return dav_svn__file_revs_report(resource, doc, output);
 
1359
        }
 
1360
      else if (strcmp(doc->root->name, "get-locks-report") == 0)
 
1361
        {
 
1362
          return dav_svn__get_locks_report(resource, doc, output);
 
1363
        }
 
1364
 
 
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.
 
1367
       */
 
1368
    }
 
1369
 
 
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,
 
1375
                           SVN_DAV_ERROR_TAG);
 
1376
}
 
1377
 
 
1378
static int dav_svn_can_be_activity(const dav_resource *resource)
 
1379
{
 
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.
 
1383
   */
 
1384
  return (resource->info->auto_checked_out == TRUE ||
 
1385
          (resource->type == DAV_RESOURCE_TYPE_ACTIVITY &&
 
1386
           !resource->exists));
 
1387
}
 
1388
 
 
1389
static dav_error *dav_svn_make_activity(dav_resource *resource)
 
1390
{
 
1391
  const char *activity_id = resource->info->root.activity_id;
 
1392
  const char *txn_name;
 
1393
  dav_error *err;
 
1394
 
 
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,
 
1403
                             SVN_DAV_ERROR_TAG);
 
1404
   
 
1405
  err = dav_svn_create_activity(resource->info->repos, &txn_name,
 
1406
                                resource->pool);
 
1407
  if (err != NULL)
 
1408
    return err;
 
1409
 
 
1410
  err = dav_svn_store_activity(resource->info->repos, activity_id, txn_name);
 
1411
  if (err != NULL)
 
1412
    return err;
 
1413
 
 
1414
  /* everything is happy. update the resource */
 
1415
  resource->info->root.txn_name = txn_name;
 
1416
  resource->exists = 1;
 
1417
  return NULL;
 
1418
}
 
1419
 
 
1420
 
 
1421
 
 
1422
dav_error *dav_svn__build_lock_hash(apr_hash_t **locks,
 
1423
                                    request_rec *r,
 
1424
                                    const char *path_prefix,
 
1425
                                    apr_pool_t *pool)
 
1426
{
 
1427
  apr_status_t apr_err;
 
1428
  dav_error *derr;
 
1429
  void *data = NULL;
 
1430
  apr_xml_doc *doc = NULL;
 
1431
  apr_xml_elem *child, *lockchild;
 
1432
  int ns;
 
1433
  apr_hash_t *hash = apr_hash_make(pool);
 
1434
  
 
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
 
1437
     input filter. */
 
1438
  apr_err = apr_pool_userdata_get(&data, "svn-request-body", r->pool);
 
1439
  if (apr_err)
 
1440
    return dav_svn_convert_err(svn_error_create(apr_err, 0, NULL),
 
1441
                               HTTP_INTERNAL_SERVER_ERROR,
 
1442
                               "Error fetching pool userdata.",
 
1443
                               pool);
 
1444
  doc = data;
 
1445
  if (! doc)
 
1446
    {
 
1447
      *locks = hash;
 
1448
      return SVN_NO_ERROR;
 
1449
    }
 
1450
  
 
1451
  /* Sanity check. */
 
1452
  ns = dav_svn_find_ns(doc->namespaces, SVN_XML_NAMESPACE);
 
1453
  if (ns == -1)
 
1454
    {
 
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. */
 
1458
      *locks = hash;
 
1459
      return SVN_NO_ERROR;
 
1460
    }
 
1461
 
 
1462
  if ((doc->root->ns == ns) 
 
1463
      && (strcmp(doc->root->name, "lock-token-list") == 0))
 
1464
    {
 
1465
      child = doc->root;
 
1466
    }
 
1467
  else
 
1468
    {
 
1469
      /* Search doc's children until we find the <lock-token-list>. */
 
1470
      for (child = doc->root->first_child; child != NULL; child = child->next)
 
1471
        {
 
1472
          /* if this element isn't one of ours, then skip it */
 
1473
          if (child->ns != ns)
 
1474
            continue;
 
1475
          
 
1476
          if (strcmp(child->name, "lock-token-list") == 0)
 
1477
            break;
 
1478
        }
 
1479
    }
 
1480
 
 
1481
  /* Then look for N different <lock> structures within. */
 
1482
  for (lockchild = child->first_child; lockchild != NULL;
 
1483
       lockchild = lockchild->next)
 
1484
    {
 
1485
      const char *lockpath = NULL, *locktoken = NULL;
 
1486
      apr_xml_elem *lfchild;
 
1487
 
 
1488
      if (strcmp(lockchild->name, "lock") != 0)
 
1489
        continue;
 
1490
 
 
1491
      for (lfchild = lockchild->first_child; lfchild != NULL;
 
1492
           lfchild = lfchild->next)
 
1493
        {
 
1494
          if (strcmp(lfchild->name, "lock-path") == 0)
 
1495
            {
 
1496
              const char *cdata = dav_xml_get_cdata(lfchild, pool, 0);
 
1497
              if ((derr = dav_svn__test_canonical(cdata, pool)))
 
1498
                return derr;
 
1499
                  
 
1500
              /* Create an absolute fs-path */
 
1501
              lockpath = svn_path_join(path_prefix, cdata, pool);
 
1502
              if (lockpath && locktoken)
 
1503
                {
 
1504
                  apr_hash_set(hash, lockpath, APR_HASH_KEY_STRING, locktoken);
 
1505
                  lockpath = NULL;
 
1506
                  locktoken = NULL;
 
1507
                }
 
1508
            }
 
1509
          else if (strcmp(lfchild->name, "lock-token") == 0)
 
1510
            {
 
1511
              locktoken = dav_xml_get_cdata(lfchild, pool, 1);
 
1512
              if (lockpath && *locktoken)
 
1513
                {
 
1514
                  apr_hash_set(hash, lockpath, APR_HASH_KEY_STRING, locktoken);
 
1515
                  lockpath = NULL;
 
1516
                  locktoken = NULL;
 
1517
                }
 
1518
            }
 
1519
        }
 
1520
    }
 
1521
  
 
1522
  *locks = hash;
 
1523
  return SVN_NO_ERROR;
 
1524
}
 
1525
 
 
1526
 
 
1527
 
 
1528
dav_error *dav_svn__push_locks(dav_resource *resource,
 
1529
                               apr_hash_t *locks,
 
1530
                               apr_pool_t *pool)
 
1531
{
 
1532
  svn_fs_access_t *fsaccess;
 
1533
  apr_hash_index_t *hi;
 
1534
  svn_error_t *serr;
 
1535
  
 
1536
  serr = svn_fs_get_access (&fsaccess, resource->info->repos->fs);
 
1537
  if (serr)
 
1538
    {
 
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,
 
1544
                                                      NULL, new_msg);
 
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);
 
1550
    }
 
1551
  
 
1552
  for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
 
1553
    {
 
1554
      const char *token;
 
1555
      void *val;
 
1556
      apr_hash_this(hi, NULL, NULL, &val);
 
1557
      token = val;
 
1558
      
 
1559
      serr = svn_fs_access_add_lock_token (fsaccess, token);
 
1560
      if (serr)
 
1561
        return dav_svn_convert_err (serr, HTTP_INTERNAL_SERVER_ERROR,
 
1562
                                    "Error pushing token into filesystem.",
 
1563
                                    pool);
 
1564
    }
 
1565
 
 
1566
  return NULL;
 
1567
}
 
1568
 
 
1569
 
 
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
 
1572
   work.*/
 
1573
static svn_error_t *release_locks(apr_hash_t *locks,
 
1574
                                  svn_repos_t *repos,
 
1575
                                  request_rec *r,
 
1576
                                  apr_pool_t *pool)
 
1577
{
 
1578
  apr_hash_index_t *hi;
 
1579
  const void *key;
 
1580
  void *val;
 
1581
  apr_pool_t *subpool = svn_pool_create(pool);
 
1582
  svn_error_t *err;
 
1583
 
 
1584
  for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
 
1585
    {
 
1586
      svn_pool_clear(subpool);
 
1587
      apr_hash_this(hi, &key, NULL, &val);
 
1588
 
 
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);
 
1593
 
 
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);
 
1597
 
 
1598
      svn_error_clear(err);
 
1599
    }
 
1600
 
 
1601
  svn_pool_destroy(subpool);
 
1602
 
 
1603
  return SVN_NO_ERROR;
 
1604
}
 
1605
 
 
1606
 
 
1607
 
 
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)
 
1612
{
 
1613
  apr_pool_t *pool;
 
1614
  dav_error *err;
 
1615
  svn_fs_txn_t *txn;
 
1616
  const char *conflict;
 
1617
  svn_error_t *serr;
 
1618
  svn_revnum_t new_rev;
 
1619
  apr_hash_t *locks;
 
1620
  svn_boolean_t disable_merge_response = FALSE;
 
1621
 
 
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;
 
1625
 
 
1626
  /* ### what to verify on the target? */
 
1627
 
 
1628
  /* ### anything else for the source? */
 
1629
  if (source->type != DAV_RESOURCE_TYPE_ACTIVITY)
 
1630
    {
 
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,
 
1636
                               SVN_DAV_ERROR_TAG);
 
1637
    }
 
1638
 
 
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. */
 
1644
 
 
1645
  err = dav_svn__build_lock_hash(&locks, target->info->r,
 
1646
                                 target->info->repos_path,
 
1647
                                 pool);
 
1648
  if (err != NULL)
 
1649
    return err;
 
1650
 
 
1651
  if (apr_hash_count(locks))
 
1652
    {
 
1653
      err = dav_svn__push_locks(source, locks, pool);
 
1654
      if (err != NULL)
 
1655
        return err;
 
1656
    }
 
1657
 
 
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. */
 
1663
 
 
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)
 
1667
    return err;
 
1668
 
 
1669
  /* all righty... commit the bugger. */
 
1670
  serr = svn_repos_fs_commit_txn(&conflict, source->info->repos->repos,
 
1671
                                 &new_rev, txn, pool);
 
1672
 
 
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))
 
1679
    {
 
1680
      const char *msg;
 
1681
      svn_error_clear(svn_fs_abort_txn(txn, pool));
 
1682
 
 
1683
      if (serr->apr_err == SVN_ERR_FS_CONFLICT)
 
1684
        {
 
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 "
 
1689
                             "\"%s\" resource.",
 
1690
                             conflict);
 
1691
        }
 
1692
      else
 
1693
        msg = "An error occurred while committing the transaction.";
 
1694
 
 
1695
      return dav_svn_convert_err(serr, HTTP_CONFLICT, msg, pool);
 
1696
    }
 
1697
  else if (serr)
 
1698
    svn_error_clear(serr);
 
1699
 
 
1700
  /* Commit was successful, so schedule deltification. */
 
1701
  register_deltification_cleanup(source->info->repos->repos, new_rev,
 
1702
                                 source->info->r->connection->pool);
 
1703
 
 
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
 
1707
     transaction. */
 
1708
  err = dav_svn_store_activity(source->info->repos,
 
1709
                               source->info->root.activity_id, "");
 
1710
  if (err != NULL)
 
1711
    return err;
 
1712
 
 
1713
  /* Check the dav_resource->info area for information about the
 
1714
     special X-SVN-Options: header that may have come in the http
 
1715
     request. */
 
1716
  if (source->info->svn_client_options != NULL)
 
1717
    {
 
1718
      /* The client might want us to release all locks sent in the
 
1719
         MERGE request. */
 
1720
      if ((NULL != (ap_strstr_c(source->info->svn_client_options,
 
1721
                                SVN_DAV_OPTION_RELEASE_LOCKS)))
 
1722
          && apr_hash_count(locks))
 
1723
        {
 
1724
          serr = release_locks(locks, source->info->repos->repos, 
 
1725
                               source->info->r, pool);
 
1726
          if (serr != NULL)
 
1727
            return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
 
1728
                                       "Error releasing locks", pool);
 
1729
        }
 
1730
 
 
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;
 
1735
    }
 
1736
 
 
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);
 
1740
}
 
1741
 
 
1742
const dav_hooks_vsn dav_svn_hooks_vsn = {
 
1743
  dav_svn_get_vsn_options,
 
1744
  dav_svn_get_option,
 
1745
  dav_svn_versionable,
 
1746
  dav_svn_auto_versionable,
 
1747
  dav_svn_vsn_control,
 
1748
  dav_svn_checkout,
 
1749
  dav_svn_uncheckout,
 
1750
  dav_svn_checkin,
 
1751
  dav_svn_avail_reports,
 
1752
  dav_svn_report_label_header_allowed,
 
1753
  dav_svn_deliver_report,
 
1754
  NULL,                 /* update */
 
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,
 
1761
  dav_svn_merge,
 
1762
};