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
;
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 */
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:\">"
421 "<D:href>%s</D:href>"
422 "</D:activity-set></D:checkout>", cc
->activity_url
);
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
,
434 allow_404
? 404 /* Not Found */ : 0,
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
);
450 static svn_error_t
* checkout_resource(commit_ctx_t
*cc
,
451 version_rsrc_t
*rsrc
,
452 svn_boolean_t allow_404
,
457 const char *locn
= NULL
;
461 if (rsrc
->wr_url
!= NULL
)
463 /* already checked out! */
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)
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 */
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
492 _("File or directory '%s' is out of date; try updating"),
493 svn_path_local_style(rsrc
->local_path
, pool
));
497 /* we got the header, right? */
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)
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
);
519 static void record_prop_change(apr_pool_t
*pool
,
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
);
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
));
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
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
,
590 const char *url
= rsrc
->wr_url
;
591 apr_hash_t
*extra_headers
= NULL
;
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
,
603 return svn_ra_neon__do_proppatch(ras
, url
, rb
->prop_changes
,
604 rb
->prop_deletes
, extra_headers
, pool
);
609 add_valid_target(commit_ctx_t
*cc
,
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
,
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
;
646 root
->created
= FALSE
;
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
659 static apr_hash_t
*get_child_tokens(apr_hash_t
*lock_tokens
,
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
))
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
);
685 static svn_error_t
* commit_delete_entry(const char *path
,
686 svn_revnum_t revision
,
690 resource_baton_t
*parent
= parent_baton
;
691 const char *name
= svn_path_basename(path
, pool
);
692 apr_hash_t
*extra_headers
= NULL
;
697 if (SVN_IS_VALID_REVNUM(revision
))
699 const char *revstr
= apr_psprintf(pool
, "%ld", revision
);
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:
717 if (parent
->cc
->tokens
)
720 apr_hash_get(parent
->cc
->tokens
, path
, APR_HASH_KEY_STRING
);
724 const char *token_header_val
;
725 const char *token_uri
;
727 token_uri
= svn_path_url_add_component(parent
->cc
->ras
->url
->data
,
729 token_header_val
= apr_psprintf(pool
, "<%s> (<%s>)",
731 extra_headers
= apr_hash_make(pool
);
732 apr_hash_set(extra_headers
, "If", APR_HASH_KEY_STRING
,
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
)
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
,
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
;
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. */
789 || (apr_hash_count(child_tokens
) == 0))
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
);
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",
811 SVN_ERR(svn_ra_neon__request_dispatch(&code
, request
, NULL
, body
,
815 svn_ra_neon__request_destroy(request
);
820 /* Add this path to the valid targets hash. */
821 add_valid_target(parent
->cc
, path
, svn_nonrecursive
);
828 static svn_error_t
* commit_add_dir(const char *path
,
830 const char *copyfrom_path
,
831 svn_revnum_t copyfrom_revision
,
832 apr_pool_t
*dir_pool
,
835 resource_baton_t
*parent
= parent_baton
;
836 resource_baton_t
*child
;
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
);
857 /* This a new directory with no history, so just create a new,
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
));
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
,
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
,
889 /* Have neon do the COPY. */
890 SVN_ERR(svn_ra_neon__copy(parent
->cc
->ras
,
892 SVN_RA_NEON__DEPTH_INFINITE
, /* deep copy */
893 copy_src
, /* source URI */
894 child
->rsrc
->wr_url
, /* dest URI */
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
;
910 static svn_error_t
* commit_open_dir(const char *path
,
912 svn_revnum_t base_revision
,
913 apr_pool_t
*dir_pool
,
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
;
944 static svn_error_t
* commit_change_dir_prop(void *dir_baton
,
946 const svn_string_t
*value
,
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
);
964 static svn_error_t
* commit_close_dir(void *dir_baton
,
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
));
976 static svn_error_t
* commit_add_file(const char *path
,
978 const char *copyfrom_path
,
979 svn_revnum_t copyfrom_revision
,
980 apr_pool_t
*file_pool
,
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
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
,
1029 file
->rsrc
->url
, NULL
,
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"),
1038 else if (err
->apr_err
== SVN_ERR_RA_DAV_PATH_NOT_FOUND
)
1040 svn_error_clear(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?
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
,
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
,
1085 /* Have neon do the COPY. */
1086 SVN_ERR(svn_ra_neon__copy(parent
->cc
->ras
,
1088 SVN_RA_NEON__DEPTH_ZERO
,
1089 /* file: this doesn't matter */
1090 copy_src
, /* source URI */
1091 file
->rsrc
->wr_url
,/* dest URI */
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 */
1105 return SVN_NO_ERROR
;
1108 static svn_error_t
* commit_open_file(const char *path
,
1110 svn_revnum_t base_revision
,
1111 apr_pool_t
*file_pool
,
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
);
1142 return SVN_NO_ERROR
;
1145 static svn_error_t
* commit_stream_write(void *baton
,
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
);
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
,
1175 svn_txdelta_window_handler_t
*handler
,
1176 void **handler_baton
)
1178 resource_baton_t
*file
= file_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
;
1188 baton
->base_checksum
= apr_pstrdup(file
->pool
, base_checksum
);
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
1199 file
->cc
->ras
->callback_baton
,
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
,
1219 const svn_string_t
*value
,
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
,
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
);
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
,
1275 if (pb
->base_checksum
)
1276 svn_ra_neon__set_header(extra_headers
,
1277 SVN_DAV_BASE_FULLTEXT_MD5_HEADER
,
1281 svn_ra_neon__set_header(extra_headers
,
1282 SVN_DAV_RESULT_FULLTEXT_MD5_HEADER
,
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
));
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
,
1301 204 /* No Content */,
1303 svn_ra_neon__request_destroy(request
);
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
,
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
),
1336 cc
->disable_merge_response
,
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
,
1351 return delete_activity(edit_baton
, pool
);
1355 static svn_error_t
* apply_revprops(commit_ctx_t
*cc
,
1356 apr_hash_t
*revprop_table
,
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 */
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
,
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
)
1402 } while (err
&& (--retry_count
> 0));
1404 /* Yikes, if we couldn't hold onto HEAD after a few retries, throw a
1409 return svn_ra_neon__do_proppatch(cc
->ras
, baseline_rsrc
.wr_url
, revprop_table
,
1413 svn_error_t
* svn_ra_neon__get_commit_editor(svn_ra_session_t
*session
,
1414 const svn_delta_editor_t
**editor
,
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
,
1423 svn_ra_neon__session_t
*ras
= session
->priv
;
1424 svn_delta_editor_t
*commit_editor
;
1428 /* Build the main commit editor's baton. */
1429 cc
= apr_pcalloc(pool
, sizeof(*cc
));
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
1449 ** Create an Activity. This corresponds directly to an FS transaction.
1450 ** We will check out all further resources within the context of this
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. */
1465 svn_error_clear(commit_abort_edit(cc
, pool
));
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
;
1493 return SVN_NO_ERROR
;