Reorganize the output to "svnserve --help".
[svn.git] / subversion / libsvn_ra_neon / commit.c
blobc6d8acf070df54220d64839dd963a122f73ee4ee
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;
406 svn_error_t *err = SVN_NO_ERROR;
408 /* assert: vsn_url != NULL */
410 /* ### send a CHECKOUT resource on vsn_url; include cc->activity_url;
411 ### place result into res->wr_url and return it */
413 /* create/prep the request */
414 request =
415 svn_ra_neon__request_create(cc->ras, "CHECKOUT", vsn_url, pool);
417 /* ### store this into cc to avoid pool growth */
418 body = apr_psprintf(request->pool,
419 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
420 "<D:checkout xmlns:D=\"DAV:\">"
421 "<D:activity-set>"
422 "<D:href>%s</D:href>"
423 "</D:activity-set></D:checkout>", cc->activity_url);
425 if (token)
427 extra_headers = apr_hash_make(request->pool);
428 svn_ra_neon__set_header(extra_headers, "If",
429 apr_psprintf(request->pool, "(<%s>)", token));
432 /* run the request and get the resulting status code (and svn_error_t) */
433 err = svn_ra_neon__request_dispatch(code, request, extra_headers, body,
434 201 /* Created */,
435 allow_404 ? 404 /* Not Found */ : 0,
436 pool);
437 if (err)
438 goto cleanup;
440 if (allow_404 && *code == 404 && request->err)
442 svn_error_clear(request->err);
443 request->err = SVN_NO_ERROR;
446 *locn = svn_ra_neon__request_get_location(request, pool);
448 cleanup:
449 svn_ra_neon__request_destroy(request);
451 return err;
455 static svn_error_t * checkout_resource(commit_ctx_t *cc,
456 version_rsrc_t *rsrc,
457 svn_boolean_t allow_404,
458 const char *token,
459 apr_pool_t *pool)
461 int code;
462 const char *locn = NULL;
463 ne_uri parse;
464 svn_error_t *err;
466 if (rsrc->wr_url != NULL)
468 /* already checked out! */
469 return NULL;
472 /* check out the Version Resource */
473 err = do_checkout(cc, rsrc->vsn_url, allow_404, token, &code, &locn, pool);
475 /* possibly run the request again, with a re-fetched Version Resource */
476 if (err == NULL && allow_404 && code == 404)
478 locn = NULL;
480 /* re-fetch, forcing a query to the server */
481 SVN_ERR(get_version_url(cc, NULL, rsrc, TRUE, pool));
483 /* do it again, but don't allow a 404 this time */
484 err = do_checkout(cc, rsrc->vsn_url, FALSE, token, &code, &locn, pool);
487 /* special-case when conflicts occur */
488 if (err)
490 /* ### TODO: it's a shame we don't have the full path from the
491 ### root of the drive here, nor the type of the resource.
492 ### Because we lack this information, the error message is
493 ### overly generic. See issue #2740. */
494 if (err->apr_err == SVN_ERR_FS_CONFLICT)
495 return svn_error_createf
496 (err->apr_err, err,
497 _("File or directory '%s' is out of date; try updating"),
498 svn_path_local_style(rsrc->local_path, pool));
499 return err;
502 /* we got the header, right? */
503 if (locn == NULL)
504 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
505 _("The CHECKOUT response did not contain a "
506 "'Location:' header"));
508 /* The location is an absolute URI. We want just the path portion. */
509 /* ### what to do with the rest? what if it points somewhere other
510 ### than the current session? */
511 if (ne_uri_parse(locn, &parse) != 0)
513 ne_uri_free(&parse);
514 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
515 _("Unable to parse URL '%s'"), locn);
518 rsrc->wr_url = apr_pstrdup(rsrc->pool, parse.path);
519 ne_uri_free(&parse);
521 return SVN_NO_ERROR;
524 static void record_prop_change(apr_pool_t *pool,
525 resource_baton_t *r,
526 const char *name,
527 const svn_string_t *value)
529 /* copy the name into the pool so we get the right lifetime (who knows
530 what the caller will do with it) */
531 name = apr_pstrdup(pool, name);
533 if (value)
535 /* changed/new property */
536 if (r->prop_changes == NULL)
537 r->prop_changes = apr_hash_make(pool);
538 apr_hash_set(r->prop_changes, name, APR_HASH_KEY_STRING,
539 svn_string_dup(value, pool));
541 else
543 /* deleted property. */
544 if (r->prop_deletes == NULL)
545 r->prop_deletes = apr_array_make(pool, 5, sizeof(char *));
546 APR_ARRAY_PUSH(r->prop_deletes, const char *) = name;
551 A very long note about enforcing directory-up-to-dateness when
552 proppatching, writ by Ben:
554 Once upon a time, I thought it would be necessary to attach the
555 X-SVN-Version-Name header to every PROPPATCH request we send. This
556 would allow mod_dav_svn to verify that a directory is up-to-date.
558 But it turns out that mod_dav_svn screams and errors if you *ever* try
559 to CHECKOUT an out-of-date VR. And furthermore, a directory is never
560 a 'committable' (according to svn_client_commit) unless it has a
561 propchange. Therefore:
563 1. when ra_neon's commit editor attempts to CHECKOUT a parent directory
564 because some child is being added or deleted, it's *unable* to get
565 the VR cache, and thus just gets the HEAD one instead. So it ends
566 up always doing a CHECKOUT of the latest version of the directory.
567 This is actually fine; Subversion's semantics allow us to
568 add/delete children on out-of-date directories. If, in dav terms,
569 this means always checking out the latest directory, so be it. Any
570 namespace conflicts will be detected with the actual PUT or DELETE
571 of the child.
573 2. when ra_neon's commit editor receives a directory propchange, it
574 *is* able to get the VR cache (because the dir is a "committable"),
575 and thus it does a CHECKOUT of the older directory. And mod_dav_svn
576 will scream if the VR is out of date, which is exactly what we want in
577 the directory propchange scenario.
579 The only potential badness here is the case of committing a directory
580 with a propchange, and an add/rm of its child. This commit should
581 fail, due to the out-of-date propchange. However, it's *possible*
582 that it will fail for a different reason: we might attempt the add/rm
583 first, which means checking out the parent VR, which *would* be
584 available from the cache, and thus we get an early error. Instead of
585 seeing an error about 'cannot proppatch out-of-date dir', the user
586 will see an error about 'cannot checkout out-of-date parent'. Not
587 really a big deal I guess.
590 static svn_error_t * do_proppatch(svn_ra_neon__session_t *ras,
591 const version_rsrc_t *rsrc,
592 resource_baton_t *rb,
593 apr_pool_t *pool)
595 const char *url = rsrc->wr_url;
596 apr_hash_t *extra_headers = NULL;
598 if (rb->token)
600 const char *token_header_val;
601 token_header_val = apr_psprintf(pool, "(<%s>)", rb->token);
603 extra_headers = apr_hash_make(pool);
604 apr_hash_set(extra_headers, "If", APR_HASH_KEY_STRING,
605 token_header_val);
608 return svn_ra_neon__do_proppatch(ras, url, rb->prop_changes,
609 rb->prop_deletes, extra_headers, pool);
613 static void
614 add_valid_target(commit_ctx_t *cc,
615 const char *path,
616 enum svn_recurse_kind kind)
618 apr_hash_t *hash = cc->valid_targets;
619 svn_string_t *path_str = svn_string_create(path, apr_hash_pool_get(hash));
620 apr_hash_set(hash, path_str->data, path_str->len, (void*)kind);
625 static svn_error_t * commit_open_root(void *edit_baton,
626 svn_revnum_t base_revision,
627 apr_pool_t *dir_pool,
628 void **root_baton)
630 commit_ctx_t *cc = edit_baton;
631 resource_baton_t *root;
632 version_rsrc_t *rsrc;
634 /* create the root resource. no wr_url (yet). */
635 rsrc = apr_pcalloc(dir_pool, sizeof(*rsrc));
636 rsrc->pool = dir_pool;
638 /* ### should this be 'base_revision' here? we might not always be
639 ### working against the head! (think "properties"). */
640 rsrc->revision = SVN_INVALID_REVNUM;
642 rsrc->url = cc->ras->root.path;
643 rsrc->local_path = "";
645 SVN_ERR(get_version_url(cc, NULL, rsrc, FALSE, dir_pool));
647 root = apr_pcalloc(dir_pool, sizeof(*root));
648 root->pool = dir_pool;
649 root->cc = cc;
650 root->rsrc = rsrc;
651 root->created = FALSE;
653 *root_baton = root;
655 return SVN_NO_ERROR;
659 /* Helper func for commit_delete_entry. Find all keys in LOCK_TOKENS
660 which are children of DIR. Returns the keys (and their vals) in
661 CHILD_TOKENS. No keys or values are reallocated or dup'd. If no
662 keys are children, then return an empty hash. Use POOL to allocate
663 new hash. */
664 static apr_hash_t *get_child_tokens(apr_hash_t *lock_tokens,
665 const char *dir,
666 apr_pool_t *pool)
668 apr_hash_index_t *hi;
669 apr_hash_t *tokens = apr_hash_make(pool);
670 apr_pool_t *subpool = svn_pool_create(pool);
672 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
674 const void *key;
675 apr_ssize_t klen;
676 void *val;
678 svn_pool_clear(subpool);
679 apr_hash_this(hi, &key, &klen, &val);
681 if (svn_path_is_child(dir, key, subpool))
682 apr_hash_set(tokens, key, klen, val);
685 svn_pool_destroy(subpool);
686 return tokens;
690 static svn_error_t * commit_delete_entry(const char *path,
691 svn_revnum_t revision,
692 void *parent_baton,
693 apr_pool_t *pool)
695 resource_baton_t *parent = parent_baton;
696 const char *name = svn_path_basename(path, pool);
697 apr_hash_t *extra_headers = NULL;
698 const char *child;
699 int code;
700 svn_error_t *serr;
702 if (SVN_IS_VALID_REVNUM(revision))
704 const char *revstr = apr_psprintf(pool, "%ld", revision);
706 if (! extra_headers)
707 extra_headers = apr_hash_make(pool);
709 apr_hash_set(extra_headers, SVN_DAV_VERSION_NAME_HEADER,
710 APR_HASH_KEY_STRING, revstr);
713 /* get the URL to the working collection */
714 SVN_ERR(checkout_resource(parent->cc, parent->rsrc, TRUE, NULL, pool));
716 /* create the URL for the child resource */
717 child = svn_path_url_add_component(parent->rsrc->wr_url, name, pool);
719 /* Start out assuming that we're deleting a file; try to lookup the
720 path itself in the token-hash, and if found, attach it to the If:
721 header. */
722 if (parent->cc->tokens)
724 const char *token =
725 apr_hash_get(parent->cc->tokens, path, APR_HASH_KEY_STRING);
727 if (token)
729 const char *token_header_val;
730 const char *token_uri;
732 token_uri = svn_path_url_add_component(parent->cc->ras->url->data,
733 path, pool);
734 token_header_val = apr_psprintf(pool, "<%s> (<%s>)",
735 token_uri, token);
736 extra_headers = apr_hash_make(pool);
737 apr_hash_set(extra_headers, "If", APR_HASH_KEY_STRING,
738 token_header_val);
742 /* dav_method_delete() always calls dav_unlock(), but if the svn
743 client passed --no-unlock to 'svn commit', then we need to send a
744 header which prevents mod_dav_svn from actually doing the unlock. */
745 if (parent->cc->keep_locks)
747 if (! extra_headers)
748 extra_headers = apr_hash_make(pool);
750 apr_hash_set(extra_headers, SVN_DAV_OPTIONS_HEADER,
751 APR_HASH_KEY_STRING, SVN_DAV_OPTION_KEEP_LOCKS);
754 /* 404 is ignored, because mod_dav_svn is effectively merging
755 against the HEAD revision on-the-fly. In such a universe, a
756 failed deletion (because it's already missing) is OK; deletion
757 is an idempotent merge operation. */
758 serr = svn_ra_neon__simple_request(&code, parent->cc->ras,
759 "DELETE", child,
760 extra_headers, NULL,
761 204 /* Created */,
762 404 /* Not Found */, pool);
764 /* A locking-related error most likely means we were deleting a
765 directory rather than a file, and didn't send all of the
766 necessary lock-tokens within the directory. */
767 if (serr && ((serr->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
768 || (serr->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN)
769 || (serr->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH)
770 || (serr->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED)))
772 /* Re-attempt the DELETE request as if the path were a directory.
773 Discover all lock-tokens within the directory, and send them in
774 the body of the request (which is normally empty). Of course,
775 if we don't *find* any additional lock-tokens, don't bother to
776 retry (it ain't gonna do any good).
778 Note that we're not sending the locks in the If: header, for
779 the same reason we're not sending in MERGE's headers: httpd has
780 limits on the amount of data it's willing to receive in headers. */
782 apr_hash_t *child_tokens = NULL;
783 svn_ra_neon__request_t *request;
784 const char *body;
785 const char *token;
786 svn_stringbuf_t *locks_list;
787 svn_error_t *err = SVN_NO_ERROR;
789 if (parent->cc->tokens)
790 child_tokens = get_child_tokens(parent->cc->tokens, path, pool);
792 /* No kiddos? Return the original error. Else, clear it so it
793 doesn't get leaked. */
794 if ((! child_tokens)
795 || (apr_hash_count(child_tokens) == 0))
796 return serr;
797 else
798 svn_error_clear(serr);
800 /* In preparation of directory locks, go ahead and add the actual
801 target's lock token to those of its children. */
802 if ((token = apr_hash_get(parent->cc->tokens, path,
803 APR_HASH_KEY_STRING)))
804 apr_hash_set(child_tokens, path, APR_HASH_KEY_STRING, token);
807 request =
808 svn_ra_neon__request_create(parent->cc->ras, "DELETE", child, pool);
810 err = svn_ra_neon__assemble_locktoken_body(&locks_list,
811 child_tokens, request->pool);
812 if (err)
813 goto cleanup;
815 body = apr_psprintf(request->pool,
816 "<?xml version=\"1.0\" encoding=\"utf-8\"?> %s",
817 locks_list->data);
819 err = svn_ra_neon__request_dispatch(&code, request, NULL, body,
820 204 /* Created */,
821 404 /* Not Found */,
822 pool);
823 cleanup:
824 svn_ra_neon__request_destroy(request);
825 SVN_ERR(err);
827 else if (serr)
828 return serr;
830 /* Add this path to the valid targets hash. */
831 add_valid_target(parent->cc, path, svn_nonrecursive);
833 return SVN_NO_ERROR;
838 static svn_error_t * commit_add_dir(const char *path,
839 void *parent_baton,
840 const char *copyfrom_path,
841 svn_revnum_t copyfrom_revision,
842 apr_pool_t *dir_pool,
843 void **child_baton)
845 resource_baton_t *parent = parent_baton;
846 resource_baton_t *child;
847 int code;
848 const char *name = svn_path_basename(path, dir_pool);
849 apr_pool_t *workpool = svn_pool_create(dir_pool);
850 version_rsrc_t *rsrc = NULL;
852 /* check out the parent resource so that we can create the new collection
853 as one of its children. */
854 SVN_ERR(checkout_resource(parent->cc, parent->rsrc, TRUE, NULL, dir_pool));
856 /* create a child object that contains all the resource urls */
857 child = apr_pcalloc(dir_pool, sizeof(*child));
858 child->pool = dir_pool;
859 child->cc = parent->cc;
860 child->created = TRUE;
861 SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc,
862 name, 1, SVN_INVALID_REVNUM, workpool));
863 child->rsrc = dup_resource(rsrc, dir_pool);
865 if (! copyfrom_path)
867 /* This a new directory with no history, so just create a new,
868 empty collection */
869 SVN_ERR(svn_ra_neon__simple_request(&code, parent->cc->ras, "MKCOL",
870 child->rsrc->wr_url, NULL, NULL,
871 201 /* Created */, 0, workpool));
873 else
875 svn_string_t bc_url, bc_relative;
876 const char *copy_src;
878 /* This add has history, so we need to do a COPY. */
880 /* Convert the copyfrom_* url/rev "public" pair into a Baseline
881 Collection (BC) URL that represents the revision -- and a
882 relative path under that BC. */
883 SVN_ERR(svn_ra_neon__get_baseline_info(NULL,
884 &bc_url, &bc_relative, NULL,
885 parent->cc->ras,
886 copyfrom_path,
887 copyfrom_revision,
888 workpool));
891 /* Combine the BC-URL and relative path; this is the main
892 "source" argument to the COPY request. The "Destination:"
893 header given to COPY is simply the wr_url that is already
894 part of the child object. */
895 copy_src = svn_path_url_add_component(bc_url.data,
896 bc_relative.data,
897 workpool);
899 /* Have neon do the COPY. */
900 SVN_ERR(svn_ra_neon__copy(parent->cc->ras,
901 1, /* overwrite */
902 SVN_RA_NEON__DEPTH_INFINITE, /* deep copy */
903 copy_src, /* source URI */
904 child->rsrc->wr_url, /* dest URI */
905 workpool));
907 /* Remember that this object was copied. */
908 child->copied = TRUE;
911 /* Add this path to the valid targets hash. */
912 add_valid_target(parent->cc, path,
913 copyfrom_path ? svn_recursive : svn_nonrecursive);
915 svn_pool_destroy(workpool);
916 *child_baton = child;
917 return SVN_NO_ERROR;
920 static svn_error_t * commit_open_dir(const char *path,
921 void *parent_baton,
922 svn_revnum_t base_revision,
923 apr_pool_t *dir_pool,
924 void **child_baton)
926 resource_baton_t *parent = parent_baton;
927 resource_baton_t *child = apr_pcalloc(dir_pool, sizeof(*child));
928 const char *name = svn_path_basename(path, dir_pool);
929 apr_pool_t *workpool = svn_pool_create(dir_pool);
930 version_rsrc_t *rsrc = NULL;
932 child->pool = dir_pool;
933 child->cc = parent->cc;
934 child->created = FALSE;
936 SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc,
937 name, 0, base_revision, workpool));
938 child->rsrc = dup_resource(rsrc, dir_pool);
941 ** Note: open_dir simply means that a change has occurred somewhere
942 ** within this directory. We have nothing to do, to prepare for
943 ** those changes (each will be considered independently).
945 ** Note: if a directory is replaced by something else, then this callback
946 ** will not be used: a true replacement is modeled with a "delete"
947 ** followed by an "add".
949 svn_pool_destroy(workpool);
950 *child_baton = child;
951 return SVN_NO_ERROR;
954 static svn_error_t * commit_change_dir_prop(void *dir_baton,
955 const char *name,
956 const svn_string_t *value,
957 apr_pool_t *pool)
959 resource_baton_t *dir = dir_baton;
961 /* record the change. it will be applied at close_dir time. */
962 /* ### we should put this into the dir_baton's pool */
963 record_prop_change(dir->pool, dir, name, value);
965 /* do the CHECKOUT sooner rather than later */
966 SVN_ERR(checkout_resource(dir->cc, dir->rsrc, TRUE, NULL, pool));
968 /* Add this path to the valid targets hash. */
969 add_valid_target(dir->cc, dir->rsrc->local_path, svn_nonrecursive);
971 return SVN_NO_ERROR;
974 static svn_error_t * commit_close_dir(void *dir_baton,
975 apr_pool_t *pool)
977 resource_baton_t *dir = dir_baton;
979 /* Perform all of the property changes on the directory. Note that we
980 checked out the directory when the first prop change was noted. */
981 SVN_ERR(do_proppatch(dir->cc->ras, dir->rsrc, dir, pool));
983 return SVN_NO_ERROR;
986 static svn_error_t * commit_add_file(const char *path,
987 void *parent_baton,
988 const char *copyfrom_path,
989 svn_revnum_t copyfrom_revision,
990 apr_pool_t *file_pool,
991 void **file_baton)
993 resource_baton_t *parent = parent_baton;
994 resource_baton_t *file;
995 const char *name = svn_path_basename(path, file_pool);
996 apr_pool_t *workpool = svn_pool_create(file_pool);
997 version_rsrc_t *rsrc = NULL;
1000 ** To add a new file into the repository, we CHECKOUT the parent
1001 ** collection, then PUT the file as a member of the resuling working
1002 ** collection.
1004 ** If the file was copied from elsewhere, then we will use the COPY
1005 ** method to copy into the working collection.
1008 /* Do the parent CHECKOUT first */
1009 SVN_ERR(checkout_resource(parent->cc, parent->rsrc, TRUE, NULL, workpool));
1011 /* Construct a file_baton that contains all the resource urls. */
1012 file = apr_pcalloc(file_pool, sizeof(*file));
1013 file->pool = file_pool;
1014 file->cc = parent->cc;
1015 file->created = TRUE;
1016 SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc,
1017 name, 1, SVN_INVALID_REVNUM, workpool));
1018 file->rsrc = dup_resource(rsrc, file_pool);
1019 if (parent->cc->tokens)
1020 file->token = apr_hash_get(parent->cc->tokens, path, APR_HASH_KEY_STRING);
1022 /* If the parent directory existed before this commit then there may be a
1023 file with this URL already. We need to ensure such a file does not
1024 exist, which we do by attempting a PROPFIND. Of course, a
1025 PROPFIND *should* succeed if this "add" is actually the second
1026 half of a "replace".
1028 ### For now, we'll assume that if this path has already been
1029 added to the valid targets hash, that addition occurred during the
1030 "delete" phase (if that's not the case, this editor is being
1031 driven incorrectly, as we should never visit the same path twice
1032 except in a delete+add situation). */
1033 if ((! parent->created)
1034 && (! apr_hash_get(file->cc->valid_targets, path, APR_HASH_KEY_STRING)))
1036 svn_ra_neon__resource_t *res;
1037 svn_error_t *err = svn_ra_neon__get_starting_props(&res,
1038 file->cc->ras,
1039 file->rsrc->url, NULL,
1040 workpool);
1041 if (!err)
1043 /* If the PROPFIND succeeds the file already exists */
1044 return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
1045 _("File '%s' already exists"),
1046 file->rsrc->url);
1048 else if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
1050 svn_error_clear(err);
1052 else
1054 /* A real error */
1055 return err;
1059 if (! copyfrom_path)
1061 /* This a truly new file. */
1063 /* ### wait for apply_txdelta before doing a PUT. it might arrive a
1064 ### "long time" from now. certainly after many other operations, so
1065 ### we don't want to start a PUT just yet.
1066 ### so... anything else to do here?
1069 else
1071 svn_string_t bc_url, bc_relative;
1072 const char *copy_src;
1074 /* This add has history, so we need to do a COPY. */
1076 /* Convert the copyfrom_* url/rev "public" pair into a Baseline
1077 Collection (BC) URL that represents the revision -- and a
1078 relative path under that BC. */
1079 SVN_ERR(svn_ra_neon__get_baseline_info(NULL,
1080 &bc_url, &bc_relative, NULL,
1081 parent->cc->ras,
1082 copyfrom_path,
1083 copyfrom_revision,
1084 workpool));
1087 /* Combine the BC-URL and relative path; this is the main
1088 "source" argument to the COPY request. The "Destination:"
1089 header given to COPY is simply the wr_url that is already
1090 part of the file_baton. */
1091 copy_src = svn_path_url_add_component(bc_url.data,
1092 bc_relative.data,
1093 workpool);
1095 /* Have neon do the COPY. */
1096 SVN_ERR(svn_ra_neon__copy(parent->cc->ras,
1097 1, /* overwrite */
1098 SVN_RA_NEON__DEPTH_ZERO,
1099 /* file: this doesn't matter */
1100 copy_src, /* source URI */
1101 file->rsrc->wr_url,/* dest URI */
1102 workpool));
1104 /* Remember that this object was copied. */
1105 file->copied = TRUE;
1108 /* Add this path to the valid targets hash. */
1109 add_valid_target(parent->cc, path, svn_nonrecursive);
1111 svn_pool_destroy(workpool);
1113 /* return the file_baton */
1114 *file_baton = file;
1115 return SVN_NO_ERROR;
1118 static svn_error_t * commit_open_file(const char *path,
1119 void *parent_baton,
1120 svn_revnum_t base_revision,
1121 apr_pool_t *file_pool,
1122 void **file_baton)
1124 resource_baton_t *parent = parent_baton;
1125 resource_baton_t *file;
1126 const char *name = svn_path_basename(path, file_pool);
1127 apr_pool_t *workpool = svn_pool_create(file_pool);
1128 version_rsrc_t *rsrc = NULL;
1130 file = apr_pcalloc(file_pool, sizeof(*file));
1131 file->pool = file_pool;
1132 file->cc = parent->cc;
1133 file->created = FALSE;
1134 SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc,
1135 name, 0, base_revision, workpool));
1136 file->rsrc = dup_resource(rsrc, file_pool);
1137 if (parent->cc->tokens)
1138 file->token = apr_hash_get(parent->cc->tokens, path, APR_HASH_KEY_STRING);
1140 /* do the CHECKOUT now. we'll PUT the new file contents later on. */
1141 SVN_ERR(checkout_resource(parent->cc, file->rsrc, TRUE,
1142 file->token, workpool));
1144 /* ### wait for apply_txdelta before doing a PUT. it might arrive a
1145 ### "long time" from now. certainly after many other operations, so
1146 ### we don't want to start a PUT just yet.
1147 ### so... anything else to do here? what about the COPY case?
1150 svn_pool_destroy(workpool);
1151 *file_baton = file;
1152 return SVN_NO_ERROR;
1155 static svn_error_t * commit_stream_write(void *baton,
1156 const char *data,
1157 apr_size_t *len)
1159 put_baton_t *pb = baton;
1160 svn_ra_neon__session_t *ras = pb->ras;
1161 apr_status_t status;
1163 if (ras->callbacks && ras->callbacks->cancel_func)
1164 SVN_ERR(ras->callbacks->cancel_func(ras->callback_baton));
1166 /* drop the data into our temp file */
1167 status = apr_file_write_full(pb->tmpfile, data, *len, NULL);
1168 if (status)
1169 return svn_error_wrap_apr(status,
1170 _("Could not write svndiff to temp file"));
1172 if (ras->progress_func)
1174 pb->progress += *len;
1175 ras->progress_func(pb->progress, -1, ras->progress_baton, pb->pool);
1178 return SVN_NO_ERROR;
1181 static svn_error_t *
1182 commit_apply_txdelta(void *file_baton,
1183 const char *base_checksum,
1184 apr_pool_t *pool,
1185 svn_txdelta_window_handler_t *handler,
1186 void **handler_baton)
1188 resource_baton_t *file = file_baton;
1189 put_baton_t *baton;
1190 svn_stream_t *stream;
1192 baton = apr_pcalloc(file->pool, sizeof(*baton));
1193 baton->ras = file->cc->ras;
1194 baton->pool = file->pool;
1195 file->put_baton = baton;
1197 if (base_checksum)
1198 baton->base_checksum = apr_pstrdup(file->pool, base_checksum);
1199 else
1200 baton->base_checksum = NULL;
1202 /* ### oh, hell. Neon's request body support is either text (a C string),
1203 ### or a FILE*. since we are getting binary data, we must use a FILE*
1204 ### for now. isn't that special? */
1206 /* Use the client callback to create a tmpfile. */
1207 SVN_ERR(file->cc->ras->callbacks->open_tmp_file
1208 (&baton->tmpfile,
1209 file->cc->ras->callback_baton,
1210 file->pool));
1212 /* ### register a cleanup on file_pool which closes the file; this
1213 ### will ensure that the file always gets tossed, even if we exit
1214 ### with an error. */
1216 stream = svn_stream_create(baton, pool);
1217 svn_stream_set_write(stream, commit_stream_write);
1219 svn_txdelta_to_svndiff(stream, pool, handler, handler_baton);
1221 /* Add this path to the valid targets hash. */
1222 add_valid_target(file->cc, file->rsrc->local_path, svn_nonrecursive);
1224 return SVN_NO_ERROR;
1227 static svn_error_t * commit_change_file_prop(void *file_baton,
1228 const char *name,
1229 const svn_string_t *value,
1230 apr_pool_t *pool)
1232 resource_baton_t *file = file_baton;
1234 /* record the change. it will be applied at close_file time. */
1235 /* ### we should put this into the file_baton's pool */
1236 record_prop_change(file->pool, file, name, value);
1238 /* do the CHECKOUT sooner rather than later */
1239 SVN_ERR(checkout_resource(file->cc, file->rsrc, TRUE, file->token, pool));
1241 /* Add this path to the valid targets hash. */
1242 add_valid_target(file->cc, file->rsrc->local_path, svn_nonrecursive);
1244 return SVN_NO_ERROR;
1247 static svn_error_t * commit_close_file(void *file_baton,
1248 const char *text_checksum,
1249 apr_pool_t *pool)
1251 resource_baton_t *file = file_baton;
1252 commit_ctx_t *cc = file->cc;
1254 /* If this is a newly added file, not copied, and the editor driver
1255 didn't call apply_textdelta(), then we'll pretend they *did* call
1256 apply_textdelta() and described a zero-byte empty file. */
1257 if ((! file->put_baton) && file->created && (! file->copied))
1259 /* Make a dummy put_baton, with NULL fields to indicate that
1260 we're dealing with a content-less (zero-byte) file. */
1261 file->put_baton = apr_pcalloc(file->pool, sizeof(*(file->put_baton)));
1264 if (file->put_baton)
1266 put_baton_t *pb = file->put_baton;
1267 const char *url = file->rsrc->wr_url;
1268 apr_hash_t *extra_headers;
1269 svn_ra_neon__request_t *request;
1270 svn_error_t *err = SVN_NO_ERROR;
1272 /* create/prep the request */
1273 request = svn_ra_neon__request_create(cc->ras, "PUT", url, pool);
1275 extra_headers = apr_hash_make(request->pool);
1277 if (file->token)
1278 svn_ra_neon__set_header
1279 (extra_headers, "If",
1280 apr_psprintf(pool, "<%s> (<%s>)",
1281 svn_path_url_add_component(cc->ras->url->data,
1282 file->rsrc->url,
1283 request->pool),
1284 file->token));
1286 if (pb->base_checksum)
1287 svn_ra_neon__set_header(extra_headers,
1288 SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
1289 pb->base_checksum);
1291 if (text_checksum)
1292 svn_ra_neon__set_header(extra_headers,
1293 SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
1294 text_checksum);
1296 if (pb->tmpfile)
1298 svn_ra_neon__set_header(extra_headers, "Content-Type",
1299 SVN_SVNDIFF_MIME_TYPE);
1301 /* Give the file to neon. The provider will rewind the file. */
1302 err = svn_ra_neon__set_neon_body_provider(request, pb->tmpfile);
1303 if (err)
1304 goto cleanup;
1306 else
1308 ne_set_request_body_buffer(request->ne_req, "", 0);
1311 /* run the request and get the resulting status code (and svn_error_t) */
1312 err = svn_ra_neon__request_dispatch(NULL, request, extra_headers, NULL,
1313 201 /* Created */,
1314 204 /* No Content */,
1315 pool);
1316 cleanup:
1317 svn_ra_neon__request_destroy(request);
1318 SVN_ERR(err);
1320 if (pb->tmpfile)
1322 /* we're done with the file. this should delete it. */
1323 (void) apr_file_close(pb->tmpfile);
1327 /* Perform all of the property changes on the file. Note that we
1328 checked out the file when the first prop change was noted. */
1329 SVN_ERR(do_proppatch(cc->ras, file->rsrc, file, pool));
1331 return SVN_NO_ERROR;
1335 static svn_error_t * commit_close_edit(void *edit_baton,
1336 apr_pool_t *pool)
1338 commit_ctx_t *cc = edit_baton;
1339 svn_commit_info_t *commit_info = svn_create_commit_info(pool);
1341 SVN_ERR(svn_ra_neon__merge_activity(&(commit_info->revision),
1342 &(commit_info->date),
1343 &(commit_info->author),
1344 &(commit_info->post_commit_err),
1345 cc->ras,
1346 cc->ras->root.path,
1347 cc->activity_url,
1348 cc->valid_targets,
1349 cc->tokens,
1350 cc->keep_locks,
1351 cc->disable_merge_response,
1352 pool));
1353 SVN_ERR(delete_activity(edit_baton, pool));
1354 SVN_ERR(svn_ra_neon__maybe_store_auth_info(cc->ras, pool));
1356 if (commit_info->revision != SVN_INVALID_REVNUM)
1357 SVN_ERR(cc->callback(commit_info, cc->callback_baton, pool));
1359 return SVN_NO_ERROR;
1363 static svn_error_t * commit_abort_edit(void *edit_baton,
1364 apr_pool_t *pool)
1366 return delete_activity(edit_baton, pool);
1370 static svn_error_t * apply_revprops(commit_ctx_t *cc,
1371 apr_hash_t *revprop_table,
1372 apr_pool_t *pool)
1374 const svn_string_t *vcc;
1375 const svn_string_t *baseline_url;
1376 version_rsrc_t baseline_rsrc = { SVN_INVALID_REVNUM };
1377 svn_error_t *err = NULL;
1378 int retry_count = 5;
1380 /* ### this whole sequence can/should be replaced with an expand-property
1381 ### REPORT when that is available on the server. */
1383 /* fetch the DAV:version-controlled-configuration from the session's URL */
1384 SVN_ERR(svn_ra_neon__get_one_prop(&vcc, cc->ras, cc->ras->root.path,
1385 NULL, &svn_ra_neon__vcc_prop, pool));
1387 /* ### we should use DAV:apply-to-version on the CHECKOUT so we can skip
1388 ### retrieval of the baseline */
1390 do {
1392 svn_error_clear(err);
1394 /* Get the latest baseline from VCC's DAV:checked-in property.
1395 This should give us the HEAD revision of the moment. */
1396 SVN_ERR(svn_ra_neon__get_one_prop(&baseline_url, cc->ras,
1397 vcc->data, NULL,
1398 &svn_ra_neon__checked_in_prop, pool));
1399 baseline_rsrc.pool = pool;
1400 baseline_rsrc.vsn_url = baseline_url->data;
1402 /* To set the revision properties, we must checkout the latest baseline
1403 and get back a mutable "working" baseline. */
1404 err = checkout_resource(cc, &baseline_rsrc, FALSE, NULL, pool);
1406 /* There's a small chance of a race condition here, if apache is
1407 experiencing heavy commit concurrency or if the network has
1408 long latency. It's possible that the value of HEAD changed
1409 between the time we fetched the latest baseline and the time we
1410 checkout that baseline. If that happens, apache will throw us
1411 a BAD_BASELINE error (deltaV says you can only checkout the
1412 latest baseline). We just ignore that specific error and
1413 retry a few times, asking for the latest baseline again. */
1414 if (err && err->apr_err != SVN_ERR_APMOD_BAD_BASELINE)
1415 return err;
1417 } while (err && (--retry_count > 0));
1419 /* Yikes, if we couldn't hold onto HEAD after a few retries, throw a
1420 real error.*/
1421 if (err)
1422 return err;
1424 return svn_ra_neon__do_proppatch(cc->ras, baseline_rsrc.wr_url, revprop_table,
1425 NULL, NULL, pool);
1428 svn_error_t * svn_ra_neon__get_commit_editor(svn_ra_session_t *session,
1429 const svn_delta_editor_t **editor,
1430 void **edit_baton,
1431 apr_hash_t *revprop_table,
1432 svn_commit_callback2_t callback,
1433 void *callback_baton,
1434 apr_hash_t *lock_tokens,
1435 svn_boolean_t keep_locks,
1436 apr_pool_t *pool)
1438 svn_ra_neon__session_t *ras = session->priv;
1439 svn_delta_editor_t *commit_editor;
1440 commit_ctx_t *cc;
1441 svn_error_t *err;
1443 /* Build the main commit editor's baton. */
1444 cc = apr_pcalloc(pool, sizeof(*cc));
1445 cc->ras = ras;
1446 cc->valid_targets = apr_hash_make(pool);
1447 cc->get_func = ras->callbacks->get_wc_prop;
1448 cc->push_func = ras->callbacks->push_wc_prop;
1449 cc->cb_baton = ras->callback_baton;
1450 cc->callback = callback;
1451 cc->callback_baton = callback_baton;
1452 cc->tokens = lock_tokens;
1453 cc->keep_locks = keep_locks;
1455 /* If the caller didn't give us any way of storing wcprops, then
1456 there's no point in getting back a MERGE response full of VR's. */
1457 if (ras->callbacks->push_wc_prop == NULL)
1458 cc->disable_merge_response = TRUE;
1460 /* ### should we perform an OPTIONS to validate the server we're about
1461 ### to talk to? */
1464 ** Create an Activity. This corresponds directly to an FS transaction.
1465 ** We will check out all further resources within the context of this
1466 ** activity.
1468 SVN_ERR(create_activity(cc, pool));
1471 ** Find the latest baseline resource, check it out, and then apply the
1472 ** log message onto the thing.
1474 err = apply_revprops(cc, revprop_table, pool);
1475 /* If the caller gets an error during the editor drive, we rely on them
1476 to call abort_edit() so that we can clear up the activity. But if we
1477 got an error here, we need to clear up the activity ourselves. */
1478 if (err)
1480 svn_error_clear(commit_abort_edit(cc, pool));
1481 return err;
1485 ** Set up the editor.
1487 ** This structure is used during the commit process. An external caller
1488 ** uses these callbacks to describe all the changes in the working copy
1489 ** that must be committed to the server.
1491 commit_editor = svn_delta_default_editor(pool);
1492 commit_editor->open_root = commit_open_root;
1493 commit_editor->delete_entry = commit_delete_entry;
1494 commit_editor->add_directory = commit_add_dir;
1495 commit_editor->open_directory = commit_open_dir;
1496 commit_editor->change_dir_prop = commit_change_dir_prop;
1497 commit_editor->close_directory = commit_close_dir;
1498 commit_editor->add_file = commit_add_file;
1499 commit_editor->open_file = commit_open_file;
1500 commit_editor->apply_textdelta = commit_apply_txdelta;
1501 commit_editor->change_file_prop = commit_change_file_prop;
1502 commit_editor->close_file = commit_close_file;
1503 commit_editor->close_edit = commit_close_edit;
1504 commit_editor->abort_edit = commit_abort_edit;
1506 *editor = commit_editor;
1507 *edit_baton = cc;
1508 return SVN_NO_ERROR;