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>
25 #define APR_WANT_STDIO
26 #define APR_WANT_STRFUNC
31 #include "svn_pools.h"
32 #include "svn_error.h"
33 #include "svn_delta.h"
36 #include "../libsvn_ra/ra_loader.h"
40 #include "svn_props.h"
42 #include "svn_private_config.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.
67 svn_revnum_t revision
;
71 const char *local_path
;
73 apr_pool_t
*pool
; /* pool in which this resource is allocated. */
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
;
89 svn_boolean_t disable_merge_response
;
91 /* The (potential) author of this commit. */
94 /* The commit callback and baton */
95 svn_commit_callback2_t callback
;
98 /* The hash of lock-tokens owned by the working copy. */
101 /* Whether or not to keep the locks after commit is done. */
102 svn_boolean_t keep_locks
;
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 */
112 svn_ra_neon__session_t
*ras
;
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 */
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" },
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
));
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
;
156 static svn_error_t
* delete_activity(void *edit_baton
,
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
,
175 svn_ra_neon__resource_t
*propres
;
177 const svn_string_t
*url_str
;
181 if (cc
->get_func
!= NULL
)
183 const svn_string_t
*vsn_url_value
;
185 SVN_ERR((*cc
->get_func
)(cc
->cb_baton
,
187 SVN_RA_NEON__LP_VSN_URL
,
190 if (vsn_url_value
!= NULL
)
192 rsrc
->vsn_url
= apr_pstrdup(rsrc
->pool
, vsn_url_value
->data
);
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
,
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. */
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
,
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
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
);
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
,
258 SVN_RA_NEON__LP_VSN_URL
,
266 /* When FORCE is true, then we force a query to the server, ignoring any
268 static svn_error_t
* get_activity_collection(commit_ctx_t
*cc
,
269 const svn_string_t
**collection
,
273 if (!force
&& cc
->get_func
!= NULL
)
275 /* with a get_func, we can just ask for the activity URL from the
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
,
285 if (*collection
!= NULL
)
287 /* the property was there. return it. */
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
,
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
,
313 static svn_error_t
* create_activity(commit_ctx_t
*cc
,
316 const svn_string_t
* activity_collection
;
317 const char *uuid_buf
= svn_uuid_generate(pool
);
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
,
327 SVN_ERR(svn_ra_neon__simple_request(&code
, cc
->ras
,
328 "MKACTIVITY", url
, NULL
, NULL
,
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. */
337 SVN_ERR(get_activity_collection(cc
, &activity_collection
, TRUE
, pool
));
338 url
= svn_path_url_add_component(activity_collection
->data
,
340 SVN_ERR(svn_ra_neon__simple_request(&code
, cc
->ras
,
341 "MKACTIVITY", url
, NULL
, NULL
,
345 cc
->activity_url
= url
;
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
,
354 const version_rsrc_t
*parent
,
357 svn_revnum_t revision
,
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
));
371 rsrc
->revision
= revision
;
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". */
388 SVN_ERR(get_version_url(cc
, parent
, rsrc
, FALSE
, pool
));
395 static svn_error_t
* do_checkout(commit_ctx_t
*cc
,
397 svn_boolean_t allow_404
,
403 svn_ra_neon__request_t
*request
;
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 */
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:\">"
422 "<D:href>%s</D:href>"
423 "</D:activity-set></D:checkout>", cc
->activity_url
);
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
,
435 allow_404
? 404 /* Not Found */ : 0,
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
);
449 svn_ra_neon__request_destroy(request
);
455 static svn_error_t
* checkout_resource(commit_ctx_t
*cc
,
456 version_rsrc_t
*rsrc
,
457 svn_boolean_t allow_404
,
462 const char *locn
= NULL
;
466 if (rsrc
->wr_url
!= NULL
)
468 /* already checked out! */
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)
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 */
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
497 _("File or directory '%s' is out of date; try updating"),
498 svn_path_local_style(rsrc
->local_path
, pool
));
502 /* we got the header, right? */
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)
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
);
524 static void record_prop_change(apr_pool_t
*pool
,
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
);
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
));
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
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
,
595 const char *url
= rsrc
->wr_url
;
596 apr_hash_t
*extra_headers
= NULL
;
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
,
608 return svn_ra_neon__do_proppatch(ras
, url
, rb
->prop_changes
,
609 rb
->prop_deletes
, extra_headers
, pool
);
614 add_valid_target(commit_ctx_t
*cc
,
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
,
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
;
651 root
->created
= FALSE
;
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
664 static apr_hash_t
*get_child_tokens(apr_hash_t
*lock_tokens
,
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
))
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
);
690 static svn_error_t
* commit_delete_entry(const char *path
,
691 svn_revnum_t revision
,
695 resource_baton_t
*parent
= parent_baton
;
696 const char *name
= svn_path_basename(path
, pool
);
697 apr_hash_t
*extra_headers
= NULL
;
702 if (SVN_IS_VALID_REVNUM(revision
))
704 const char *revstr
= apr_psprintf(pool
, "%ld", revision
);
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:
722 if (parent
->cc
->tokens
)
725 apr_hash_get(parent
->cc
->tokens
, path
, APR_HASH_KEY_STRING
);
729 const char *token_header_val
;
730 const char *token_uri
;
732 token_uri
= svn_path_url_add_component(parent
->cc
->ras
->url
->data
,
734 token_header_val
= apr_psprintf(pool
, "<%s> (<%s>)",
736 extra_headers
= apr_hash_make(pool
);
737 apr_hash_set(extra_headers
, "If", APR_HASH_KEY_STRING
,
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
)
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
,
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
;
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. */
795 || (apr_hash_count(child_tokens
) == 0))
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
);
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
);
815 body
= apr_psprintf(request
->pool
,
816 "<?xml version=\"1.0\" encoding=\"utf-8\"?> %s",
819 err
= svn_ra_neon__request_dispatch(&code
, request
, NULL
, body
,
824 svn_ra_neon__request_destroy(request
);
830 /* Add this path to the valid targets hash. */
831 add_valid_target(parent
->cc
, path
, svn_nonrecursive
);
838 static svn_error_t
* commit_add_dir(const char *path
,
840 const char *copyfrom_path
,
841 svn_revnum_t copyfrom_revision
,
842 apr_pool_t
*dir_pool
,
845 resource_baton_t
*parent
= parent_baton
;
846 resource_baton_t
*child
;
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
);
867 /* This a new directory with no history, so just create a new,
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
));
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
,
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
,
899 /* Have neon do the COPY. */
900 SVN_ERR(svn_ra_neon__copy(parent
->cc
->ras
,
902 SVN_RA_NEON__DEPTH_INFINITE
, /* deep copy */
903 copy_src
, /* source URI */
904 child
->rsrc
->wr_url
, /* dest URI */
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
;
920 static svn_error_t
* commit_open_dir(const char *path
,
922 svn_revnum_t base_revision
,
923 apr_pool_t
*dir_pool
,
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
;
954 static svn_error_t
* commit_change_dir_prop(void *dir_baton
,
956 const svn_string_t
*value
,
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
);
974 static svn_error_t
* commit_close_dir(void *dir_baton
,
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
));
986 static svn_error_t
* commit_add_file(const char *path
,
988 const char *copyfrom_path
,
989 svn_revnum_t copyfrom_revision
,
990 apr_pool_t
*file_pool
,
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
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
,
1039 file
->rsrc
->url
, NULL
,
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"),
1048 else if (err
->apr_err
== SVN_ERR_RA_DAV_PATH_NOT_FOUND
)
1050 svn_error_clear(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?
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
,
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
,
1095 /* Have neon do the COPY. */
1096 SVN_ERR(svn_ra_neon__copy(parent
->cc
->ras
,
1098 SVN_RA_NEON__DEPTH_ZERO
,
1099 /* file: this doesn't matter */
1100 copy_src
, /* source URI */
1101 file
->rsrc
->wr_url
,/* dest URI */
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 */
1115 return SVN_NO_ERROR
;
1118 static svn_error_t
* commit_open_file(const char *path
,
1120 svn_revnum_t base_revision
,
1121 apr_pool_t
*file_pool
,
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
);
1152 return SVN_NO_ERROR
;
1155 static svn_error_t
* commit_stream_write(void *baton
,
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
);
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
,
1185 svn_txdelta_window_handler_t
*handler
,
1186 void **handler_baton
)
1188 resource_baton_t
*file
= file_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
;
1198 baton
->base_checksum
= apr_pstrdup(file
->pool
, base_checksum
);
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
1209 file
->cc
->ras
->callback_baton
,
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
,
1229 const svn_string_t
*value
,
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
,
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
);
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
,
1286 if (pb
->base_checksum
)
1287 svn_ra_neon__set_header(extra_headers
,
1288 SVN_DAV_BASE_FULLTEXT_MD5_HEADER
,
1292 svn_ra_neon__set_header(extra_headers
,
1293 SVN_DAV_RESULT_FULLTEXT_MD5_HEADER
,
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
);
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
,
1314 204 /* No Content */,
1317 svn_ra_neon__request_destroy(request
);
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
,
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
),
1351 cc
->disable_merge_response
,
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
,
1366 return delete_activity(edit_baton
, pool
);
1370 static svn_error_t
* apply_revprops(commit_ctx_t
*cc
,
1371 apr_hash_t
*revprop_table
,
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 */
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
,
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
)
1417 } while (err
&& (--retry_count
> 0));
1419 /* Yikes, if we couldn't hold onto HEAD after a few retries, throw a
1424 return svn_ra_neon__do_proppatch(cc
->ras
, baseline_rsrc
.wr_url
, revprop_table
,
1428 svn_error_t
* svn_ra_neon__get_commit_editor(svn_ra_session_t
*session
,
1429 const svn_delta_editor_t
**editor
,
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
,
1438 svn_ra_neon__session_t
*ras
= session
->priv
;
1439 svn_delta_editor_t
*commit_editor
;
1443 /* Build the main commit editor's baton. */
1444 cc
= apr_pcalloc(pool
, sizeof(*cc
));
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
1464 ** Create an Activity. This corresponds directly to an FS transaction.
1465 ** We will check out all further resources within the context of this
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. */
1480 svn_error_clear(commit_abort_edit(cc
, pool
));
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
;
1508 return SVN_NO_ERROR
;