Reorganize the output to "svnserve --help".
[svn.git] / subversion / libsvn_ra_serf / commit.c
blob162d0393bdde89b62ce37ed3c2ba56b470f767fc
1 /*
2 * commit.c : entry point for commit RA functions for ra_serf
4 * ====================================================================
5 * Copyright (c) 2006 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_uri.h>
23 #include <expat.h>
25 #include <serf.h>
27 #include "svn_pools.h"
28 #include "svn_ra.h"
29 #include "svn_dav.h"
30 #include "svn_xml.h"
31 #include "../libsvn_ra/ra_loader.h"
32 #include "svn_config.h"
33 #include "svn_delta.h"
34 #include "svn_base64.h"
35 #include "svn_version.h"
36 #include "svn_path.h"
37 #include "svn_private_config.h"
38 #include "private/svn_dep_compat.h"
40 #include "ra_serf.h"
43 /* Structure associated with a CHECKOUT request. */
44 typedef struct {
46 apr_pool_t *pool;
48 const char *activity_url;
49 apr_size_t activity_url_len;
51 const char *checkout_url;
53 const char *resource_url;
55 svn_ra_serf__simple_request_context_t progress;
57 } checkout_context_t;
59 /* Baton passed back with the commit editor. */
60 typedef struct {
61 /* Pool for our commit. */
62 apr_pool_t *pool;
64 svn_ra_serf__session_t *session;
65 svn_ra_serf__connection_t *conn;
67 apr_hash_t *revprop_table;
69 svn_commit_callback2_t callback;
70 void *callback_baton;
72 apr_hash_t *lock_tokens;
73 svn_boolean_t keep_locks;
75 const char *uuid;
76 const char *activity_url;
77 apr_size_t activity_url_len;
79 /* The checkout for the baseline. */
80 checkout_context_t *baseline;
82 /* The checked-in root to base CHECKOUTs from */
83 const char *checked_in_url;
85 /* The root baseline collection */
86 const char *baseline_url;
88 /* Deleted files - so we can detect delete+add (replace) ops. */
89 apr_hash_t *deleted_entries;
91 /* Copied entries - so we do not checkout these resources. */
92 apr_hash_t *copied_entries;
93 } commit_context_t;
95 /* Structure associated with a PROPPATCH request. */
96 typedef struct {
97 apr_pool_t *pool;
99 const char *name;
100 const char *path;
102 commit_context_t *commit;
104 /* Changed and removed properties. */
105 apr_hash_t *changed_props;
106 apr_hash_t *removed_props;
108 svn_ra_serf__simple_request_context_t progress;
109 } proppatch_context_t;
111 typedef struct {
112 const char *path;
114 svn_revnum_t revision;
116 const char *lock_token;
117 apr_hash_t *lock_token_hash;
118 svn_boolean_t keep_locks;
120 svn_ra_serf__simple_request_context_t progress;
121 } delete_context_t;
123 /* Represents a directory. */
124 typedef struct dir_context_t {
125 /* Pool for our directory. */
126 apr_pool_t *pool;
128 /* The root commit we're in progress for. */
129 commit_context_t *commit;
131 /* The checked out context for this directory.
133 * May be NULL; if so call checkout_dir() first.
135 checkout_context_t *checkout;
137 /* Our URL to CHECKOUT */
138 const char *checked_in_url;
140 /* How many pending changes we have left in this directory. */
141 unsigned int ref_count;
143 /* Is this directory being added? (Otherwise, just opened.) */
144 svn_boolean_t added;
146 /* Our parent */
147 struct dir_context_t *parent_dir;
149 /* The directory name; if NULL, we're the 'root' */
150 const char *name;
152 /* The base revision of the dir. */
153 svn_revnum_t base_revision;
155 const char *copy_path;
156 svn_revnum_t copy_revision;
158 /* Changed and removed properties */
159 apr_hash_t *changed_props;
160 apr_hash_t *removed_props;
162 } dir_context_t;
164 /* Represents a file to be committed. */
165 typedef struct {
166 /* Pool for our file. */
167 apr_pool_t *pool;
169 /* The root commit we're in progress for. */
170 commit_context_t *commit;
172 /* Is this file being added? (Otherwise, just opened.) */
173 svn_boolean_t added;
175 dir_context_t *parent_dir;
177 const char *name;
179 /* The checked out context for this file. */
180 checkout_context_t *checkout;
182 /* The base revision of the file. */
183 svn_revnum_t base_revision;
185 /* Copy path and revision */
186 const char *copy_path;
187 svn_revnum_t copy_revision;
189 /* stream */
190 svn_stream_t *stream;
192 /* Temporary file containing the svndiff. */
193 apr_file_t *svndiff;
195 /* Our base checksum as reported by the WC. */
196 const char *base_checksum;
198 /* Our resulting checksum as reported by the WC. */
199 const char *result_checksum;
201 /* Changed and removed properties. */
202 apr_hash_t *changed_props;
203 apr_hash_t *removed_props;
205 /* URL to PUT the file at. */
206 const char *put_url;
208 } file_context_t;
211 /* Setup routines and handlers for various requests we'll invoke. */
213 static svn_error_t *
214 return_response_err(svn_ra_serf__handler_t *handler,
215 svn_ra_serf__simple_request_context_t *ctx)
217 SVN_ERR(ctx->server_error.error);
219 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
220 "%s of '%s': %d %s",
221 handler->method, handler->path,
222 ctx->status, ctx->reason);
225 #define CHECKOUT_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><D:checkout xmlns:D=\"DAV:\"><D:activity-set><D:href>"
227 #define CHECKOUT_TRAILER "</D:href></D:activity-set></D:checkout>"
229 static serf_bucket_t *
230 create_checkout_body(void *baton,
231 serf_bucket_alloc_t *alloc,
232 apr_pool_t *pool)
234 checkout_context_t *ctx = baton;
235 serf_bucket_t *body_bkt, *tmp_bkt;
237 body_bkt = serf_bucket_aggregate_create(alloc);
239 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(CHECKOUT_HEADER,
240 sizeof(CHECKOUT_HEADER) - 1,
241 alloc);
242 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
244 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(ctx->activity_url,
245 ctx->activity_url_len,
246 alloc);
247 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
249 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(CHECKOUT_TRAILER,
250 sizeof(CHECKOUT_TRAILER) - 1,
251 alloc);
252 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
254 return body_bkt;
257 static apr_status_t
258 handle_checkout(serf_request_t *request,
259 serf_bucket_t *response,
260 void *handler_baton,
261 apr_pool_t *pool)
263 checkout_context_t *ctx = handler_baton;
264 apr_status_t status;
266 status = svn_ra_serf__handle_status_only(request, response, &ctx->progress,
267 pool);
269 /* Get the resulting location. */
270 if (ctx->progress.done && ctx->progress.status == 201)
272 serf_bucket_t *hdrs;
273 apr_uri_t uri;
274 const char *location;
276 hdrs = serf_bucket_response_get_headers(response);
277 location = serf_bucket_headers_get(hdrs, "Location");
278 if (!location)
280 abort();
282 apr_uri_parse(pool, location, &uri);
284 ctx->resource_url = apr_pstrdup(ctx->pool, uri.path);
287 return status;
290 /* Return the relative path from DIR's topmost parent to DIR, in
291 Subversion's internal path style, allocated in POOL. Use POOL for
292 temporary work as well. */
293 static const char *
294 relative_dir_path(dir_context_t *dir, apr_pool_t *pool)
296 const char *rel_path = "";
297 apr_array_header_t *components;
298 dir_context_t *dir_ptr = dir;
299 int i;
301 components = apr_array_make(pool, 1, sizeof(const char *));
303 for (dir_ptr = dir; dir_ptr; dir_ptr = dir_ptr->parent_dir)
304 APR_ARRAY_PUSH(components, const char *) = dir_ptr->name;
306 for (i = 0; i < components->nelts; i++)
308 rel_path = svn_path_join(rel_path,
309 APR_ARRAY_IDX(components, i, const char *),
310 pool);
313 return rel_path;
317 /* Return the relative path from FILE's topmost parent to FILE, in
318 Subversion's internal path style, allocated in POOL. Use POOL for
319 temporary work as well. */
320 static const char *
321 relative_file_path(file_context_t *f, apr_pool_t *pool)
323 const char *dir_path = relative_dir_path(f->parent_dir, pool);
324 return svn_path_join(dir_path, f->name, pool);
328 static svn_error_t *
329 checkout_dir(dir_context_t *dir)
331 checkout_context_t *checkout_ctx;
332 svn_ra_serf__handler_t *handler;
333 svn_error_t *err;
335 if (dir->checkout)
337 return SVN_NO_ERROR;
340 if (dir->parent_dir)
342 /* Is our parent a copy? If so, we're already implicitly checked out. */
343 if (apr_hash_get(dir->commit->copied_entries,
344 dir->parent_dir->name, APR_HASH_KEY_STRING))
346 /* Implicitly checkout this dir now. */
347 dir->checkout = apr_pcalloc(dir->pool, sizeof(*dir->checkout));
348 dir->checkout->pool = dir->pool;
349 dir->checkout->activity_url = dir->commit->activity_url;
350 dir->checkout->activity_url_len = dir->commit->activity_url_len;
351 dir->checkout->resource_url =
352 svn_path_url_add_component(dir->parent_dir->checkout->resource_url,
353 svn_path_basename(dir->name, dir->pool),
354 dir->pool);
356 apr_hash_set(dir->commit->copied_entries,
357 apr_pstrdup(dir->commit->pool, dir->name),
358 APR_HASH_KEY_STRING, (void*)1);
360 return SVN_NO_ERROR;
364 /* Checkout our directory into the activity URL now. */
365 handler = apr_pcalloc(dir->pool, sizeof(*handler));
366 handler->session = dir->commit->session;
367 handler->conn = dir->commit->conn;
369 checkout_ctx = apr_pcalloc(dir->pool, sizeof(*checkout_ctx));
370 checkout_ctx->pool = dir->pool;
372 checkout_ctx->activity_url = dir->commit->activity_url;
373 checkout_ctx->activity_url_len = dir->commit->activity_url_len;
375 /* We could be called twice for the root: once to checkout the baseline;
376 * once to checkout the directory itself if we need to do so.
378 if (!dir->parent_dir && !dir->commit->baseline)
380 checkout_ctx->checkout_url = dir->commit->baseline_url;
381 dir->commit->baseline = checkout_ctx;
383 else
385 checkout_ctx->checkout_url = dir->checked_in_url;
386 dir->checkout = checkout_ctx;
389 handler->body_delegate = create_checkout_body;
390 handler->body_delegate_baton = checkout_ctx;
391 handler->body_type = "text/xml";
393 handler->response_handler = handle_checkout;
394 handler->response_baton = checkout_ctx;
396 handler->method = "CHECKOUT";
397 handler->path = checkout_ctx->checkout_url;
399 svn_ra_serf__request_create(handler);
401 err = svn_ra_serf__context_run_wait(&checkout_ctx->progress.done,
402 dir->commit->session,
403 dir->pool);
404 if (err)
406 if (err->apr_err == SVN_ERR_FS_CONFLICT)
407 SVN_ERR_W(err, apr_psprintf(dir->pool,
408 _("Directory '%s' is out of date; try updating"),
409 svn_path_local_style(relative_dir_path(dir, dir->pool),
410 dir->pool)));
411 return err;
414 if (checkout_ctx->progress.status != 201)
416 if (checkout_ctx->progress.status == 404)
418 return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
419 return_response_err(handler,
420 &checkout_ctx->progress),
421 _("Path '%s' not present"),
422 dir->name);
425 return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
426 return_response_err(handler,
427 &checkout_ctx->progress),
428 _("Directory '%s' is out of date; try updating"),
429 svn_path_local_style(relative_dir_path(dir, dir->pool),
430 dir->pool));
433 return SVN_NO_ERROR;
437 /* Set *CHECKED_IN_URL to the appropriate DAV version url for
438 * RELPATH (relative to the root of SESSION).
440 * Try to find this version url in three ways:
441 * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
442 * version url from the working copy properties.
443 * Second, if the version url of the parent directory PARENT_VSN_URL is
444 * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
445 * RELPATH.
446 * Else, fetch the version url for the root of SESSION using CONN and
447 * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
448 * with RELPATH.
450 * Allocate the result in POOL, and use POOL for temporary allocation.
452 static svn_error_t *
453 get_version_url(const char **checked_in_url,
454 svn_ra_serf__session_t *session,
455 svn_ra_serf__connection_t *conn,
456 const char *relpath,
457 svn_revnum_t base_revision,
458 const char *parent_vsn_url,
459 apr_pool_t *pool)
461 const char *root_checkout;
463 if (session->wc_callbacks->get_wc_prop)
465 const svn_string_t *current_version;
467 SVN_ERR(session->wc_callbacks->get_wc_prop(session->wc_callback_baton,
468 relpath,
469 SVN_RA_SERF__WC_CHECKED_IN_URL,
470 &current_version, pool));
472 if (current_version)
474 *checked_in_url = current_version->data;
475 return SVN_NO_ERROR;
479 if (parent_vsn_url)
481 root_checkout = parent_vsn_url;
483 else
485 svn_ra_serf__propfind_context_t *propfind_ctx;
486 apr_hash_t *props;
488 props = apr_hash_make(pool);
490 propfind_ctx = NULL;
491 svn_ra_serf__deliver_props(&propfind_ctx, props, session,
492 conn, session->repos_url.path,
493 base_revision, "0",
494 checked_in_props, FALSE, NULL, pool);
496 SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, session, pool));
498 root_checkout =
499 svn_ra_serf__get_ver_prop(props, session->repos_url.path,
500 base_revision, "DAV:", "checked-in");
502 if (!root_checkout)
503 return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND, NULL,
504 _("Path '%s' not present"),
505 session->repos_url.path);
508 *checked_in_url = svn_path_url_add_component(root_checkout, relpath, pool);
510 return SVN_NO_ERROR;
513 static svn_error_t *
514 checkout_file(file_context_t *file)
516 svn_ra_serf__handler_t *handler;
517 svn_error_t *err;
519 if (file->parent_dir)
521 dir_context_t *dir;
523 dir = file->parent_dir;
524 while (dir && ! apr_hash_get(file->commit->copied_entries,
525 dir->name, APR_HASH_KEY_STRING))
527 dir = dir->parent_dir;
531 /* Is our parent a copy? If so, we're already implicitly checked out. */
532 if (dir)
534 const char *diff_path;
536 /* Implicitly checkout this dir now. */
537 file->checkout = apr_pcalloc(file->pool, sizeof(*file->checkout));
538 file->checkout->pool = file->pool;
540 file->checkout->activity_url = file->commit->activity_url;
541 file->checkout->activity_url_len = file->commit->activity_url_len;
542 diff_path = svn_path_is_child(dir->name, file->name, file->pool);
543 file->checkout->resource_url =
544 svn_path_url_add_component(dir->checkout->resource_url,
545 diff_path,
546 file->pool);
547 return SVN_NO_ERROR;
551 /* Checkout our file into the activity URL now. */
552 handler = apr_pcalloc(file->pool, sizeof(*handler));
553 handler->session = file->commit->session;
554 handler->conn = file->commit->conn;
556 file->checkout = apr_pcalloc(file->pool, sizeof(*file->checkout));
557 file->checkout->pool = file->pool;
559 file->checkout->activity_url = file->commit->activity_url;
560 file->checkout->activity_url_len = file->commit->activity_url_len;
562 SVN_ERR(get_version_url(&(file->checkout->checkout_url),
563 file->commit->session, file->commit->conn,
564 file->name, file->base_revision,
565 NULL, file->pool));
567 handler->body_delegate = create_checkout_body;
568 handler->body_delegate_baton = file->checkout;
569 handler->body_type = "text/xml";
571 handler->response_handler = handle_checkout;
572 handler->response_baton = file->checkout;
574 handler->method = "CHECKOUT";
575 handler->path = file->checkout->checkout_url;
577 svn_ra_serf__request_create(handler);
579 /* There's no need to wait here as we only need this when we start the
580 * PROPPATCH or PUT of the file.
582 err = svn_ra_serf__context_run_wait(&file->checkout->progress.done,
583 file->commit->session,
584 file->pool);
585 if (err)
587 if (err->apr_err == SVN_ERR_FS_CONFLICT)
588 SVN_ERR_W(err, apr_psprintf(file->pool,
589 _("File '%s' is out of date; try updating"),
590 svn_path_local_style(relative_file_path(file, file->pool),
591 file->pool)));
592 return err;
595 if (file->checkout->progress.status != 201)
597 if (file->checkout->progress.status == 404)
599 return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
600 return_response_err(handler,
601 &file->checkout->progress),
602 _("Path '%s' not present"),
603 file->name);
606 return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
607 return_response_err(handler,
608 &file->checkout->progress),
609 _("File '%s' is out of date; try updating"),
610 svn_path_local_style(relative_file_path(file, file->pool),
611 file->pool));
614 return SVN_NO_ERROR;
617 static svn_error_t *
618 proppatch_walker(void *baton,
619 const char *ns, apr_ssize_t ns_len,
620 const char *name, apr_ssize_t name_len,
621 const svn_string_t *val,
622 apr_pool_t *pool)
624 serf_bucket_t *body_bkt = baton;
625 serf_bucket_t *tmp_bkt;
626 serf_bucket_alloc_t *alloc;
627 svn_boolean_t binary_prop;
628 char *prop_name;
630 if (svn_xml_is_xml_safe(val->data, val->len))
632 binary_prop = FALSE;
634 else
636 binary_prop = TRUE;
639 /* Use the namespace prefix instead of adding the xmlns attribute to support
640 property names containing ':' */
641 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
642 prop_name = apr_pstrcat(pool, "S:", name, NULL);
643 else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
644 prop_name = apr_pstrcat(pool, "C:", name, NULL);
645 name_len = strlen(prop_name);
647 alloc = body_bkt->allocator;
649 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<",
650 sizeof("<") - 1,
651 alloc);
652 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
654 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(prop_name, name_len, alloc);
655 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
657 if (binary_prop == TRUE)
659 tmp_bkt =
660 SERF_BUCKET_SIMPLE_STRING_LEN(" V:encoding=\"base64\"",
661 sizeof(" V:encoding=\"base64\"") - 1,
662 alloc);
663 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
666 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(">",
667 sizeof(">") - 1,
668 alloc);
669 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
671 if (binary_prop == TRUE)
673 val = svn_base64_encode_string(val, pool);
675 else
677 svn_stringbuf_t *prop_buf = svn_stringbuf_create("", pool);
678 svn_xml_escape_cdata_string(&prop_buf, val, pool);
679 val = svn_string_create_from_buf(prop_buf, pool);
682 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(val->data, val->len, alloc);
683 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
685 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</",
686 sizeof("</") - 1,
687 alloc);
688 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
690 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(prop_name, name_len, alloc);
691 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
693 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(">",
694 sizeof(">") - 1,
695 alloc);
696 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
698 return SVN_NO_ERROR;
701 static apr_status_t
702 setup_proppatch_headers(serf_bucket_t *headers,
703 void *baton,
704 apr_pool_t *pool)
706 proppatch_context_t *proppatch = baton;
708 if (proppatch->name && proppatch->commit->lock_tokens)
710 const char *token;
712 token = apr_hash_get(proppatch->commit->lock_tokens, proppatch->name,
713 APR_HASH_KEY_STRING);
715 if (token)
717 const char *token_header;
719 token_header = apr_pstrcat(pool, "(<", token, ">)", NULL);
721 serf_bucket_headers_set(headers, "If", token_header);
725 return APR_SUCCESS;
728 #define PROPPATCH_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
729 "<D:propertyupdate xmlns:D=\"DAV:\" "\
730 "xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "\
731 "xmlns:C=\"" SVN_DAV_PROP_NS_CUSTOM "\" "\
732 "xmlns:S=\"" SVN_DAV_PROP_NS_SVN "\">"
734 #define PROPPATCH_TRAILER "</D:propertyupdate>"
736 static serf_bucket_t *
737 create_proppatch_body(void *baton,
738 serf_bucket_alloc_t *alloc,
739 apr_pool_t *pool)
741 proppatch_context_t *ctx = baton;
742 serf_bucket_t *body_bkt, *tmp_bkt;
744 body_bkt = serf_bucket_aggregate_create(alloc);
746 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(PROPPATCH_HEADER,
747 sizeof(PROPPATCH_HEADER) - 1,
748 alloc);
749 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
751 if (apr_hash_count(ctx->changed_props) > 0)
753 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<D:set>",
754 sizeof("<D:set>") - 1,
755 alloc);
756 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
758 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<D:prop>",
759 sizeof("<D:prop>") - 1,
760 alloc);
761 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
763 svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
764 SVN_INVALID_REVNUM,
765 proppatch_walker, body_bkt, pool);
767 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</D:prop>",
768 sizeof("</D:prop>") - 1,
769 alloc);
770 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
772 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</D:set>",
773 sizeof("</D:set>") - 1,
774 alloc);
775 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
778 if (apr_hash_count(ctx->removed_props) > 0)
780 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<D:remove>",
781 sizeof("<D:remove>") - 1,
782 alloc);
783 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
785 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<D:prop>",
786 sizeof("<D:prop>") - 1,
787 alloc);
788 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
790 svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
791 SVN_INVALID_REVNUM,
792 proppatch_walker, body_bkt, pool);
794 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</D:prop>",
795 sizeof("</D:prop>") - 1,
796 alloc);
797 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
799 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</D:remove>",
800 sizeof("</D:remove>") - 1,
801 alloc);
802 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
805 tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(PROPPATCH_TRAILER,
806 sizeof(PROPPATCH_TRAILER) - 1,
807 alloc);
808 serf_bucket_aggregate_append(body_bkt, tmp_bkt);
810 return body_bkt;
813 static svn_error_t*
814 proppatch_resource(proppatch_context_t *proppatch,
815 commit_context_t *commit,
816 apr_pool_t *pool)
818 svn_ra_serf__handler_t *handler;
820 handler = apr_pcalloc(pool, sizeof(*handler));
821 handler->method = "PROPPATCH";
822 handler->path = proppatch->path;
823 handler->conn = commit->conn;
824 handler->session = commit->session;
826 handler->header_delegate = setup_proppatch_headers;
827 handler->header_delegate_baton = proppatch;
829 handler->body_delegate = create_proppatch_body;
830 handler->body_delegate_baton = proppatch;
832 handler->response_handler = svn_ra_serf__handle_multistatus_only;
833 handler->response_baton = &proppatch->progress;
835 svn_ra_serf__request_create(handler);
837 /* If we don't wait for the response, our pool will be gone! */
838 SVN_ERR(svn_ra_serf__context_run_wait(&proppatch->progress.done,
839 commit->session, pool));
841 if (proppatch->progress.status != 207 ||
842 proppatch->progress.server_error.error)
844 svn_error_t *err;
845 err = return_response_err(handler, &proppatch->progress);
846 return svn_error_create(SVN_ERR_RA_DAV_PROPPATCH_FAILED, err,
847 _("At least one property change failed; repository is unchanged"));
850 return SVN_NO_ERROR;
853 static serf_bucket_t *
854 create_put_body(void *baton,
855 serf_bucket_alloc_t *alloc,
856 apr_pool_t *pool)
858 file_context_t *ctx = baton;
859 apr_off_t offset;
861 /* We need to flush the file, make it unbuffered (so that it can be
862 * zero-copied via mmap), and reset the position before attempting to
863 * deliver the file.
865 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
866 * and zero-copy the PUT body. However, on older APR versions, we can't
867 * check the buffer status; but serf will fall through and create a file
868 * bucket for us on the buffered svndiff handle.
870 apr_file_flush(ctx->svndiff);
871 #if APR_VERSION_AT_LEAST(1, 3, 0)
872 apr_file_buffer_set(ctx->svndiff, NULL, 0);
873 #endif
874 offset = 0;
875 apr_file_seek(ctx->svndiff, APR_SET, &offset);
877 return serf_bucket_file_create(ctx->svndiff, alloc);
880 static serf_bucket_t *
881 create_empty_put_body(void *baton,
882 serf_bucket_alloc_t *alloc,
883 apr_pool_t *pool)
885 return SERF_BUCKET_SIMPLE_STRING("", alloc);
888 static apr_status_t
889 setup_put_headers(serf_bucket_t *headers,
890 void *baton,
891 apr_pool_t *pool)
893 file_context_t *ctx = baton;
895 if (ctx->base_checksum)
897 serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
898 ctx->base_checksum);
901 if (ctx->result_checksum)
903 serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
904 ctx->result_checksum);
907 if (ctx->commit->lock_tokens)
909 const char *token;
911 token = apr_hash_get(ctx->commit->lock_tokens, ctx->name,
912 APR_HASH_KEY_STRING);
914 if (token)
916 const char *token_header;
918 token_header = apr_pstrcat(pool, "(<", token, ">)", NULL);
920 serf_bucket_headers_set(headers, "If", token_header);
924 return APR_SUCCESS;
927 static apr_status_t
928 setup_copy_file_headers(serf_bucket_t *headers,
929 void *baton,
930 apr_pool_t *pool)
932 file_context_t *file = baton;
933 apr_uri_t uri;
934 const char *absolute_uri;
936 /* The Dest URI must be absolute. Bummer. */
937 uri = file->commit->session->repos_url;
938 uri.path = (char*)file->put_url;
939 absolute_uri = apr_uri_unparse(pool, &uri, 0);
941 serf_bucket_headers_set(headers, "Destination", absolute_uri);
943 serf_bucket_headers_set(headers, "Depth", "0");
944 serf_bucket_headers_set(headers, "Overwrite", "T");
946 return APR_SUCCESS;
949 static apr_status_t
950 setup_copy_dir_headers(serf_bucket_t *headers,
951 void *baton,
952 apr_pool_t *pool)
954 dir_context_t *dir = baton;
955 apr_uri_t uri;
956 const char *absolute_uri;
958 /* The Dest URI must be absolute. Bummer. */
959 uri = dir->commit->session->repos_url;
960 uri.path =
961 (char*)svn_path_url_add_component(dir->parent_dir->checkout->resource_url,
962 svn_path_basename(dir->name, pool),
963 pool);
965 absolute_uri = apr_uri_unparse(pool, &uri, 0);
967 serf_bucket_headers_set(headers, "Destination", absolute_uri);
969 serf_bucket_headers_set(headers, "Depth", "infinity");
970 serf_bucket_headers_set(headers, "Overwrite", "T");
972 /* Implicitly checkout this dir now. */
973 dir->checkout = apr_pcalloc(dir->pool, sizeof(*dir->checkout));
974 dir->checkout->pool = dir->pool;
975 dir->checkout->activity_url = dir->commit->activity_url;
976 dir->checkout->activity_url_len = dir->commit->activity_url_len;
977 dir->checkout->resource_url = apr_pstrdup(dir->checkout->pool, uri.path);
979 apr_hash_set(dir->commit->copied_entries,
980 apr_pstrdup(dir->commit->pool, dir->name), APR_HASH_KEY_STRING,
981 (void*)1);
983 return APR_SUCCESS;
986 static apr_status_t
987 setup_delete_headers(serf_bucket_t *headers,
988 void *baton,
989 apr_pool_t *pool)
991 delete_context_t *ctx = baton;
993 serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
994 apr_ltoa(pool, ctx->revision));
996 if (ctx->lock_token_hash)
998 ctx->lock_token = apr_hash_get(ctx->lock_token_hash, ctx->path,
999 APR_HASH_KEY_STRING);
1001 if (ctx->lock_token)
1003 const char *token_header;
1005 token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
1006 ctx->lock_token, ">)", NULL);
1008 serf_bucket_headers_set(headers, "If", token_header);
1010 if (ctx->keep_locks)
1011 serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
1012 SVN_DAV_OPTION_KEEP_LOCKS);
1016 return APR_SUCCESS;
1019 #define XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
1021 static serf_bucket_t *
1022 create_delete_body(void *baton,
1023 serf_bucket_alloc_t *alloc,
1024 apr_pool_t *pool)
1026 delete_context_t *ctx = baton;
1027 serf_bucket_t *body, *tmp;
1029 body = serf_bucket_aggregate_create(alloc);
1031 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(XML_HEADER, sizeof(XML_HEADER) - 1,
1032 alloc);
1033 serf_bucket_aggregate_append(body, tmp);
1035 svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
1036 body, alloc, pool);
1038 return body;
1041 /* Helper function to write the svndiff stream to temporary file. */
1042 static svn_error_t *
1043 svndiff_stream_write(void *file_baton,
1044 const char *data,
1045 apr_size_t *len)
1047 file_context_t *ctx = file_baton;
1048 apr_status_t status;
1050 status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
1051 if (status)
1052 return svn_error_wrap_apr(status, _("Failed writing updated file"));
1054 return SVN_NO_ERROR;
1059 /* Commit baton callbacks */
1061 static svn_error_t *
1062 open_root(void *edit_baton,
1063 svn_revnum_t base_revision,
1064 apr_pool_t *dir_pool,
1065 void **root_baton)
1067 commit_context_t *ctx = edit_baton;
1068 svn_ra_serf__options_context_t *opt_ctx;
1069 svn_ra_serf__propfind_context_t *propfind_ctx;
1070 svn_ra_serf__handler_t *handler;
1071 svn_ra_serf__simple_request_context_t *mkact_ctx;
1072 proppatch_context_t *proppatch_ctx;
1073 dir_context_t *dir;
1074 const char *activity_str;
1075 const char *vcc_url;
1076 apr_hash_t *props;
1077 apr_hash_index_t *hi;
1078 svn_error_t *err;
1080 /* Create a UUID for this commit. */
1081 ctx->uuid = svn_uuid_generate(ctx->pool);
1083 svn_ra_serf__create_options_req(&opt_ctx, ctx->session,
1084 ctx->session->conns[0],
1085 ctx->session->repos_url.path, ctx->pool);
1087 err = svn_ra_serf__context_run_wait(
1088 svn_ra_serf__get_options_done_ptr(opt_ctx),
1089 ctx->session, ctx->pool);
1090 if (svn_ra_serf__get_options_error(opt_ctx) ||
1091 svn_ra_serf__get_options_parser_error(opt_ctx))
1093 svn_error_clear(err);
1094 SVN_ERR(svn_ra_serf__get_options_error(opt_ctx));
1095 SVN_ERR(svn_ra_serf__get_options_parser_error(opt_ctx));
1097 SVN_ERR(err);
1099 activity_str = svn_ra_serf__options_get_activity_collection(opt_ctx);
1101 if (!activity_str)
1103 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1104 _("The OPTIONS response did not include the "
1105 "requested activity-collection-set value"));
1108 ctx->activity_url = svn_path_url_add_component(activity_str,
1109 ctx->uuid, ctx->pool);
1110 ctx->activity_url_len = strlen(ctx->activity_url);
1112 /* Create our activity URL now on the server. */
1113 handler = apr_pcalloc(ctx->pool, sizeof(*handler));
1114 handler->method = "MKACTIVITY";
1115 handler->path = ctx->activity_url;
1116 handler->conn = ctx->session->conns[0];
1117 handler->session = ctx->session;
1119 mkact_ctx = apr_pcalloc(ctx->pool, sizeof(*mkact_ctx));
1121 handler->response_handler = svn_ra_serf__handle_status_only;
1122 handler->response_baton = mkact_ctx;
1124 svn_ra_serf__request_create(handler);
1126 SVN_ERR(svn_ra_serf__context_run_wait(&mkact_ctx->done, ctx->session,
1127 ctx->pool));
1129 if (mkact_ctx->status != 201)
1131 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1132 _("%s of '%s': %d %s (%s://%s)"),
1133 handler->method, handler->path,
1134 mkact_ctx->status, mkact_ctx->reason,
1135 ctx->session->repos_url.scheme,
1136 ctx->session->repos_url.hostinfo);
1139 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
1140 ctx->session, ctx->conn,
1141 ctx->session->repos_url.path,
1142 ctx->pool));
1144 /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1145 props = apr_hash_make(ctx->pool);
1146 propfind_ctx = NULL;
1147 svn_ra_serf__deliver_props(&propfind_ctx, props, ctx->session,
1148 ctx->conn, vcc_url, SVN_INVALID_REVNUM, "0",
1149 checked_in_props, FALSE, NULL, ctx->pool);
1151 SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, ctx->session, ctx->pool));
1153 ctx->baseline_url = svn_ra_serf__get_ver_prop(props, vcc_url,
1154 SVN_INVALID_REVNUM,
1155 "DAV:", "checked-in");
1157 if (!ctx->baseline_url)
1159 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1160 _("The OPTIONS response did not include the "
1161 "requested checked-in value"));
1164 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1166 dir->pool = dir_pool;
1167 dir->commit = ctx;
1168 dir->base_revision = base_revision;
1169 dir->name = "";
1170 dir->changed_props = apr_hash_make(dir->pool);
1171 dir->removed_props = apr_hash_make(dir->pool);
1173 SVN_ERR(get_version_url(&dir->checked_in_url,
1174 dir->commit->session, dir->commit->conn,
1175 dir->name, dir->base_revision,
1176 dir->commit->checked_in_url, dir->pool));
1177 ctx->checked_in_url = dir->checked_in_url;
1179 /* Checkout our root dir */
1180 SVN_ERR(checkout_dir(dir));
1182 /* PROPPATCH our revprops and pass them along. */
1183 proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
1184 proppatch_ctx->pool = dir_pool;
1185 proppatch_ctx->commit = ctx;
1186 proppatch_ctx->path = ctx->baseline->resource_url;
1187 proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
1188 proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
1190 for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
1191 hi = apr_hash_next(hi))
1193 const void *key;
1194 void *val;
1195 const char *name;
1196 svn_string_t *value;
1197 const char *ns;
1199 apr_hash_this(hi, &key, NULL, &val);
1200 name = key;
1201 value = val;
1203 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1205 ns = SVN_DAV_PROP_NS_SVN;
1206 name += sizeof(SVN_PROP_PREFIX) - 1;
1208 else
1210 ns = SVN_DAV_PROP_NS_CUSTOM;
1213 svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
1214 ns, name, value, proppatch_ctx->pool);
1217 SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
1219 *root_baton = dir;
1221 return SVN_NO_ERROR;
1224 static svn_error_t *
1225 delete_entry(const char *path,
1226 svn_revnum_t revision,
1227 void *parent_baton,
1228 apr_pool_t *pool)
1230 dir_context_t *dir = parent_baton;
1231 delete_context_t *delete_ctx;
1232 svn_ra_serf__handler_t *handler;
1233 svn_error_t *err;
1235 /* Ensure our directory has been checked out */
1236 SVN_ERR(checkout_dir(dir));
1238 /* DELETE our entry */
1239 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1240 delete_ctx->path = apr_pstrdup(pool, path);
1241 delete_ctx->revision = revision;
1242 delete_ctx->lock_token_hash = dir->commit->lock_tokens;
1243 delete_ctx->keep_locks = dir->commit->keep_locks;
1245 handler = apr_pcalloc(pool, sizeof(*handler));
1246 handler->session = dir->commit->session;
1247 handler->conn = dir->commit->conn;
1249 handler->response_handler = svn_ra_serf__handle_status_only;
1250 handler->response_baton = &delete_ctx->progress;
1252 handler->header_delegate = setup_delete_headers;
1253 handler->header_delegate_baton = delete_ctx;
1255 handler->method = "DELETE";
1256 handler->path =
1257 svn_path_url_add_component(dir->checkout->resource_url,
1258 svn_path_basename(path, pool),
1259 pool);
1261 svn_ra_serf__request_create(handler);
1263 err = svn_ra_serf__context_run_wait(&delete_ctx->progress.done,
1264 dir->commit->session, pool);
1266 if (err &&
1267 (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
1268 err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
1269 err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
1270 err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
1272 svn_error_clear(err);
1274 handler->body_delegate = create_delete_body;
1275 handler->body_delegate_baton = delete_ctx;
1276 handler->body_type = "text/xml";
1278 svn_ra_serf__request_create(handler);
1280 delete_ctx->progress.done = 0;
1282 SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->progress.done,
1283 dir->commit->session, pool));
1285 else if (err)
1287 return err;
1290 /* 204 No Content: item successfully deleted
1291 404 Not found: ignored, the item might have been deleted in this
1292 transaction. */
1293 if (delete_ctx->progress.status != 204 &&
1294 delete_ctx->progress.status != 404)
1296 return return_response_err(handler, &delete_ctx->progress);
1299 apr_hash_set(dir->commit->deleted_entries,
1300 apr_pstrdup(dir->commit->pool, path), APR_HASH_KEY_STRING,
1301 (void*)1);
1303 return SVN_NO_ERROR;
1306 static svn_error_t *
1307 add_directory(const char *path,
1308 void *parent_baton,
1309 const char *copyfrom_path,
1310 svn_revnum_t copyfrom_revision,
1311 apr_pool_t *dir_pool,
1312 void **child_baton)
1314 dir_context_t *parent = parent_baton;
1315 dir_context_t *dir;
1316 svn_ra_serf__handler_t *handler;
1317 svn_ra_serf__simple_request_context_t *add_dir_ctx;
1318 apr_status_t status;
1320 /* Ensure our parent is checked out. */
1321 SVN_ERR(checkout_dir(parent));
1323 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1325 dir->pool = dir_pool;
1327 dir->parent_dir = parent;
1328 dir->commit = parent->commit;
1330 dir->added = TRUE;
1331 dir->base_revision = SVN_INVALID_REVNUM;
1332 dir->copy_revision = copyfrom_revision;
1333 dir->copy_path = copyfrom_path;
1334 dir->name = apr_pstrdup(dir->pool, path);
1335 dir->checked_in_url =
1336 svn_path_url_add_component(parent->commit->checked_in_url,
1337 path, dir->pool);
1338 dir->changed_props = apr_hash_make(dir->pool);
1339 dir->removed_props = apr_hash_make(dir->pool);
1341 handler = apr_pcalloc(dir->pool, sizeof(*handler));
1342 handler->conn = dir->commit->conn;
1343 handler->session = dir->commit->session;
1345 add_dir_ctx = apr_pcalloc(dir->pool, sizeof(*add_dir_ctx));
1347 handler->response_handler = svn_ra_serf__handle_status_only;
1348 handler->response_baton = add_dir_ctx;
1349 if (!dir->copy_path)
1351 handler->method = "MKCOL";
1352 handler->path = svn_path_url_add_component(parent->checkout->resource_url,
1353 svn_path_basename(path,
1354 dir->pool),
1355 dir->pool);
1357 else
1359 apr_uri_t uri;
1360 apr_hash_t *props;
1361 const char *vcc_url, *rel_copy_path, *basecoll_url, *req_url;
1363 props = apr_hash_make(dir->pool);
1365 status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1366 if (status)
1368 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1369 _("Unable to parse URL '%s'"),
1370 dir->copy_path);
1373 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &rel_copy_path,
1374 dir->commit->session,
1375 dir->commit->conn,
1376 uri.path, dir->pool));
1377 SVN_ERR(svn_ra_serf__retrieve_props(props,
1378 dir->commit->session,
1379 dir->commit->conn,
1380 vcc_url, dir->copy_revision, "0",
1381 baseline_props, dir->pool));
1382 basecoll_url = svn_ra_serf__get_ver_prop(props,
1383 vcc_url, dir->copy_revision,
1384 "DAV:", "baseline-collection");
1386 if (!basecoll_url)
1388 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1389 _("The OPTIONS response did not include the "
1390 "requested baseline-collection value"));
1393 req_url = svn_path_url_add_component(basecoll_url, rel_copy_path,
1394 dir->pool);
1396 handler->method = "COPY";
1397 handler->path = req_url;
1399 handler->header_delegate = setup_copy_dir_headers;
1400 handler->header_delegate_baton = dir;
1403 svn_ra_serf__request_create(handler);
1405 SVN_ERR(svn_ra_serf__context_run_wait(&add_dir_ctx->done,
1406 dir->commit->session, dir->pool));
1408 /* 201 Created: item was successfully copied
1409 204 No Content: item successfully replaced an existing target */
1410 if (add_dir_ctx->status != 201 &&
1411 add_dir_ctx->status != 204)
1413 SVN_ERR(add_dir_ctx->server_error.error);
1414 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1415 _("Adding a directory failed: %s on %s (%d)"),
1416 handler->method, handler->path,
1417 add_dir_ctx->status);
1420 *child_baton = dir;
1422 return SVN_NO_ERROR;
1425 static svn_error_t *
1426 open_directory(const char *path,
1427 void *parent_baton,
1428 svn_revnum_t base_revision,
1429 apr_pool_t *dir_pool,
1430 void **child_baton)
1432 dir_context_t *parent = parent_baton;
1433 dir_context_t *dir;
1435 dir = apr_pcalloc(dir_pool, sizeof(*dir));
1437 dir->pool = dir_pool;
1439 dir->parent_dir = parent;
1440 dir->commit = parent->commit;
1442 dir->added = FALSE;
1443 dir->base_revision = base_revision;
1444 dir->name = apr_pstrdup(dir->pool, path);
1445 dir->changed_props = apr_hash_make(dir->pool);
1446 dir->removed_props = apr_hash_make(dir->pool);
1448 SVN_ERR(get_version_url(&dir->checked_in_url,
1449 dir->commit->session, dir->commit->conn,
1450 dir->name, dir->base_revision,
1451 dir->commit->checked_in_url, dir->pool));
1452 *child_baton = dir;
1454 return SVN_NO_ERROR;
1457 static svn_error_t *
1458 change_dir_prop(void *dir_baton,
1459 const char *name,
1460 const svn_string_t *value,
1461 apr_pool_t *pool)
1463 dir_context_t *dir = dir_baton;
1464 const char *ns;
1466 /* Ensure we have a checked out dir. */
1467 SVN_ERR(checkout_dir(dir));
1469 name = apr_pstrdup(dir->pool, name);
1471 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1473 ns = SVN_DAV_PROP_NS_SVN;
1474 name += sizeof(SVN_PROP_PREFIX) - 1;
1476 else
1478 ns = SVN_DAV_PROP_NS_CUSTOM;
1481 if (value)
1483 value = svn_string_dup(value, dir->pool);
1484 svn_ra_serf__set_prop(dir->changed_props, dir->checkout->resource_url,
1485 ns, name, value, dir->pool);
1487 else
1489 value = svn_string_create("", dir->pool);
1491 svn_ra_serf__set_prop(dir->removed_props, dir->checkout->resource_url,
1492 ns, name, value, dir->pool);
1495 return SVN_NO_ERROR;
1498 static svn_error_t *
1499 close_directory(void *dir_baton,
1500 apr_pool_t *pool)
1502 dir_context_t *dir = dir_baton;
1504 /* Huh? We're going to be called before the texts are sent. Ugh.
1505 * Therefore, just wave politely at our caller.
1508 /* PROPPATCH our prop change and pass it along. */
1509 if (apr_hash_count(dir->changed_props) ||
1510 apr_hash_count(dir->removed_props))
1512 proppatch_context_t *proppatch_ctx;
1514 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1515 proppatch_ctx->pool = pool;
1516 proppatch_ctx->commit = dir->commit;
1517 proppatch_ctx->name = dir->name;
1518 proppatch_ctx->path = dir->checkout->resource_url;
1519 proppatch_ctx->changed_props = dir->changed_props;
1520 proppatch_ctx->removed_props = dir->removed_props;
1522 SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
1525 return SVN_NO_ERROR;
1528 static svn_error_t *
1529 absent_directory(const char *path,
1530 void *parent_baton,
1531 apr_pool_t *pool)
1533 #if 0
1534 dir_context_t *ctx = parent_baton;
1535 #endif
1537 abort();
1540 static svn_error_t *
1541 add_file(const char *path,
1542 void *parent_baton,
1543 const char *copy_path,
1544 svn_revnum_t copy_revision,
1545 apr_pool_t *file_pool,
1546 void **file_baton)
1548 dir_context_t *dir = parent_baton;
1549 file_context_t *new_file;
1551 /* Ensure our directory has been checked out */
1552 SVN_ERR(checkout_dir(dir));
1554 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1556 new_file->pool = file_pool;
1558 dir->ref_count++;
1559 new_file->parent_dir = dir;
1561 new_file->commit = dir->commit;
1563 new_file->name = apr_pstrdup(new_file->pool, path);
1565 new_file->added = TRUE;
1566 new_file->base_revision = SVN_INVALID_REVNUM;
1567 new_file->copy_path = copy_path;
1568 new_file->copy_revision = copy_revision;
1570 new_file->changed_props = apr_hash_make(new_file->pool);
1571 new_file->removed_props = apr_hash_make(new_file->pool);
1573 /* Ensure that the file doesn't exist by doing a HEAD on the
1574 * resource, but only if we haven't deleted it in this commit
1575 * already, or if the parent directory was also added (without
1576 * history) in this commit.
1578 if (! ((dir->added && !dir->copy_path) ||
1579 apr_hash_get(dir->commit->deleted_entries,
1580 new_file->name, APR_HASH_KEY_STRING)))
1582 svn_ra_serf__simple_request_context_t *head_ctx;
1583 svn_ra_serf__handler_t *handler;
1585 handler = apr_pcalloc(new_file->pool, sizeof(*handler));
1587 handler->session = new_file->commit->session;
1588 handler->conn = new_file->commit->conn;
1590 handler->method = "HEAD";
1591 handler->path =
1592 svn_path_url_add_component(new_file->commit->session->repos_url.path,
1593 path, new_file->pool);
1595 head_ctx = apr_pcalloc(new_file->pool, sizeof(*head_ctx));
1597 handler->response_handler = svn_ra_serf__handle_status_only;
1598 handler->response_baton = head_ctx;
1600 svn_ra_serf__request_create(handler);
1602 SVN_ERR(svn_ra_serf__context_run_wait(&head_ctx->done,
1603 new_file->commit->session,
1604 new_file->pool));
1606 if (head_ctx->status != 404)
1608 return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
1609 _("File '%s' already exists"), path);
1613 new_file->put_url =
1614 svn_path_url_add_component(dir->checkout->resource_url,
1615 svn_path_basename(path, new_file->pool),
1616 new_file->pool);
1618 *file_baton = new_file;
1620 return SVN_NO_ERROR;
1623 static svn_error_t *
1624 open_file(const char *path,
1625 void *parent_baton,
1626 svn_revnum_t base_revision,
1627 apr_pool_t *file_pool,
1628 void **file_baton)
1630 dir_context_t *ctx = parent_baton;
1631 file_context_t *new_file;
1633 new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1635 new_file->pool = file_pool;
1637 ctx->ref_count++;
1638 new_file->parent_dir = ctx;
1640 new_file->commit = ctx->commit;
1642 /* TODO: Remove directory names? */
1643 new_file->name = apr_pstrdup(new_file->pool, path);
1645 new_file->added = FALSE;
1646 new_file->base_revision = base_revision;
1648 new_file->changed_props = apr_hash_make(new_file->pool);
1649 new_file->removed_props = apr_hash_make(new_file->pool);
1651 /* CHECKOUT the file into our activity. */
1652 SVN_ERR(checkout_file(new_file));
1654 new_file->put_url = new_file->checkout->resource_url;
1656 *file_baton = new_file;
1658 return SVN_NO_ERROR;
1661 static svn_error_t *
1662 apply_textdelta(void *file_baton,
1663 const char *base_checksum,
1664 apr_pool_t *pool,
1665 svn_txdelta_window_handler_t *handler,
1666 void **handler_baton)
1668 file_context_t *ctx = file_baton;
1669 const svn_ra_callbacks2_t *wc_callbacks;
1670 void *wc_callback_baton;
1672 /* Store the stream in a temporary file; we'll give it to serf when we
1673 * close this file.
1675 * TODO: There should be a way we can stream the request body instead of
1676 * writing to a temporary file (ugh). A special svn stream serf bucket
1677 * that returns EAGAIN until we receive the done call? But, when
1678 * would we run through the serf context? Grr.
1680 wc_callbacks = ctx->commit->session->wc_callbacks;
1681 wc_callback_baton = ctx->commit->session->wc_callback_baton;
1682 SVN_ERR(wc_callbacks->open_tmp_file(&ctx->svndiff,
1683 wc_callback_baton,
1684 ctx->pool));
1686 ctx->stream = svn_stream_create(ctx, pool);
1687 svn_stream_set_write(ctx->stream, svndiff_stream_write);
1689 svn_txdelta_to_svndiff(ctx->stream, pool, handler, handler_baton);
1691 ctx->base_checksum = base_checksum;
1693 return SVN_NO_ERROR;
1696 static svn_error_t *
1697 change_file_prop(void *file_baton,
1698 const char *name,
1699 const svn_string_t *value,
1700 apr_pool_t *pool)
1702 file_context_t *file = file_baton;
1703 const char *ns;
1705 name = apr_pstrdup(file->pool, name);
1707 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
1709 ns = SVN_DAV_PROP_NS_SVN;
1710 name += sizeof(SVN_PROP_PREFIX) - 1;
1712 else
1714 ns = SVN_DAV_PROP_NS_CUSTOM;
1717 if (value)
1719 value = svn_string_dup(value, file->pool);
1720 svn_ra_serf__set_prop(file->changed_props, file->put_url,
1721 ns, name, value, file->pool);
1723 else
1725 value = svn_string_create("", file->pool);
1727 svn_ra_serf__set_prop(file->removed_props, file->put_url,
1728 ns, name, value, file->pool);
1731 return SVN_NO_ERROR;
1734 static svn_error_t *
1735 close_file(void *file_baton,
1736 const char *text_checksum,
1737 apr_pool_t *pool)
1739 file_context_t *ctx = file_baton;
1740 svn_boolean_t put_empty_file = FALSE;
1741 apr_status_t status;
1743 ctx->result_checksum = text_checksum;
1745 if (ctx->copy_path)
1747 svn_ra_serf__handler_t *handler;
1748 svn_ra_serf__simple_request_context_t *copy_ctx;
1749 apr_uri_t uri;
1750 apr_hash_t *props;
1751 const char *vcc_url, *rel_copy_path, *basecoll_url, *req_url;
1753 props = apr_hash_make(pool);
1755 status = apr_uri_parse(pool, ctx->copy_path, &uri);
1756 if (status)
1758 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1759 _("Unable to parse URL '%s'"),
1760 ctx->copy_path);
1763 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &rel_copy_path,
1764 ctx->commit->session,
1765 ctx->commit->conn,
1766 uri.path, pool));
1767 SVN_ERR(svn_ra_serf__retrieve_props(props,
1768 ctx->commit->session,
1769 ctx->commit->conn,
1770 vcc_url, ctx->copy_revision, "0",
1771 baseline_props, pool));
1772 basecoll_url = svn_ra_serf__get_ver_prop(props,
1773 vcc_url, ctx->copy_revision,
1774 "DAV:", "baseline-collection");
1776 if (!basecoll_url)
1778 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1779 _("The OPTIONS response did not include the "
1780 "requested baseline-collection value"));
1783 req_url = svn_path_url_add_component(basecoll_url, rel_copy_path, pool);
1785 handler = apr_pcalloc(pool, sizeof(*handler));
1786 handler->method = "COPY";
1787 handler->path = req_url;
1788 handler->conn = ctx->commit->conn;
1789 handler->session = ctx->commit->session;
1791 copy_ctx = apr_pcalloc(pool, sizeof(*copy_ctx));
1793 handler->response_handler = svn_ra_serf__handle_status_only;
1794 handler->response_baton = copy_ctx;
1796 handler->header_delegate = setup_copy_file_headers;
1797 handler->header_delegate_baton = ctx;
1799 svn_ra_serf__request_create(handler);
1801 SVN_ERR(svn_ra_serf__context_run_wait(&copy_ctx->done,
1802 ctx->commit->session, pool));
1804 if (copy_ctx->status != 201 && copy_ctx->status != 204)
1806 return return_response_err(handler, copy_ctx);
1810 /* If we got no stream of changes, but this is an added-without-history
1811 * file, make a note that we'll be PUTting a zero-byte file to the server.
1813 if ((!ctx->stream) && ctx->added && (!ctx->copy_path))
1814 put_empty_file = TRUE;
1816 /* If we had a stream of changes, push them to the server... */
1817 if (ctx->stream || put_empty_file)
1819 svn_ra_serf__handler_t *handler;
1820 svn_ra_serf__simple_request_context_t *put_ctx;
1822 handler = apr_pcalloc(pool, sizeof(*handler));
1823 handler->method = "PUT";
1824 handler->path = ctx->put_url;
1825 handler->conn = ctx->commit->conn;
1826 handler->session = ctx->commit->session;
1828 put_ctx = apr_pcalloc(pool, sizeof(*put_ctx));
1830 handler->response_handler = svn_ra_serf__handle_status_only;
1831 handler->response_baton = put_ctx;
1833 if (put_empty_file)
1835 handler->body_delegate = create_empty_put_body;
1836 handler->body_delegate_baton = ctx;
1837 handler->body_type = "text/plain";
1839 else
1841 handler->body_delegate = create_put_body;
1842 handler->body_delegate_baton = ctx;
1843 handler->body_type = "application/vnd.svn-svndiff";
1846 handler->header_delegate = setup_put_headers;
1847 handler->header_delegate_baton = ctx;
1849 svn_ra_serf__request_create(handler);
1851 SVN_ERR(svn_ra_serf__context_run_wait(&put_ctx->done,
1852 ctx->commit->session, pool));
1854 if (put_ctx->status != 204 && put_ctx->status != 201)
1856 return return_response_err(handler, put_ctx);
1860 /* If we had any prop changes, push them via PROPPATCH. */
1861 if (apr_hash_count(ctx->changed_props) ||
1862 apr_hash_count(ctx->removed_props))
1864 proppatch_context_t *proppatch;
1866 proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
1867 proppatch->pool = ctx->pool;
1868 proppatch->name = ctx->name;
1869 proppatch->path = ctx->put_url;
1870 proppatch->commit = ctx->commit;
1871 proppatch->changed_props = ctx->changed_props;
1872 proppatch->removed_props = ctx->removed_props;
1874 SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
1877 return SVN_NO_ERROR;
1880 static svn_error_t *
1881 absent_file(const char *path,
1882 void *parent_baton,
1883 apr_pool_t *pool)
1885 #if 0
1886 dir_context_t *ctx = parent_baton;
1887 #endif
1889 abort();
1892 static svn_error_t *
1893 close_edit(void *edit_baton,
1894 apr_pool_t *pool)
1896 commit_context_t *ctx = edit_baton;
1897 svn_ra_serf__merge_context_t *merge_ctx;
1898 svn_ra_serf__simple_request_context_t *delete_ctx;
1899 svn_ra_serf__handler_t *handler;
1900 svn_boolean_t *merge_done;
1902 /* MERGE our activity */
1903 SVN_ERR(svn_ra_serf__merge_create_req(&merge_ctx, ctx->session,
1904 ctx->session->conns[0],
1905 ctx->session->repos_url.path,
1906 ctx->activity_url,
1907 ctx->activity_url_len,
1908 ctx->lock_tokens,
1909 ctx->keep_locks,
1910 pool));
1912 merge_done = svn_ra_serf__merge_get_done_ptr(merge_ctx);
1914 SVN_ERR(svn_ra_serf__context_run_wait(merge_done, ctx->session, pool));
1916 if (svn_ra_serf__merge_get_status(merge_ctx) != 200)
1918 abort();
1921 /* Inform the WC that we did a commit. */
1922 SVN_ERR(ctx->callback(svn_ra_serf__merge_get_commit_info(merge_ctx),
1923 ctx->callback_baton, pool));
1925 /* DELETE our completed activity */
1926 handler = apr_pcalloc(pool, sizeof(*handler));
1927 handler->method = "DELETE";
1928 handler->path = ctx->activity_url;
1929 handler->conn = ctx->conn;
1930 handler->session = ctx->session;
1932 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1934 handler->response_handler = svn_ra_serf__handle_status_only;
1935 handler->response_baton = delete_ctx;
1937 svn_ra_serf__request_create(handler);
1939 SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->done, ctx->session,
1940 pool));
1942 if (delete_ctx->status != 204)
1944 abort();
1947 return SVN_NO_ERROR;
1950 static svn_error_t *
1951 abort_edit(void *edit_baton,
1952 apr_pool_t *pool)
1954 commit_context_t *ctx = edit_baton;
1955 svn_ra_serf__handler_t *handler;
1956 svn_ra_serf__simple_request_context_t *delete_ctx;
1958 /* If an activity wasn't even created, don't bother trying to delete it. */
1959 if (! ctx->activity_url)
1960 return SVN_NO_ERROR;
1962 /* DELETE our aborted activity */
1963 handler = apr_pcalloc(pool, sizeof(*handler));
1964 handler->method = "DELETE";
1965 handler->path = ctx->activity_url;
1966 handler->conn = ctx->session->conns[0];
1967 handler->session = ctx->session;
1969 delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1971 handler->response_handler = svn_ra_serf__handle_status_only;
1972 handler->response_baton = delete_ctx;
1974 svn_ra_serf__request_create(handler);
1976 SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->done, ctx->session,
1977 pool));
1979 /* 204 if deleted,
1980 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
1981 404 if the activity wasn't found. */
1982 if (delete_ctx->status != 204 &&
1983 delete_ctx->status != 403 &&
1984 delete_ctx->status != 404
1987 abort();
1990 return SVN_NO_ERROR;
1993 svn_error_t *
1994 svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
1995 const svn_delta_editor_t **ret_editor,
1996 void **edit_baton,
1997 apr_hash_t *revprop_table,
1998 svn_commit_callback2_t callback,
1999 void *callback_baton,
2000 apr_hash_t *lock_tokens,
2001 svn_boolean_t keep_locks,
2002 apr_pool_t *pool)
2004 svn_ra_serf__session_t *session = ra_session->priv;
2005 svn_delta_editor_t *editor;
2006 commit_context_t *ctx;
2007 apr_hash_index_t *hi;
2009 ctx = apr_pcalloc(pool, sizeof(*ctx));
2011 ctx->pool = pool;
2013 ctx->session = session;
2014 ctx->conn = session->conns[0];
2016 ctx->revprop_table = apr_hash_make(pool);
2017 for (hi = apr_hash_first(pool, revprop_table); hi; hi = apr_hash_next(hi))
2019 const void *key;
2020 apr_ssize_t klen;
2021 void *val;
2023 apr_hash_this(hi, &key, &klen, &val);
2024 apr_hash_set(ctx->revprop_table, apr_pstrdup(pool, key), klen,
2025 svn_string_dup(val, pool));
2028 ctx->callback = callback;
2029 ctx->callback_baton = callback_baton;
2031 ctx->lock_tokens = lock_tokens;
2032 ctx->keep_locks = keep_locks;
2034 ctx->deleted_entries = apr_hash_make(ctx->pool);
2035 ctx->copied_entries = apr_hash_make(ctx->pool);
2037 editor = svn_delta_default_editor(pool);
2038 editor->open_root = open_root;
2039 editor->delete_entry = delete_entry;
2040 editor->add_directory = add_directory;
2041 editor->open_directory = open_directory;
2042 editor->change_dir_prop = change_dir_prop;
2043 editor->close_directory = close_directory;
2044 editor->absent_directory = absent_directory;
2045 editor->add_file = add_file;
2046 editor->open_file = open_file;
2047 editor->apply_textdelta = apply_textdelta;
2048 editor->change_file_prop = change_file_prop;
2049 editor->close_file = close_file;
2050 editor->absent_file = absent_file;
2051 editor->close_edit = close_edit;
2052 editor->abort_edit = abort_edit;
2054 *ret_editor = editor;
2055 *edit_baton = ctx;
2057 return SVN_NO_ERROR;
2060 svn_error_t *
2061 svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2062 svn_revnum_t rev,
2063 const char *name,
2064 const svn_string_t *value,
2065 apr_pool_t *pool)
2067 svn_ra_serf__session_t *session = ra_session->priv;
2068 svn_ra_serf__propfind_context_t *propfind_ctx;
2069 proppatch_context_t *proppatch_ctx;
2070 commit_context_t *commit;
2071 const char *vcc_url, *checked_in_href, *ns;
2072 apr_hash_t *props;
2073 svn_error_t *err;
2075 commit = apr_pcalloc(pool, sizeof(*commit));
2077 commit->pool = pool;
2079 commit->session = session;
2080 commit->conn = session->conns[0];
2082 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
2083 commit->session,
2084 commit->conn,
2085 commit->session->repos_url.path, pool));
2087 props = apr_hash_make(pool);
2089 propfind_ctx = NULL;
2090 svn_ra_serf__deliver_props(&propfind_ctx, props, commit->session,
2091 commit->conn, vcc_url, rev, "0",
2092 checked_in_props, FALSE, NULL, pool);
2094 SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, commit->session, pool));
2096 checked_in_href = svn_ra_serf__get_ver_prop(props, vcc_url, rev,
2097 "DAV:", "href");
2099 if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
2101 ns = SVN_DAV_PROP_NS_SVN;
2102 name += sizeof(SVN_PROP_PREFIX) - 1;
2104 else
2106 ns = SVN_DAV_PROP_NS_CUSTOM;
2109 /* PROPPATCH our log message and pass it along. */
2110 proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2111 proppatch_ctx->pool = pool;
2112 proppatch_ctx->commit = commit;
2113 proppatch_ctx->path = checked_in_href;
2114 proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
2115 proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
2117 if (value)
2119 svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
2120 ns, name, value, proppatch_ctx->pool);
2122 else
2124 value = svn_string_create("", proppatch_ctx->pool);
2126 svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
2127 ns, name, value, proppatch_ctx->pool);
2130 err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool);
2131 if (err)
2132 return
2133 svn_error_create
2134 (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
2135 _("DAV request failed; it's possible that the repository's "
2136 "pre-revprop-change hook either failed or is non-existent"));
2138 return SVN_NO_ERROR;