In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_neon / commit.c
blob9bb82c21c170d75097948b92b2594769bd9789fa
1 /*
2 * commit.c : routines for committing changes to the server
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <apr_pools.h>
22 #include <apr_hash.h>
23 #include <apr_uuid.h>
25 #define APR_WANT_STDIO
26 #define APR_WANT_STRFUNC
27 #include <apr_want.h>
29 #include <assert.h>
31 #include "svn_pools.h"
32 #include "svn_error.h"
33 #include "svn_delta.h"
34 #include "svn_io.h"
35 #include "svn_ra.h"
36 #include "../libsvn_ra/ra_loader.h"
37 #include "svn_path.h"
38 #include "svn_xml.h"
39 #include "svn_dav.h"
40 #include "svn_props.h"
42 #include "svn_private_config.h"
44 #include "ra_neon.h"
48 ** version_rsrc_t: identify the relevant pieces of a resource on the server
50 ** REVISION is the resource's revision, or SVN_INVALID_REVNUM if it is
51 ** new or is the HEAD.
53 ** URL refers to the public/viewable/original resource.
54 ** VSN_URL refers to the version resource that we stored locally
55 ** WR_URL refers to a working resource for this resource
57 ** Note that VSN_URL is NULL if this resource has just been added, and
58 ** WR_URL can be NULL if the resource has not (yet) been checked out.
60 ** LOCAL_PATH is relative to the root of the commit. It will be used
61 ** for the get_func, push_func, and close_func callbacks.
63 ** NAME is the name of the resource.
65 typedef struct
67 svn_revnum_t revision;
68 const char *url;
69 const char *vsn_url;
70 const char *wr_url;
71 const char *local_path;
72 const char *name;
73 apr_pool_t *pool; /* pool in which this resource is allocated. */
75 } version_rsrc_t;
78 typedef struct
80 svn_ra_neon__session_t *ras;
81 const char *activity_url;
83 apr_hash_t *valid_targets;
85 svn_ra_get_wc_prop_func_t get_func;
86 svn_ra_push_wc_prop_func_t push_func;
87 void *cb_baton;
89 svn_boolean_t disable_merge_response;
91 /* The (potential) author of this commit. */
92 const char *user;
94 /* The commit callback and baton */
95 svn_commit_callback2_t callback;
96 void *callback_baton;
98 /* The hash of lock-tokens owned by the working copy. */
99 apr_hash_t *tokens;
101 /* Whether or not to keep the locks after commit is done. */
102 svn_boolean_t keep_locks;
104 } commit_ctx_t;
106 typedef struct
108 apr_file_t *tmpfile; /* may be NULL for content-less file */
109 svn_stringbuf_t *fname; /* may be NULL for content-less file */
110 const char *base_checksum; /* hex md5 of base text; may be null */
111 apr_off_t progress;
112 svn_ra_neon__session_t *ras;
113 apr_pool_t *pool;
114 } put_baton_t;
116 typedef struct
118 commit_ctx_t *cc;
119 version_rsrc_t *rsrc;
120 apr_hash_t *prop_changes; /* name/values pairs of new/changed properties. */
121 apr_array_header_t *prop_deletes; /* names of properties to delete. */
122 svn_boolean_t created; /* set if this is an add rather than an update */
123 svn_boolean_t copied; /* set if this object was copied */
124 apr_pool_t *pool; /* the pool from open_foo() / add_foo() */
125 put_baton_t *put_baton; /* baton for this file's PUT request */
126 const char *token; /* file's lock token, if available */
127 } resource_baton_t;
129 /* this property will be fetched from the server when we don't find it
130 cached in the WC property store. */
131 static const ne_propname fetch_props[] =
133 { "DAV:", "checked-in" },
134 { NULL }
137 static const ne_propname log_message_prop = { SVN_DAV_PROP_NS_SVN, "log" };
139 /* perform a deep copy of BASE into POOL, and return the result. */
140 static version_rsrc_t * dup_resource(version_rsrc_t *base, apr_pool_t *pool)
142 version_rsrc_t *rsrc = apr_pcalloc(pool, sizeof(*rsrc));
143 rsrc->pool = pool;
144 rsrc->revision = base->revision;
145 rsrc->url = base->url ?
146 apr_pstrdup(pool, base->url) : NULL;
147 rsrc->vsn_url = base->vsn_url ?
148 apr_pstrdup(pool, base->vsn_url) : NULL;
149 rsrc->wr_url = base->wr_url ?
150 apr_pstrdup(pool, base->wr_url) : NULL;
151 rsrc->local_path = base->local_path ?
152 apr_pstrdup(pool, base->local_path) : NULL;
153 return rsrc;
156 static svn_error_t * delete_activity(void *edit_baton,
157 apr_pool_t *pool)
159 commit_ctx_t *cc = edit_baton;
160 return svn_ra_neon__simple_request(NULL, cc->ras, "DELETE",
161 cc->activity_url, NULL, NULL,
162 204 /* No Content */,
163 404 /* Not Found */, pool);
167 /* Get the version resource URL for RSRC, storing it in
168 RSRC->vsn_url. Use POOL for all temporary allocations. */
169 static svn_error_t * get_version_url(commit_ctx_t *cc,
170 const version_rsrc_t *parent,
171 version_rsrc_t *rsrc,
172 svn_boolean_t force,
173 apr_pool_t *pool)
175 svn_ra_neon__resource_t *propres;
176 const char *url;
177 const svn_string_t *url_str;
179 if (!force)
181 if (cc->get_func != NULL)
183 const svn_string_t *vsn_url_value;
185 SVN_ERR((*cc->get_func)(cc->cb_baton,
186 rsrc->local_path,
187 SVN_RA_NEON__LP_VSN_URL,
188 &vsn_url_value,
189 pool));
190 if (vsn_url_value != NULL)
192 rsrc->vsn_url = apr_pstrdup(rsrc->pool, vsn_url_value->data);
193 return SVN_NO_ERROR;
197 /* If we know the version resource URL of the parent and it is
198 the same revision as RSRC, use that as a base to calculate
199 the version resource URL of RSRC. */
200 if (parent && parent->vsn_url && parent->revision == rsrc->revision)
202 rsrc->vsn_url = svn_path_url_add_component(parent->vsn_url,
203 rsrc->name,
204 rsrc->pool);
205 return SVN_NO_ERROR;
208 /* whoops. it wasn't there. go grab it from the server. */
211 if (rsrc->revision == SVN_INVALID_REVNUM)
213 /* We aren't trying to get a specific version -- use the HEAD. We
214 fetch the version URL from the public URL. */
215 url = rsrc->url;
217 else
219 svn_string_t bc_url;
220 svn_string_t bc_relative;
222 /* The version URL comes from a resource in the Baseline Collection. */
223 SVN_ERR(svn_ra_neon__get_baseline_info(NULL,
224 &bc_url, &bc_relative, NULL,
225 cc->ras,
226 rsrc->url,
227 rsrc->revision,
228 pool));
230 url = svn_path_url_add_component(bc_url.data, bc_relative.data, pool);
233 /* Get the DAV:checked-in property, which contains the URL of the
234 Version Resource */
235 SVN_ERR(svn_ra_neon__get_props_resource(&propres, cc->ras, url,
236 NULL, fetch_props, pool));
237 url_str = apr_hash_get(propres->propset,
238 SVN_RA_NEON__PROP_CHECKED_IN,
239 APR_HASH_KEY_STRING);
240 if (url_str == NULL)
242 /* ### need a proper SVN_ERR here */
243 return svn_error_create(APR_EGENERAL, NULL,
244 _("Could not fetch the Version Resource URL "
245 "(needed during an import or when it is "
246 "missing from the local, cached props)"));
249 /* ensure we get the proper lifetime for this URL since it is going into
250 a resource object. */
251 rsrc->vsn_url = apr_pstrdup(rsrc->pool, url_str->data);
253 if (cc->push_func != NULL)
255 /* Now we can store the new version-url. */
256 SVN_ERR((*cc->push_func)(cc->cb_baton,
257 rsrc->local_path,
258 SVN_RA_NEON__LP_VSN_URL,
259 url_str,
260 pool));
263 return SVN_NO_ERROR;
266 /* When FORCE is true, then we force a query to the server, ignoring any
267 cached property. */
268 static svn_error_t * get_activity_collection(commit_ctx_t *cc,
269 const svn_string_t **collection,
270 svn_boolean_t force,
271 apr_pool_t *pool)
273 if (!force && cc->get_func != NULL)
275 /* with a get_func, we can just ask for the activity URL from the
276 property store. */
278 /* get the URL where we should create activities */
279 SVN_ERR((*cc->get_func)(cc->cb_baton,
281 SVN_RA_NEON__LP_ACTIVITY_COLL,
282 collection,
283 pool));
285 if (*collection != NULL)
287 /* the property was there. return it. */
288 return SVN_NO_ERROR;
291 /* property not found for some reason. get it from the server. */
294 /* use our utility function to fetch the activity URL */
295 SVN_ERR(svn_ra_neon__get_activity_collection(collection,
296 cc->ras,
297 cc->ras->root.path,
298 pool));
300 if (cc->push_func != NULL)
302 /* save the (new) activity collection URL into the directory */
303 SVN_ERR((*cc->push_func)(cc->cb_baton,
305 SVN_RA_NEON__LP_ACTIVITY_COLL,
306 *collection,
307 pool));
310 return SVN_NO_ERROR;
313 static svn_error_t * create_activity(commit_ctx_t *cc,
314 apr_pool_t *pool)
316 const svn_string_t * activity_collection;
317 const char *uuid_buf = svn_uuid_generate(pool);
318 int code;
319 const char *url;
321 /* get the URL where we'll create activities, construct the URL for
322 the activity, and create the activity. The URL for our activity
323 will be ACTIVITY_COLL/UUID */
324 SVN_ERR(get_activity_collection(cc, &activity_collection, FALSE, pool));
325 url = svn_path_url_add_component(activity_collection->data,
326 uuid_buf, pool);
327 SVN_ERR(svn_ra_neon__simple_request(&code, cc->ras,
328 "MKACTIVITY", url, NULL, NULL,
329 201 /* Created */,
330 404 /* Not Found */, pool));
332 /* if we get a 404, then it generally means that the cached activity
333 collection no longer exists. Retry the sequence, but force a query
334 to the server for the activity collection. */
335 if (code == 404)
337 SVN_ERR(get_activity_collection(cc, &activity_collection, TRUE, pool));
338 url = svn_path_url_add_component(activity_collection->data,
339 uuid_buf, pool);
340 SVN_ERR(svn_ra_neon__simple_request(&code, cc->ras,
341 "MKACTIVITY", url, NULL, NULL,
342 201, 0, pool));
345 cc->activity_url = url;
347 return SVN_NO_ERROR;
350 /* Add a child resource. POOL should be as "temporary" as possible,
351 but probably not as far as requiring a new temp pool. */
352 static svn_error_t * add_child(version_rsrc_t **child,
353 commit_ctx_t *cc,
354 const version_rsrc_t *parent,
355 const char *name,
356 int created,
357 svn_revnum_t revision,
358 apr_pool_t *pool)
360 version_rsrc_t *rsrc;
362 /* ### todo: This from Yoshiki Hayashi <yoshiki@xemacs.org>:
364 Probably created flag in add_child can be removed because
365 revision is valid => created is false
366 revision is invalid => created is true
369 rsrc = apr_pcalloc(pool, sizeof(*rsrc));
370 rsrc->pool = pool;
371 rsrc->revision = revision;
372 rsrc->name = name;
373 rsrc->url = svn_path_url_add_component(parent->url, name, pool);
374 rsrc->local_path = svn_path_join(parent->local_path, name, pool);
376 /* Case 1: the resource is truly "new". Either it was added as a
377 completely new object, or implicitly created via a COPY. Either
378 way, it has no VR URL anywhere. However, we *can* derive its WR
379 URL by the rules of deltaV: "copy structure is preserved below
380 the WR you COPY to." */
381 if (created || (parent->vsn_url == NULL))
382 rsrc->wr_url = svn_path_url_add_component(parent->wr_url, name, pool);
384 /* Case 2: the resource is already under version-control somewhere.
385 This means it has a VR URL already, and the WR URL won't exist
386 until it's "checked out". */
387 else
388 SVN_ERR(get_version_url(cc, parent, rsrc, FALSE, pool));
390 *child = rsrc;
391 return SVN_NO_ERROR;
395 static svn_error_t * do_checkout(commit_ctx_t *cc,
396 const char *vsn_url,
397 svn_boolean_t allow_404,
398 const char *token,
399 int *code,
400 const char **locn,
401 apr_pool_t *pool)
403 svn_ra_neon__request_t *request;
404 const char *body;
405 apr_hash_t *extra_headers = NULL;
407 /* assert: vsn_url != NULL */
409 /* ### send a CHECKOUT resource on vsn_url; include cc->activity_url;
410 ### place result into res->wr_url and return it */
412 /* create/prep the request */
413 request =
414 svn_ra_neon__request_create(cc->ras, "CHECKOUT", vsn_url, pool);
416 /* ### store this into cc to avoid pool growth */
417 body = apr_psprintf(request->pool,
418 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
419 "<D:checkout xmlns:D=\"DAV:\">"
420 "<D:activity-set>"
421 "<D:href>%s</D:href>"
422 "</D:activity-set></D:checkout>", cc->activity_url);
424 if (token)
426 extra_headers = apr_hash_make(request->pool);
427 svn_ra_neon__set_header(extra_headers, "If",
428 apr_psprintf(request->pool, "(<%s>)", token));
431 /* run the request and get the resulting status code (and svn_error_t) */
432 SVN_ERR(svn_ra_neon__request_dispatch(code, request, extra_headers, body,
433 201 /* Created */,
434 allow_404 ? 404 /* Not Found */ : 0,
435 pool));
437 if (allow_404 && *code == 404 && request->err)
439 svn_error_clear(request->err);
440 request->err = SVN_NO_ERROR;
443 *locn = svn_ra_neon__request_get_location(request, pool);
444 svn_ra_neon__request_destroy(request);
446 return SVN_NO_ERROR;
450 static svn_error_t * checkout_resource(commit_ctx_t *cc,
451 version_rsrc_t *rsrc,
452 svn_boolean_t allow_404,
453 const char *token,
454 apr_pool_t *pool)
456 int code;
457 const char *locn = NULL;
458 ne_uri parse;
459 svn_error_t *err;
461 if (rsrc->wr_url != NULL)
463 /* already checked out! */
464 return NULL;
467 /* check out the Version Resource */
468 err = do_checkout(cc, rsrc->vsn_url, allow_404, token, &code, &locn, pool);
470 /* possibly run the request again, with a re-fetched Version Resource */
471 if (err == NULL && allow_404 && code == 404)
473 locn = NULL;
475 /* re-fetch, forcing a query to the server */
476 SVN_ERR(get_version_url(cc, NULL, rsrc, TRUE, pool));
478 /* do it again, but don't allow a 404 this time */
479 err = do_checkout(cc, rsrc->vsn_url, FALSE, token, &code, &locn, pool);
482 /* special-case when conflicts occur */
483 if (err)
485 /* ### TODO: it's a shame we don't have the full path from the
486 ### root of the drive here, nor the type of the resource.
487 ### Because we lack this information, the error message is
488 ### overly generic. See issue #2740. */
489 if (err->apr_err == SVN_ERR_FS_CONFLICT)
490 return svn_error_createf
491 (err->apr_err, err,
492 _("File or directory '%s' is out of date; try updating"),
493 svn_path_local_style(rsrc->local_path, pool));
494 return err;
497 /* we got the header, right? */
498 if (locn == NULL)
499 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
500 _("The CHECKOUT response did not contain a "
501 "'Location:' header"));
503 /* The location is an absolute URI. We want just the path portion. */
504 /* ### what to do with the rest? what if it points somewhere other
505 ### than the current session? */
506 if (ne_uri_parse(locn, &parse) != 0)
508 ne_uri_free(&parse);
509 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
510 _("Unable to parse URL '%s'"), locn);
513 rsrc->wr_url = apr_pstrdup(rsrc->pool, parse.path);
514 ne_uri_free(&parse);
516 return SVN_NO_ERROR;
519 static void record_prop_change(apr_pool_t *pool,
520 resource_baton_t *r,
521 const char *name,
522 const svn_string_t *value)
524 /* copy the name into the pool so we get the right lifetime (who knows
525 what the caller will do with it) */
526 name = apr_pstrdup(pool, name);
528 if (value)
530 /* changed/new property */
531 if (r->prop_changes == NULL)
532 r->prop_changes = apr_hash_make(pool);
533 apr_hash_set(r->prop_changes, name, APR_HASH_KEY_STRING,
534 svn_string_dup(value, pool));
536 else
538 /* deleted property. */
539 if (r->prop_deletes == NULL)
540 r->prop_deletes = apr_array_make(pool, 5, sizeof(char *));
541 APR_ARRAY_PUSH(r->prop_deletes, const char *) = name;
546 A very long note about enforcing directory-up-to-dateness when
547 proppatching, writ by Ben:
549 Once upon a time, I thought it would be necessary to attach the
550 X-SVN-Version-Name header to every PROPPATCH request we send. This
551 would allow mod_dav_svn to verify that a directory is up-to-date.
553 But it turns out that mod_dav_svn screams and errors if you *ever* try
554 to CHECKOUT an out-of-date VR. And furthermore, a directory is never
555 a 'committable' (according to svn_client_commit) unless it has a
556 propchange. Therefore:
558 1. when ra_neon's commit editor attempts to CHECKOUT a parent directory
559 because some child is being added or deleted, it's *unable* to get
560 the VR cache, and thus just gets the HEAD one instead. So it ends
561 up always doing a CHECKOUT of the latest version of the directory.
562 This is actually fine; Subversion's semantics allow us to
563 add/delete children on out-of-date directories. If, in dav terms,
564 this means always checking out the latest directory, so be it. Any
565 namespace conflicts will be detected with the actual PUT or DELETE
566 of the child.
568 2. when ra_neon's commit editor receives a directory propchange, it
569 *is* able to get the VR cache (because the dir is a "committable"),
570 and thus it does a CHECKOUT of the older directory. And mod_dav_svn
571 will scream if the VR is out of date, which is exactly what we want in
572 the directory propchange scenario.
574 The only potential badness here is the case of committing a directory
575 with a propchange, and an add/rm of its child. This commit should
576 fail, due to the out-of-date propchange. However, it's *possible*
577 that it will fail for a different reason: we might attempt the add/rm
578 first, which means checking out the parent VR, which *would* be
579 available from the cache, and thus we get an early error. Instead of
580 seeing an error about 'cannot proppatch out-of-date dir', the user
581 will see an error about 'cannot checkout out-of-date parent'. Not
582 really a big deal I guess.
585 static svn_error_t * do_proppatch(svn_ra_neon__session_t *ras,
586 const version_rsrc_t *rsrc,
587 resource_baton_t *rb,
588 apr_pool_t *pool)
590 const char *url = rsrc->wr_url;
591 apr_hash_t *extra_headers = NULL;
593 if (rb->token)
595 const char *token_header_val;
596 token_header_val = apr_psprintf(pool, "(<%s>)", rb->token);
598 extra_headers = apr_hash_make(pool);
599 apr_hash_set(extra_headers, "If", APR_HASH_KEY_STRING,
600 token_header_val);
603 return svn_ra_neon__do_proppatch(ras, url, rb->prop_changes,
604 rb->prop_deletes, extra_headers, pool);
608 static void
609 add_valid_target(commit_ctx_t *cc,
610 const char *path,
611 enum svn_recurse_kind kind)
613 apr_hash_t *hash = cc->valid_targets;
614 svn_string_t *path_str = svn_string_create(path, apr_hash_pool_get(hash));
615 apr_hash_set(hash, path_str->data, path_str->len, (void*)kind);
620 static svn_error_t * commit_open_root(void *edit_baton,
621 svn_revnum_t base_revision,
622 apr_pool_t *dir_pool,
623 void **root_baton)
625 commit_ctx_t *cc = edit_baton;
626 resource_baton_t *root;
627 version_rsrc_t *rsrc;
629 /* create the root resource. no wr_url (yet). */
630 rsrc = apr_pcalloc(dir_pool, sizeof(*rsrc));
631 rsrc->pool = dir_pool;
633 /* ### should this be 'base_revision' here? we might not always be
634 ### working against the head! (think "properties"). */
635 rsrc->revision = SVN_INVALID_REVNUM;
637 rsrc->url = cc->ras->root.path;
638 rsrc->local_path = "";
640 SVN_ERR(get_version_url(cc, NULL, rsrc, FALSE, dir_pool));
642 root = apr_pcalloc(dir_pool, sizeof(*root));
643 root->pool = dir_pool;
644 root->cc = cc;
645 root->rsrc = rsrc;
646 root->created = FALSE;
648 *root_baton = root;
650 return SVN_NO_ERROR;
654 /* Helper func for commit_delete_entry. Find all keys in LOCK_TOKENS
655 which are children of DIR. Returns the keys (and their vals) in
656 CHILD_TOKENS. No keys or values are reallocated or dup'd. If no
657 keys are children, then return an empty hash. Use POOL to allocate
658 new hash. */
659 static apr_hash_t *get_child_tokens(apr_hash_t *lock_tokens,
660 const char *dir,
661 apr_pool_t *pool)
663 apr_hash_index_t *hi;
664 apr_hash_t *tokens = apr_hash_make(pool);
665 apr_pool_t *subpool = svn_pool_create(pool);
667 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
669 const void *key;
670 apr_ssize_t klen;
671 void *val;
673 svn_pool_clear(subpool);
674 apr_hash_this(hi, &key, &klen, &val);
676 if (svn_path_is_child(dir, key, subpool))
677 apr_hash_set(tokens, key, klen, val);
680 svn_pool_destroy(subpool);
681 return tokens;
685 static svn_error_t * commit_delete_entry(const char *path,
686 svn_revnum_t revision,
687 void *parent_baton,
688 apr_pool_t *pool)
690 resource_baton_t *parent = parent_baton;
691 const char *name = svn_path_basename(path, pool);
692 apr_hash_t *extra_headers = NULL;
693 const char *child;
694 int code;
695 svn_error_t *serr;
697 if (SVN_IS_VALID_REVNUM(revision))
699 const char *revstr = apr_psprintf(pool, "%ld", revision);
701 if (! extra_headers)
702 extra_headers = apr_hash_make(pool);
704 apr_hash_set(extra_headers, SVN_DAV_VERSION_NAME_HEADER,
705 APR_HASH_KEY_STRING, revstr);
708 /* get the URL to the working collection */
709 SVN_ERR(checkout_resource(parent->cc, parent->rsrc, TRUE, NULL, pool));
711 /* create the URL for the child resource */
712 child = svn_path_url_add_component(parent->rsrc->wr_url, name, pool);
714 /* Start out assuming that we're deleting a file; try to lookup the
715 path itself in the token-hash, and if found, attach it to the If:
716 header. */
717 if (parent->cc->tokens)
719 const char *token =
720 apr_hash_get(parent->cc->tokens, path, APR_HASH_KEY_STRING);
722 if (token)
724 const char *token_header_val;
725 const char *token_uri;
727 token_uri = svn_path_url_add_component(parent->cc->ras->url->data,
728 path, pool);
729 token_header_val = apr_psprintf(pool, "<%s> (<%s>)",
730 token_uri, token);
731 extra_headers = apr_hash_make(pool);
732 apr_hash_set(extra_headers, "If", APR_HASH_KEY_STRING,
733 token_header_val);
737 /* dav_method_delete() always calls dav_unlock(), but if the svn
738 client passed --no-unlock to 'svn commit', then we need to send a
739 header which prevents mod_dav_svn from actually doing the unlock. */
740 if (parent->cc->keep_locks)
742 if (! extra_headers)
743 extra_headers = apr_hash_make(pool);
745 apr_hash_set(extra_headers, SVN_DAV_OPTIONS_HEADER,
746 APR_HASH_KEY_STRING, SVN_DAV_OPTION_KEEP_LOCKS);
749 /* 404 is ignored, because mod_dav_svn is effectively merging
750 against the HEAD revision on-the-fly. In such a universe, a
751 failed deletion (because it's already missing) is OK; deletion
752 is an idempotent merge operation. */
753 serr = svn_ra_neon__simple_request(&code, parent->cc->ras,
754 "DELETE", child,
755 extra_headers, NULL,
756 204 /* Created */,
757 404 /* Not Found */, pool);
759 /* A locking-related error most likely means we were deleting a
760 directory rather than a file, and didn't send all of the
761 necessary lock-tokens within the directory. */
762 if (serr && ((serr->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
763 || (serr->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN)
764 || (serr->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH)
765 || (serr->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED)))
767 /* Re-attempt the DELETE request as if the path were a directory.
768 Discover all lock-tokens within the directory, and send them in
769 the body of the request (which is normally empty). Of course,
770 if we don't *find* any additional lock-tokens, don't bother to
771 retry (it ain't gonna do any good).
773 Note that we're not sending the locks in the If: header, for
774 the same reason we're not sending in MERGE's headers: httpd has
775 limits on the amount of data it's willing to receive in headers. */
777 apr_hash_t *child_tokens = NULL;
778 svn_ra_neon__request_t *request;
779 const char *body;
780 const char *token;
781 svn_stringbuf_t *locks_list;
783 if (parent->cc->tokens)
784 child_tokens = get_child_tokens(parent->cc->tokens, path, pool);
786 /* No kiddos? Return the original error. Else, clear it so it
787 doesn't get leaked. */
788 if ((! child_tokens)
789 || (apr_hash_count(child_tokens) == 0))
790 return serr;
791 else
792 svn_error_clear(serr);
794 /* In preparation of directory locks, go ahead and add the actual
795 target's lock token to those of its children. */
796 if ((token = apr_hash_get(parent->cc->tokens, path,
797 APR_HASH_KEY_STRING)))
798 apr_hash_set(child_tokens, path, APR_HASH_KEY_STRING, token);
801 request =
802 svn_ra_neon__request_create(parent->cc->ras, "DELETE", child, pool);
804 SVN_ERR(svn_ra_neon__assemble_locktoken_body(&locks_list,
805 child_tokens, request->pool));
807 body = apr_psprintf(request->pool,
808 "<?xml version=\"1.0\" encoding=\"utf-8\"?> %s",
809 locks_list->data);
811 SVN_ERR(svn_ra_neon__request_dispatch(&code, request, NULL, body,
812 204 /* Created */,
813 404 /* Not Found */,
814 pool));
815 svn_ra_neon__request_destroy(request);
817 else if (serr)
818 return serr;
820 /* Add this path to the valid targets hash. */
821 add_valid_target(parent->cc, path, svn_nonrecursive);
823 return SVN_NO_ERROR;
828 static svn_error_t * commit_add_dir(const char *path,
829 void *parent_baton,
830 const char *copyfrom_path,
831 svn_revnum_t copyfrom_revision,
832 apr_pool_t *dir_pool,
833 void **child_baton)
835 resource_baton_t *parent = parent_baton;
836 resource_baton_t *child;
837 int code;
838 const char *name = svn_path_basename(path, dir_pool);
839 apr_pool_t *workpool = svn_pool_create(dir_pool);
840 version_rsrc_t *rsrc = NULL;
842 /* check out the parent resource so that we can create the new collection
843 as one of its children. */
844 SVN_ERR(checkout_resource(parent->cc, parent->rsrc, TRUE, NULL, dir_pool));
846 /* create a child object that contains all the resource urls */
847 child = apr_pcalloc(dir_pool, sizeof(*child));
848 child->pool = dir_pool;
849 child->cc = parent->cc;
850 child->created = TRUE;
851 SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc,
852 name, 1, SVN_INVALID_REVNUM, workpool));
853 child->rsrc = dup_resource(rsrc, dir_pool);
855 if (! copyfrom_path)
857 /* This a new directory with no history, so just create a new,
858 empty collection */
859 SVN_ERR(svn_ra_neon__simple_request(&code, parent->cc->ras, "MKCOL",
860 child->rsrc->wr_url, NULL, NULL,
861 201 /* Created */, 0, workpool));
863 else
865 svn_string_t bc_url, bc_relative;
866 const char *copy_src;
868 /* This add has history, so we need to do a COPY. */
870 /* Convert the copyfrom_* url/rev "public" pair into a Baseline
871 Collection (BC) URL that represents the revision -- and a
872 relative path under that BC. */
873 SVN_ERR(svn_ra_neon__get_baseline_info(NULL,
874 &bc_url, &bc_relative, NULL,
875 parent->cc->ras,
876 copyfrom_path,
877 copyfrom_revision,
878 workpool));
881 /* Combine the BC-URL and relative path; this is the main
882 "source" argument to the COPY request. The "Destination:"
883 header given to COPY is simply the wr_url that is already
884 part of the child object. */
885 copy_src = svn_path_url_add_component(bc_url.data,
886 bc_relative.data,
887 workpool);
889 /* Have neon do the COPY. */
890 SVN_ERR(svn_ra_neon__copy(parent->cc->ras,
891 1, /* overwrite */
892 SVN_RA_NEON__DEPTH_INFINITE, /* deep copy */
893 copy_src, /* source URI */
894 child->rsrc->wr_url, /* dest URI */
895 workpool));
897 /* Remember that this object was copied. */
898 child->copied = TRUE;
901 /* Add this path to the valid targets hash. */
902 add_valid_target(parent->cc, path,
903 copyfrom_path ? svn_recursive : svn_nonrecursive);
905 svn_pool_destroy(workpool);
906 *child_baton = child;
907 return SVN_NO_ERROR;
910 static svn_error_t * commit_open_dir(const char *path,
911 void *parent_baton,
912 svn_revnum_t base_revision,
913 apr_pool_t *dir_pool,
914 void **child_baton)
916 resource_baton_t *parent = parent_baton;
917 resource_baton_t *child = apr_pcalloc(dir_pool, sizeof(*child));
918 const char *name = svn_path_basename(path, dir_pool);
919 apr_pool_t *workpool = svn_pool_create(dir_pool);
920 version_rsrc_t *rsrc = NULL;
922 child->pool = dir_pool;
923 child->cc = parent->cc;
924 child->created = FALSE;
926 SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc,
927 name, 0, base_revision, workpool));
928 child->rsrc = dup_resource(rsrc, dir_pool);
931 ** Note: open_dir simply means that a change has occurred somewhere
932 ** within this directory. We have nothing to do, to prepare for
933 ** those changes (each will be considered independently).
935 ** Note: if a directory is replaced by something else, then this callback
936 ** will not be used: a true replacement is modeled with a "delete"
937 ** followed by an "add".
939 svn_pool_destroy(workpool);
940 *child_baton = child;
941 return SVN_NO_ERROR;
944 static svn_error_t * commit_change_dir_prop(void *dir_baton,
945 const char *name,
946 const svn_string_t *value,
947 apr_pool_t *pool)
949 resource_baton_t *dir = dir_baton;
951 /* record the change. it will be applied at close_dir time. */
952 /* ### we should put this into the dir_baton's pool */
953 record_prop_change(dir->pool, dir, name, value);
955 /* do the CHECKOUT sooner rather than later */
956 SVN_ERR(checkout_resource(dir->cc, dir->rsrc, TRUE, NULL, pool));
958 /* Add this path to the valid targets hash. */
959 add_valid_target(dir->cc, dir->rsrc->local_path, svn_nonrecursive);
961 return SVN_NO_ERROR;
964 static svn_error_t * commit_close_dir(void *dir_baton,
965 apr_pool_t *pool)
967 resource_baton_t *dir = dir_baton;
969 /* Perform all of the property changes on the directory. Note that we
970 checked out the directory when the first prop change was noted. */
971 SVN_ERR(do_proppatch(dir->cc->ras, dir->rsrc, dir, pool));
973 return SVN_NO_ERROR;
976 static svn_error_t * commit_add_file(const char *path,
977 void *parent_baton,
978 const char *copyfrom_path,
979 svn_revnum_t copyfrom_revision,
980 apr_pool_t *file_pool,
981 void **file_baton)
983 resource_baton_t *parent = parent_baton;
984 resource_baton_t *file;
985 const char *name = svn_path_basename(path, file_pool);
986 apr_pool_t *workpool = svn_pool_create(file_pool);
987 version_rsrc_t *rsrc = NULL;
990 ** To add a new file into the repository, we CHECKOUT the parent
991 ** collection, then PUT the file as a member of the resuling working
992 ** collection.
994 ** If the file was copied from elsewhere, then we will use the COPY
995 ** method to copy into the working collection.
998 /* Do the parent CHECKOUT first */
999 SVN_ERR(checkout_resource(parent->cc, parent->rsrc, TRUE, NULL, workpool));
1001 /* Construct a file_baton that contains all the resource urls. */
1002 file = apr_pcalloc(file_pool, sizeof(*file));
1003 file->pool = file_pool;
1004 file->cc = parent->cc;
1005 file->created = TRUE;
1006 SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc,
1007 name, 1, SVN_INVALID_REVNUM, workpool));
1008 file->rsrc = dup_resource(rsrc, file_pool);
1009 if (parent->cc->tokens)
1010 file->token = apr_hash_get(parent->cc->tokens, path, APR_HASH_KEY_STRING);
1012 /* If the parent directory existed before this commit then there may be a
1013 file with this URL already. We need to ensure such a file does not
1014 exist, which we do by attempting a PROPFIND. Of course, a
1015 PROPFIND *should* succeed if this "add" is actually the second
1016 half of a "replace".
1018 ### For now, we'll assume that if this path has already been
1019 added to the valid targets hash, that addition occurred during the
1020 "delete" phase (if that's not the case, this editor is being
1021 driven incorrectly, as we should never visit the same path twice
1022 except in a delete+add situation). */
1023 if ((! parent->created)
1024 && (! apr_hash_get(file->cc->valid_targets, path, APR_HASH_KEY_STRING)))
1026 svn_ra_neon__resource_t *res;
1027 svn_error_t *err = svn_ra_neon__get_starting_props(&res,
1028 file->cc->ras,
1029 file->rsrc->url, NULL,
1030 workpool);
1031 if (!err)
1033 /* If the PROPFIND succeeds the file already exists */
1034 return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
1035 _("File '%s' already exists"),
1036 file->rsrc->url);
1038 else if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1040 svn_error_clear(err);
1042 else
1044 /* A real error */
1045 return err;
1049 if (! copyfrom_path)
1051 /* This a truly new file. */
1053 /* ### wait for apply_txdelta before doing a PUT. it might arrive a
1054 ### "long time" from now. certainly after many other operations, so
1055 ### we don't want to start a PUT just yet.
1056 ### so... anything else to do here?
1059 else
1061 svn_string_t bc_url, bc_relative;
1062 const char *copy_src;
1064 /* This add has history, so we need to do a COPY. */
1066 /* Convert the copyfrom_* url/rev "public" pair into a Baseline
1067 Collection (BC) URL that represents the revision -- and a
1068 relative path under that BC. */
1069 SVN_ERR(svn_ra_neon__get_baseline_info(NULL,
1070 &bc_url, &bc_relative, NULL,
1071 parent->cc->ras,
1072 copyfrom_path,
1073 copyfrom_revision,
1074 workpool));
1077 /* Combine the BC-URL and relative path; this is the main
1078 "source" argument to the COPY request. The "Destination:"
1079 header given to COPY is simply the wr_url that is already
1080 part of the file_baton. */
1081 copy_src = svn_path_url_add_component(bc_url.data,
1082 bc_relative.data,
1083 workpool);
1085 /* Have neon do the COPY. */
1086 SVN_ERR(svn_ra_neon__copy(parent->cc->ras,
1087 1, /* overwrite */
1088 SVN_RA_NEON__DEPTH_ZERO,
1089 /* file: this doesn't matter */
1090 copy_src, /* source URI */
1091 file->rsrc->wr_url,/* dest URI */
1092 workpool));
1094 /* Remember that this object was copied. */
1095 file->copied = TRUE;
1098 /* Add this path to the valid targets hash. */
1099 add_valid_target(parent->cc, path, svn_nonrecursive);
1101 svn_pool_destroy(workpool);
1103 /* return the file_baton */
1104 *file_baton = file;
1105 return SVN_NO_ERROR;
1108 static svn_error_t * commit_open_file(const char *path,
1109 void *parent_baton,
1110 svn_revnum_t base_revision,
1111 apr_pool_t *file_pool,
1112 void **file_baton)
1114 resource_baton_t *parent = parent_baton;
1115 resource_baton_t *file;
1116 const char *name = svn_path_basename(path, file_pool);
1117 apr_pool_t *workpool = svn_pool_create(file_pool);
1118 version_rsrc_t *rsrc = NULL;
1120 file = apr_pcalloc(file_pool, sizeof(*file));
1121 file->pool = file_pool;
1122 file->cc = parent->cc;
1123 file->created = FALSE;
1124 SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc,
1125 name, 0, base_revision, workpool));
1126 file->rsrc = dup_resource(rsrc, file_pool);
1127 if (parent->cc->tokens)
1128 file->token = apr_hash_get(parent->cc->tokens, path, APR_HASH_KEY_STRING);
1130 /* do the CHECKOUT now. we'll PUT the new file contents later on. */
1131 SVN_ERR(checkout_resource(parent->cc, file->rsrc, TRUE,
1132 file->token, workpool));
1134 /* ### wait for apply_txdelta before doing a PUT. it might arrive a
1135 ### "long time" from now. certainly after many other operations, so
1136 ### we don't want to start a PUT just yet.
1137 ### so... anything else to do here? what about the COPY case?
1140 svn_pool_destroy(workpool);
1141 *file_baton = file;
1142 return SVN_NO_ERROR;
1145 static svn_error_t * commit_stream_write(void *baton,
1146 const char *data,
1147 apr_size_t *len)
1149 put_baton_t *pb = baton;
1150 svn_ra_neon__session_t *ras = pb->ras;
1151 apr_status_t status;
1153 if (ras->callbacks && ras->callbacks->cancel_func)
1154 SVN_ERR(ras->callbacks->cancel_func(ras->callback_baton));
1156 /* drop the data into our temp file */
1157 status = apr_file_write_full(pb->tmpfile, data, *len, NULL);
1158 if (status)
1159 return svn_error_wrap_apr(status,
1160 _("Could not write svndiff to temp file"));
1162 if (ras->progress_func)
1164 pb->progress += *len;
1165 ras->progress_func(pb->progress, -1, ras->progress_baton, pb->pool);
1168 return SVN_NO_ERROR;
1171 static svn_error_t *
1172 commit_apply_txdelta(void *file_baton,
1173 const char *base_checksum,
1174 apr_pool_t *pool,
1175 svn_txdelta_window_handler_t *handler,
1176 void **handler_baton)
1178 resource_baton_t *file = file_baton;
1179 put_baton_t *baton;
1180 svn_stream_t *stream;
1182 baton = apr_pcalloc(file->pool, sizeof(*baton));
1183 baton->ras = file->cc->ras;
1184 baton->pool = file->pool;
1185 file->put_baton = baton;
1187 if (base_checksum)
1188 baton->base_checksum = apr_pstrdup(file->pool, base_checksum);
1189 else
1190 baton->base_checksum = NULL;
1192 /* ### oh, hell. Neon's request body support is either text (a C string),
1193 ### or a FILE*. since we are getting binary data, we must use a FILE*
1194 ### for now. isn't that special? */
1196 /* Use the client callback to create a tmpfile. */
1197 SVN_ERR(file->cc->ras->callbacks->open_tmp_file
1198 (&baton->tmpfile,
1199 file->cc->ras->callback_baton,
1200 file->pool));
1202 /* ### register a cleanup on file_pool which closes the file; this
1203 ### will ensure that the file always gets tossed, even if we exit
1204 ### with an error. */
1206 stream = svn_stream_create(baton, pool);
1207 svn_stream_set_write(stream, commit_stream_write);
1209 svn_txdelta_to_svndiff(stream, pool, handler, handler_baton);
1211 /* Add this path to the valid targets hash. */
1212 add_valid_target(file->cc, file->rsrc->local_path, svn_nonrecursive);
1214 return SVN_NO_ERROR;
1217 static svn_error_t * commit_change_file_prop(void *file_baton,
1218 const char *name,
1219 const svn_string_t *value,
1220 apr_pool_t *pool)
1222 resource_baton_t *file = file_baton;
1224 /* record the change. it will be applied at close_file time. */
1225 /* ### we should put this into the file_baton's pool */
1226 record_prop_change(file->pool, file, name, value);
1228 /* do the CHECKOUT sooner rather than later */
1229 SVN_ERR(checkout_resource(file->cc, file->rsrc, TRUE, file->token, pool));
1231 /* Add this path to the valid targets hash. */
1232 add_valid_target(file->cc, file->rsrc->local_path, svn_nonrecursive);
1234 return SVN_NO_ERROR;
1237 static svn_error_t * commit_close_file(void *file_baton,
1238 const char *text_checksum,
1239 apr_pool_t *pool)
1241 resource_baton_t *file = file_baton;
1242 commit_ctx_t *cc = file->cc;
1244 /* If this is a newly added file, not copied, and the editor driver
1245 didn't call apply_textdelta(), then we'll pretend they *did* call
1246 apply_textdelta() and described a zero-byte empty file. */
1247 if ((! file->put_baton) && file->created && (! file->copied))
1249 /* Make a dummy put_baton, with NULL fields to indicate that
1250 we're dealing with a content-less (zero-byte) file. */
1251 file->put_baton = apr_pcalloc(file->pool, sizeof(*(file->put_baton)));
1254 if (file->put_baton)
1256 put_baton_t *pb = file->put_baton;
1257 const char *url = file->rsrc->wr_url;
1258 apr_hash_t *extra_headers;
1259 svn_ra_neon__request_t *request;
1261 /* create/prep the request */
1262 request = svn_ra_neon__request_create(cc->ras, "PUT", url, pool);
1264 extra_headers = apr_hash_make(request->pool);
1266 if (file->token)
1267 svn_ra_neon__set_header
1268 (extra_headers, "If",
1269 apr_psprintf(pool, "<%s> (<%s>)",
1270 svn_path_url_add_component(cc->ras->url->data,
1271 file->rsrc->url,
1272 request->pool),
1273 file->token));
1275 if (pb->base_checksum)
1276 svn_ra_neon__set_header(extra_headers,
1277 SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
1278 pb->base_checksum);
1280 if (text_checksum)
1281 svn_ra_neon__set_header(extra_headers,
1282 SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
1283 text_checksum);
1285 if (pb->tmpfile)
1287 svn_ra_neon__set_header(extra_headers, "Content-Type",
1288 SVN_SVNDIFF_MIME_TYPE);
1290 /* Give the file to neon. The provider will rewind the file. */
1291 SVN_ERR(svn_ra_neon__set_neon_body_provider(request, pb->tmpfile));
1293 else
1295 ne_set_request_body_buffer(request->ne_req, "", 0);
1298 /* run the request and get the resulting status code (and svn_error_t) */
1299 SVN_ERR(svn_ra_neon__request_dispatch(NULL, request, extra_headers, NULL,
1300 201 /* Created */,
1301 204 /* No Content */,
1302 pool));
1303 svn_ra_neon__request_destroy(request);
1305 if (pb->tmpfile)
1307 /* we're done with the file. this should delete it. */
1308 (void) apr_file_close(pb->tmpfile);
1312 /* Perform all of the property changes on the file. Note that we
1313 checked out the file when the first prop change was noted. */
1314 SVN_ERR(do_proppatch(cc->ras, file->rsrc, file, pool));
1316 return SVN_NO_ERROR;
1320 static svn_error_t * commit_close_edit(void *edit_baton,
1321 apr_pool_t *pool)
1323 commit_ctx_t *cc = edit_baton;
1324 svn_commit_info_t *commit_info = svn_create_commit_info(pool);
1326 SVN_ERR(svn_ra_neon__merge_activity(&(commit_info->revision),
1327 &(commit_info->date),
1328 &(commit_info->author),
1329 &(commit_info->post_commit_err),
1330 cc->ras,
1331 cc->ras->root.path,
1332 cc->activity_url,
1333 cc->valid_targets,
1334 cc->tokens,
1335 cc->keep_locks,
1336 cc->disable_merge_response,
1337 pool));
1338 SVN_ERR(delete_activity(edit_baton, pool));
1339 SVN_ERR(svn_ra_neon__maybe_store_auth_info(cc->ras, pool));
1341 if (commit_info->revision != SVN_INVALID_REVNUM)
1342 SVN_ERR(cc->callback(commit_info, cc->callback_baton, pool));
1344 return SVN_NO_ERROR;
1348 static svn_error_t * commit_abort_edit(void *edit_baton,
1349 apr_pool_t *pool)
1351 return delete_activity(edit_baton, pool);
1355 static svn_error_t * apply_revprops(commit_ctx_t *cc,
1356 apr_hash_t *revprop_table,
1357 apr_pool_t *pool)
1359 const svn_string_t *vcc;
1360 const svn_string_t *baseline_url;
1361 version_rsrc_t baseline_rsrc = { SVN_INVALID_REVNUM };
1362 svn_error_t *err = NULL;
1363 int retry_count = 5;
1365 /* ### this whole sequence can/should be replaced with an expand-property
1366 ### REPORT when that is available on the server. */
1368 /* fetch the DAV:version-controlled-configuration from the session's URL */
1369 SVN_ERR(svn_ra_neon__get_one_prop(&vcc, cc->ras, cc->ras->root.path,
1370 NULL, &svn_ra_neon__vcc_prop, pool));
1372 /* ### we should use DAV:apply-to-version on the CHECKOUT so we can skip
1373 ### retrieval of the baseline */
1375 do {
1377 svn_error_clear(err);
1379 /* Get the latest baseline from VCC's DAV:checked-in property.
1380 This should give us the HEAD revision of the moment. */
1381 SVN_ERR(svn_ra_neon__get_one_prop(&baseline_url, cc->ras,
1382 vcc->data, NULL,
1383 &svn_ra_neon__checked_in_prop, pool));
1384 baseline_rsrc.pool = pool;
1385 baseline_rsrc.vsn_url = baseline_url->data;
1387 /* To set the revision properties, we must checkout the latest baseline
1388 and get back a mutable "working" baseline. */
1389 err = checkout_resource(cc, &baseline_rsrc, FALSE, NULL, pool);
1391 /* There's a small chance of a race condition here, if apache is
1392 experiencing heavy commit concurrency or if the network has
1393 long latency. It's possible that the value of HEAD changed
1394 between the time we fetched the latest baseline and the time we
1395 checkout that baseline. If that happens, apache will throw us
1396 a BAD_BASELINE error (deltaV says you can only checkout the
1397 latest baseline). We just ignore that specific error and
1398 retry a few times, asking for the latest baseline again. */
1399 if (err && err->apr_err != SVN_ERR_APMOD_BAD_BASELINE)
1400 return err;
1402 } while (err && (--retry_count > 0));
1404 /* Yikes, if we couldn't hold onto HEAD after a few retries, throw a
1405 real error.*/
1406 if (err)
1407 return err;
1409 return svn_ra_neon__do_proppatch(cc->ras, baseline_rsrc.wr_url, revprop_table,
1410 NULL, NULL, pool);
1413 svn_error_t * svn_ra_neon__get_commit_editor(svn_ra_session_t *session,
1414 const svn_delta_editor_t **editor,
1415 void **edit_baton,
1416 apr_hash_t *revprop_table,
1417 svn_commit_callback2_t callback,
1418 void *callback_baton,
1419 apr_hash_t *lock_tokens,
1420 svn_boolean_t keep_locks,
1421 apr_pool_t *pool)
1423 svn_ra_neon__session_t *ras = session->priv;
1424 svn_delta_editor_t *commit_editor;
1425 commit_ctx_t *cc;
1426 svn_error_t *err;
1428 /* Build the main commit editor's baton. */
1429 cc = apr_pcalloc(pool, sizeof(*cc));
1430 cc->ras = ras;
1431 cc->valid_targets = apr_hash_make(pool);
1432 cc->get_func = ras->callbacks->get_wc_prop;
1433 cc->push_func = ras->callbacks->push_wc_prop;
1434 cc->cb_baton = ras->callback_baton;
1435 cc->callback = callback;
1436 cc->callback_baton = callback_baton;
1437 cc->tokens = lock_tokens;
1438 cc->keep_locks = keep_locks;
1440 /* If the caller didn't give us any way of storing wcprops, then
1441 there's no point in getting back a MERGE response full of VR's. */
1442 if (ras->callbacks->push_wc_prop == NULL)
1443 cc->disable_merge_response = TRUE;
1445 /* ### should we perform an OPTIONS to validate the server we're about
1446 ### to talk to? */
1449 ** Create an Activity. This corresponds directly to an FS transaction.
1450 ** We will check out all further resources within the context of this
1451 ** activity.
1453 SVN_ERR(create_activity(cc, pool));
1456 ** Find the latest baseline resource, check it out, and then apply the
1457 ** log message onto the thing.
1459 err = apply_revprops(cc, revprop_table, pool);
1460 /* If the caller gets an error during the editor drive, we rely on them
1461 to call abort_edit() so that we can clear up the activity. But if we
1462 got an error here, we need to clear up the activity ourselves. */
1463 if (err)
1465 svn_error_clear(commit_abort_edit(cc, pool));
1466 return err;
1470 ** Set up the editor.
1472 ** This structure is used during the commit process. An external caller
1473 ** uses these callbacks to describe all the changes in the working copy
1474 ** that must be committed to the server.
1476 commit_editor = svn_delta_default_editor(pool);
1477 commit_editor->open_root = commit_open_root;
1478 commit_editor->delete_entry = commit_delete_entry;
1479 commit_editor->add_directory = commit_add_dir;
1480 commit_editor->open_directory = commit_open_dir;
1481 commit_editor->change_dir_prop = commit_change_dir_prop;
1482 commit_editor->close_directory = commit_close_dir;
1483 commit_editor->add_file = commit_add_file;
1484 commit_editor->open_file = commit_open_file;
1485 commit_editor->apply_textdelta = commit_apply_txdelta;
1486 commit_editor->change_file_prop = commit_change_file_prop;
1487 commit_editor->close_file = commit_close_file;
1488 commit_editor->close_edit = commit_close_edit;
1489 commit_editor->abort_edit = commit_abort_edit;
1491 *editor = commit_editor;
1492 *edit_baton = cc;
1493 return SVN_NO_ERROR;