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 * ====================================================================
27 #include "svn_pools.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"
37 #include "svn_private_config.h"
38 #include "private/svn_dep_compat.h"
43 /* Structure associated with a CHECKOUT request. */
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
;
59 /* Baton passed back with the commit editor. */
61 /* Pool for our commit. */
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
;
72 apr_hash_t
*lock_tokens
;
73 svn_boolean_t keep_locks
;
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
;
95 /* Structure associated with a PROPPATCH request. */
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
;
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
;
123 /* Represents a directory. */
124 typedef struct dir_context_t
{
125 /* Pool for our directory. */
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.) */
147 struct dir_context_t
*parent_dir
;
149 /* The directory name; if NULL, we're the 'root' */
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
;
164 /* Represents a file to be committed. */
166 /* Pool for our file. */
169 /* The root commit we're in progress for. */
170 commit_context_t
*commit
;
172 /* Is this file being added? (Otherwise, just opened.) */
175 dir_context_t
*parent_dir
;
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
;
190 svn_stream_t
*stream
;
192 /* Temporary file containing the 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. */
211 /* Setup routines and handlers for various requests we'll invoke. */
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
,
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
,
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,
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
,
247 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
249 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN(CHECKOUT_TRAILER
,
250 sizeof(CHECKOUT_TRAILER
) - 1,
252 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
258 handle_checkout(serf_request_t
*request
,
259 serf_bucket_t
*response
,
263 checkout_context_t
*ctx
= handler_baton
;
266 status
= svn_ra_serf__handle_status_only(request
, response
, &ctx
->progress
,
269 /* Get the resulting location. */
270 if (ctx
->progress
.done
&& ctx
->progress
.status
== 201)
274 const char *location
;
276 hdrs
= serf_bucket_response_get_headers(response
);
277 location
= serf_bucket_headers_get(hdrs
, "Location");
282 apr_uri_parse(pool
, location
, &uri
);
284 ctx
->resource_url
= apr_pstrdup(ctx
->pool
, uri
.path
);
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. */
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
;
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 *),
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. */
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
);
329 checkout_dir(dir_context_t
*dir
)
331 checkout_context_t
*checkout_ctx
;
332 svn_ra_serf__handler_t
*handler
;
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
),
356 apr_hash_set(dir
->commit
->copied_entries
,
357 apr_pstrdup(dir
->commit
->pool
, dir
->name
),
358 APR_HASH_KEY_STRING
, (void*)1);
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
;
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
,
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
),
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"),
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
),
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
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
450 * Allocate the result in POOL, and use POOL for temporary allocation.
453 get_version_url(const char **checked_in_url
,
454 svn_ra_serf__session_t
*session
,
455 svn_ra_serf__connection_t
*conn
,
457 svn_revnum_t base_revision
,
458 const char *parent_vsn_url
,
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
,
469 SVN_RA_SERF__WC_CHECKED_IN_URL
,
470 ¤t_version
, pool
));
474 *checked_in_url
= current_version
->data
;
481 root_checkout
= parent_vsn_url
;
485 svn_ra_serf__propfind_context_t
*propfind_ctx
;
488 props
= apr_hash_make(pool
);
491 svn_ra_serf__deliver_props(&propfind_ctx
, props
, session
,
492 conn
, session
->repos_url
.path
,
494 checked_in_props
, FALSE
, NULL
, pool
);
496 SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx
, session
, pool
));
499 svn_ra_serf__get_ver_prop(props
, session
->repos_url
.path
,
500 base_revision
, "DAV:", "checked-in");
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
);
514 checkout_file(file_context_t
*file
)
516 svn_ra_serf__handler_t
*handler
;
519 if (file
->parent_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. */
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
,
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
,
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
,
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
),
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"),
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
),
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
,
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
;
630 if (svn_xml_is_xml_safe(val
->data
, val
->len
))
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("<",
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
)
660 SERF_BUCKET_SIMPLE_STRING_LEN(" V:encoding=\"base64\"",
661 sizeof(" V:encoding=\"base64\"") - 1,
663 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
666 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN(">",
669 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
671 if (binary_prop
== TRUE
)
673 val
= svn_base64_encode_string(val
, pool
);
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("</",
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(">",
696 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
702 setup_proppatch_headers(serf_bucket_t
*headers
,
706 proppatch_context_t
*proppatch
= baton
;
708 if (proppatch
->name
&& proppatch
->commit
->lock_tokens
)
712 token
= apr_hash_get(proppatch
->commit
->lock_tokens
, proppatch
->name
,
713 APR_HASH_KEY_STRING
);
717 const char *token_header
;
719 token_header
= apr_pstrcat(pool
, "(<", token
, ">)", NULL
);
721 serf_bucket_headers_set(headers
, "If", token_header
);
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
,
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,
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,
756 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
758 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN("<D:prop>",
759 sizeof("<D:prop>") - 1,
761 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
763 svn_ra_serf__walk_all_props(ctx
->changed_props
, ctx
->path
,
765 proppatch_walker
, body_bkt
, pool
);
767 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN("</D:prop>",
768 sizeof("</D:prop>") - 1,
770 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
772 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN("</D:set>",
773 sizeof("</D:set>") - 1,
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,
783 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
785 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN("<D:prop>",
786 sizeof("<D:prop>") - 1,
788 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
790 svn_ra_serf__walk_all_props(ctx
->removed_props
, ctx
->path
,
792 proppatch_walker
, body_bkt
, pool
);
794 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN("</D:prop>",
795 sizeof("</D:prop>") - 1,
797 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
799 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN("</D:remove>",
800 sizeof("</D:remove>") - 1,
802 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
805 tmp_bkt
= SERF_BUCKET_SIMPLE_STRING_LEN(PROPPATCH_TRAILER
,
806 sizeof(PROPPATCH_TRAILER
) - 1,
808 serf_bucket_aggregate_append(body_bkt
, tmp_bkt
);
814 proppatch_resource(proppatch_context_t
*proppatch
,
815 commit_context_t
*commit
,
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
)
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"));
853 static serf_bucket_t
*
854 create_put_body(void *baton
,
855 serf_bucket_alloc_t
*alloc
,
858 file_context_t
*ctx
= baton
;
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
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);
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
,
885 return SERF_BUCKET_SIMPLE_STRING("", alloc
);
889 setup_put_headers(serf_bucket_t
*headers
,
893 file_context_t
*ctx
= baton
;
895 if (ctx
->base_checksum
)
897 serf_bucket_headers_set(headers
, SVN_DAV_BASE_FULLTEXT_MD5_HEADER
,
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
)
911 token
= apr_hash_get(ctx
->commit
->lock_tokens
, ctx
->name
,
912 APR_HASH_KEY_STRING
);
916 const char *token_header
;
918 token_header
= apr_pstrcat(pool
, "(<", token
, ">)", NULL
);
920 serf_bucket_headers_set(headers
, "If", token_header
);
928 setup_copy_file_headers(serf_bucket_t
*headers
,
932 file_context_t
*file
= baton
;
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");
950 setup_copy_dir_headers(serf_bucket_t
*headers
,
954 dir_context_t
*dir
= baton
;
956 const char *absolute_uri
;
958 /* The Dest URI must be absolute. Bummer. */
959 uri
= dir
->commit
->session
->repos_url
;
961 (char*)svn_path_url_add_component(dir
->parent_dir
->checkout
->resource_url
,
962 svn_path_basename(dir
->name
, 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
,
987 setup_delete_headers(serf_bucket_t
*headers
,
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
);
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
,
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,
1033 serf_bucket_aggregate_append(body
, tmp
);
1035 svn_ra_serf__merge_lock_token_list(ctx
->lock_token_hash
, ctx
->path
,
1041 /* Helper function to write the svndiff stream to temporary file. */
1042 static svn_error_t
*
1043 svndiff_stream_write(void *file_baton
,
1047 file_context_t
*ctx
= file_baton
;
1048 apr_status_t status
;
1050 status
= apr_file_write_full(ctx
->svndiff
, data
, *len
, NULL
);
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
,
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
;
1074 const char *activity_str
;
1075 const char *vcc_url
;
1077 apr_hash_index_t
*hi
;
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
));
1099 activity_str
= svn_ra_serf__options_get_activity_collection(opt_ctx
);
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
,
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
,
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
,
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
;
1168 dir
->base_revision
= base_revision
;
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
))
1196 svn_string_t
*value
;
1199 apr_hash_this(hi
, &key
, NULL
, &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;
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
));
1221 return SVN_NO_ERROR
;
1224 static svn_error_t
*
1225 delete_entry(const char *path
,
1226 svn_revnum_t revision
,
1230 dir_context_t
*dir
= parent_baton
;
1231 delete_context_t
*delete_ctx
;
1232 svn_ra_serf__handler_t
*handler
;
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";
1257 svn_path_url_add_component(dir
->checkout
->resource_url
,
1258 svn_path_basename(path
, 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
);
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
));
1290 /* 204 No Content: item successfully deleted
1291 404 Not found: ignored, the item might have been deleted in this
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
,
1303 return SVN_NO_ERROR
;
1306 static svn_error_t
*
1307 add_directory(const char *path
,
1309 const char *copyfrom_path
,
1310 svn_revnum_t copyfrom_revision
,
1311 apr_pool_t
*dir_pool
,
1314 dir_context_t
*parent
= parent_baton
;
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
;
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
,
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
,
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
);
1368 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1369 _("Unable to parse URL '%s'"),
1373 SVN_ERR(svn_ra_serf__discover_root(&vcc_url
, &rel_copy_path
,
1374 dir
->commit
->session
,
1376 uri
.path
, dir
->pool
));
1377 SVN_ERR(svn_ra_serf__retrieve_props(props
,
1378 dir
->commit
->session
,
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");
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
,
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
);
1422 return SVN_NO_ERROR
;
1425 static svn_error_t
*
1426 open_directory(const char *path
,
1428 svn_revnum_t base_revision
,
1429 apr_pool_t
*dir_pool
,
1432 dir_context_t
*parent
= parent_baton
;
1435 dir
= apr_pcalloc(dir_pool
, sizeof(*dir
));
1437 dir
->pool
= dir_pool
;
1439 dir
->parent_dir
= parent
;
1440 dir
->commit
= parent
->commit
;
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
));
1454 return SVN_NO_ERROR
;
1457 static svn_error_t
*
1458 change_dir_prop(void *dir_baton
,
1460 const svn_string_t
*value
,
1463 dir_context_t
*dir
= dir_baton
;
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;
1478 ns
= SVN_DAV_PROP_NS_CUSTOM
;
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
);
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
,
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
,
1534 dir_context_t
*ctx
= parent_baton
;
1540 static svn_error_t
*
1541 add_file(const char *path
,
1543 const char *copy_path
,
1544 svn_revnum_t copy_revision
,
1545 apr_pool_t
*file_pool
,
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
;
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";
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
,
1606 if (head_ctx
->status
!= 404)
1608 return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS
, NULL
,
1609 _("File '%s' already exists"), path
);
1614 svn_path_url_add_component(dir
->checkout
->resource_url
,
1615 svn_path_basename(path
, new_file
->pool
),
1618 *file_baton
= new_file
;
1620 return SVN_NO_ERROR
;
1623 static svn_error_t
*
1624 open_file(const char *path
,
1626 svn_revnum_t base_revision
,
1627 apr_pool_t
*file_pool
,
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
;
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
,
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
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
,
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
,
1699 const svn_string_t
*value
,
1702 file_context_t
*file
= file_baton
;
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;
1714 ns
= SVN_DAV_PROP_NS_CUSTOM
;
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
);
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
,
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
;
1747 svn_ra_serf__handler_t
*handler
;
1748 svn_ra_serf__simple_request_context_t
*copy_ctx
;
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
);
1758 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
1759 _("Unable to parse URL '%s'"),
1763 SVN_ERR(svn_ra_serf__discover_root(&vcc_url
, &rel_copy_path
,
1764 ctx
->commit
->session
,
1767 SVN_ERR(svn_ra_serf__retrieve_props(props
,
1768 ctx
->commit
->session
,
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");
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(©_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
;
1835 handler
->body_delegate
= create_empty_put_body
;
1836 handler
->body_delegate_baton
= ctx
;
1837 handler
->body_type
= "text/plain";
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
,
1886 dir_context_t
*ctx
= parent_baton
;
1892 static svn_error_t
*
1893 close_edit(void *edit_baton
,
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
,
1907 ctx
->activity_url_len
,
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)
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
,
1942 if (delete_ctx
->status
!= 204)
1947 return SVN_NO_ERROR
;
1950 static svn_error_t
*
1951 abort_edit(void *edit_baton
,
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
,
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
1990 return SVN_NO_ERROR
;
1994 svn_ra_serf__get_commit_editor(svn_ra_session_t
*ra_session
,
1995 const svn_delta_editor_t
**ret_editor
,
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
,
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
));
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
))
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
;
2057 return SVN_NO_ERROR
;
2061 svn_ra_serf__change_rev_prop(svn_ra_session_t
*ra_session
,
2064 const svn_string_t
*value
,
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
;
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
,
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
,
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;
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
);
2119 svn_ra_serf__set_prop(proppatch_ctx
->changed_props
, proppatch_ctx
->path
,
2120 ns
, name
, value
, proppatch_ctx
->pool
);
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
);
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
;