In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_neon / merge.c
blob957d4dc5c7915a48ac24703b423e67c837e9c8b6
1 /*
2 * merge.c : routines for performing a MERGE server requests
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <apr_pools.h>
22 #include <apr_hash.h>
23 #define APR_WANT_STRFUNC
24 #include <apr_want.h>
26 #include "svn_string.h"
27 #include "svn_error.h"
28 #include "svn_path.h"
29 #include "svn_ra.h"
30 #include "svn_pools.h"
31 #include "svn_props.h"
32 #include "svn_xml.h"
34 #include "private/svn_dav_protocol.h"
35 #include "svn_private_config.h"
37 #include "ra_neon.h"
40 static const svn_ra_neon__xml_elm_t merge_elements[] =
42 { "DAV:", "updated-set", ELEM_updated_set, 0 },
43 { "DAV:", "merged-set", ELEM_merged_set, 0 },
44 { "DAV:", "ignored-set", ELEM_ignored_set, 0 },
45 { "DAV:", "href", ELEM_href, SVN_RA_NEON__XML_CDATA },
46 { "DAV:", "merge-response", ELEM_merge_response, 0 },
47 { "DAV:", "checked-in", ELEM_checked_in, 0 },
48 { "DAV:", "response", ELEM_response, 0 },
49 { "DAV:", "propstat", ELEM_propstat, 0 },
50 { "DAV:", "status", ELEM_status, SVN_RA_NEON__XML_CDATA },
51 { "DAV:", "responsedescription", ELEM_responsedescription,
52 SVN_RA_NEON__XML_CDATA },
53 { "DAV:", "prop", ELEM_prop, 0 },
54 { "DAV:", "resourcetype", ELEM_resourcetype, 0 },
55 { "DAV:", "collection", ELEM_collection, 0 },
56 { "DAV:", "baseline", ELEM_baseline, 0 },
57 { "DAV:", SVN_DAV__VERSION_NAME, ELEM_version_name, SVN_RA_NEON__XML_CDATA },
58 { SVN_XML_NAMESPACE, "post-commit-err",
59 ELEM_post_commit_err, SVN_RA_NEON__XML_CDATA },
60 { "DAV:", SVN_DAV__CREATIONDATE, ELEM_creationdate, SVN_RA_NEON__XML_CDATA },
61 { "DAV:", "creator-displayname", ELEM_creator_displayname,
62 SVN_RA_NEON__XML_CDATA },
64 { NULL }
67 enum merge_rtype {
68 RTYPE_UNKNOWN, /* unknown (haven't seen it in the response yet) */
69 RTYPE_REGULAR, /* a regular (member) resource */
70 RTYPE_COLLECTION, /* a collection resource */
71 RTYPE_BASELINE /* a baseline resource */
74 typedef struct {
75 /*WARNING: WANT_CDATA should stay the first element in the baton:
76 svn_ra_neon__xml_collect_cdata() assumes the baton starts with a stringbuf.
78 svn_stringbuf_t *want_cdata;
79 svn_stringbuf_t *cdata;
80 apr_pool_t *pool;
82 /* a clearable subpool of pool, for loops. Do not use for anything
83 that must persist beyond the scope of your function! */
84 apr_pool_t *scratchpool;
86 /* the BASE_HREF contains the merge target. as resources are specified in
87 the merge response, we make their URLs relative to this URL, thus giving
88 us a path for use in the commit callbacks. */
89 const char *base_href;
91 svn_revnum_t rev; /* the new/target revision number for this commit */
93 svn_boolean_t response_has_error;
94 int response_parent; /* what element did DAV:response appear within? */
96 int href_parent; /* what element is the DAV:href appearing within? */
97 svn_stringbuf_t *href; /* current response */
99 int status; /* HTTP status for this DAV:propstat */
100 enum merge_rtype rtype; /* DAV:resourcetype of this resource */
102 svn_stringbuf_t *vsn_name; /* DAV:version-name for this resource */
103 svn_stringbuf_t *vsn_url; /* DAV:checked-in for this resource */
104 svn_stringbuf_t *committed_date; /* DAV:creationdate for this resource */
105 svn_stringbuf_t *last_author; /* DAV:creator-displayname for this
106 resource */
107 svn_stringbuf_t *post_commit_err;/* SVN_XML_NAMESPACE:post-commit hook's
108 stderr */
110 /* We only invoke set_prop() on targets listed in valid_targets.
111 Some entities (such as directories that have had changes
112 committed underneath but are not themselves targets) will be
113 mentioned in the merge response but not appear in
114 valid_targets. */
115 apr_hash_t *valid_targets;
117 /* Client callbacks */
118 svn_ra_push_wc_prop_func_t push_prop;
119 void *cb_baton; /* baton for above */
121 } merge_ctx_t;
124 static void add_ignored(merge_ctx_t *mc, const char *cdata)
126 /* ### the server didn't check in the file(!) */
127 /* ### remember the file and issue a report/warning later */
131 static svn_boolean_t okay_to_bump_path(const char *path,
132 apr_hash_t *valid_targets,
133 apr_pool_t *pool)
135 svn_stringbuf_t *parent_path;
136 enum svn_recurse_kind r;
138 /* Easy check: if path itself is in the hash, then it's legit. */
139 if (apr_hash_get(valid_targets, path, APR_HASH_KEY_STRING))
140 return TRUE;
141 /* Otherwise, this path is bumpable IFF one of its parents is in the
142 hash and marked with a 'recursion' flag. */
143 parent_path = svn_stringbuf_create(path, pool);
145 do {
146 apr_size_t len = parent_path->len;
147 svn_path_remove_component(parent_path);
148 if (len == parent_path->len)
149 break;
150 r = (enum svn_recurse_kind) apr_hash_get(valid_targets,
151 parent_path->data,
152 APR_HASH_KEY_STRING);
153 if (r == svn_recursive)
154 return TRUE;
156 } while (! svn_path_is_empty(parent_path->data));
158 /* Default answer: if we get here, don't allow the bumping. */
159 return FALSE;
163 /* If committed PATH appears in MC->valid_targets, and an MC->push_prop
164 * function exists, then store VSN_URL as the SVN_RA_NEON__LP_VSN_URL
165 * property on PATH. Use POOL for all allocations.
167 * Otherwise, just return SVN_NO_ERROR.
169 static svn_error_t *bump_resource(merge_ctx_t *mc,
170 const char *path,
171 char *vsn_url,
172 apr_pool_t *pool)
174 /* no sense in doing any more work if there's no property setting
175 function at our disposal. */
176 if (mc->push_prop == NULL)
177 return SVN_NO_ERROR;
179 /* Only invoke a client callback on PATH if PATH counts as a
180 committed target. The commit-tracking editor built this list for
181 us, and took care not to include directories unless they were
182 directly committed (i.e., received a property change). */
183 if (! okay_to_bump_path(path, mc->valid_targets, pool))
184 return SVN_NO_ERROR;
186 /* Okay, NOW set the new version url. */
188 svn_string_t vsn_url_str; /* prop setter wants an svn_string_t */
190 vsn_url_str.data = vsn_url;
191 vsn_url_str.len = strlen(vsn_url);
193 SVN_ERR((*mc->push_prop)(mc->cb_baton, path,
194 SVN_RA_NEON__LP_VSN_URL, &vsn_url_str,
195 pool));
198 return SVN_NO_ERROR;
201 static svn_error_t * handle_resource(merge_ctx_t *mc,
202 apr_pool_t *pool)
204 const char *relative;
206 if (mc->response_has_error)
208 /* ### what to do? */
209 /* ### return "no error", presuming whatever set response_has_error
210 ### has already handled the problem. */
211 return SVN_NO_ERROR;
213 if (mc->response_parent == ELEM_merged_set)
215 /* ### shouldn't have happened. we told the server "don't merge" */
216 /* ### need something better than APR_EGENERAL */
217 return svn_error_createf(APR_EGENERAL, NULL,
218 _("Protocol error: we told the server not to "
219 "auto-merge any resources, but it said that "
220 "'%s' was merged"), mc->href->data);
222 if (mc->response_parent != ELEM_updated_set)
224 /* ### unknown parent for this response(!) */
225 /* ### need something better than APR_EGENERAL */
226 return svn_error_createf(APR_EGENERAL, NULL,
227 _("Internal error: there is an unknown parent "
228 "(%d) for the 'DAV:response' element within"
229 " the MERGE response"), mc->response_parent);
231 #if 0
232 /* ### right now, the server isn't sending everything for all resources.
233 ### just skip this requirement. */
234 if (mc->href->len == 0
235 || mc->vsn_name->len == 0
236 || mc->vsn_url->len == 0
237 || mc->rtype == RTYPE_UNKNOWN)
239 /* one or more properties were missing in the DAV:response for the
240 resource. */
241 return svn_error_createf(APR_EGENERAL, NULL,
242 _("Protocol error: the MERGE response for the "
243 "'%s' resource did not return all of the "
244 "properties that we asked for (and need to "
245 "complete the commit)"), mc->href->data);
247 #endif
249 if (mc->rtype == RTYPE_BASELINE)
251 /* cool. the DAV:version-name tells us the new revision */
252 mc->rev = SVN_STR_TO_REV(mc->vsn_name->data);
253 return SVN_NO_ERROR;
256 /* a collection or regular resource */
257 if (! svn_path_is_ancestor(mc->base_href, mc->href->data))
259 /* ### need something better than APR_EGENERAL */
260 return svn_error_createf(APR_EGENERAL, NULL,
261 _("A MERGE response for '%s' is not a child "
262 "of the destination ('%s')"),
263 mc->href->data, mc->base_href);
266 /* given HREF of the form: BASE "/" RELATIVE, extract the relative portion */
267 relative = svn_path_is_child(mc->base_href, mc->href->data, NULL);
268 if (! relative) /* the paths are equal */
269 relative = "";
271 /* bump the resource */
272 relative = svn_path_uri_decode(relative, pool);
273 return bump_resource(mc, relative, mc->vsn_url->data, pool);
276 /* Determine whether we're receiving the expected XML response.
277 Return CHILD when interested in receiving the child's contents
278 or one of SVN_RA_NEON__XML_INVALID and SVN_RA_NEON__XML_DECLINE
279 when respectively this is the incorrect response or
280 the element (and its children) are uninteresting */
281 static int validate_element(svn_ra_neon__xml_elmid parent,
282 svn_ra_neon__xml_elmid child)
284 if ((child == ELEM_collection || child == ELEM_baseline)
285 && parent != ELEM_resourcetype) {
286 /* ### technically, they could occur elsewhere, but screw it */
287 return SVN_RA_NEON__XML_INVALID;
290 switch (parent)
292 case ELEM_root:
293 if (child == ELEM_merge_response)
294 return child;
295 else
296 return SVN_RA_NEON__XML_INVALID;
298 case ELEM_merge_response:
299 if (child == ELEM_updated_set
300 || child == ELEM_merged_set
301 || child == ELEM_ignored_set)
302 return child;
303 else
304 return SVN_RA_NEON__XML_DECLINE; /* any child is allowed */
306 case ELEM_updated_set:
307 case ELEM_merged_set:
308 if (child == ELEM_response)
309 return child;
310 else
311 return SVN_RA_NEON__XML_DECLINE; /* ignore if something else
312 was in there */
314 case ELEM_ignored_set:
315 if (child == ELEM_href)
316 return child;
317 else
318 return SVN_RA_NEON__XML_DECLINE; /* ignore if something else
319 was in there */
321 case ELEM_response:
322 if (child == ELEM_href
323 || child == ELEM_status
324 || child == ELEM_propstat)
325 return child;
326 else if (child == ELEM_responsedescription)
327 /* ### I think we want this... to save a message for the user */
328 return SVN_RA_NEON__XML_DECLINE; /* valid, but we don't need to see it */
329 else
330 return SVN_RA_NEON__XML_DECLINE; /* ignore if something else
331 was in there */
333 case ELEM_propstat:
334 if (child == ELEM_prop || child == ELEM_status)
335 return child;
336 else if (child == ELEM_responsedescription)
337 /* ### I think we want this... to save a message for the user */
338 return SVN_RA_NEON__XML_DECLINE; /* valid, but we don't need to see it */
339 else
340 return SVN_RA_NEON__XML_DECLINE; /* ignore if something else
341 was in there */
343 case ELEM_prop:
344 if (child == ELEM_checked_in
345 || child == ELEM_resourcetype
346 || child == ELEM_version_name
347 || child == ELEM_creationdate
348 || child == ELEM_creator_displayname
349 || child == ELEM_post_commit_err
350 /* other props */)
351 return child;
352 else
353 return SVN_RA_NEON__XML_DECLINE; /* ignore other props */
355 case ELEM_checked_in:
356 if (child == ELEM_href)
357 return child;
358 else
359 return SVN_RA_NEON__XML_DECLINE; /* ignore if something else
360 was in there */
362 case ELEM_resourcetype:
363 if (child == ELEM_collection || child == ELEM_baseline)
364 return child;
365 else
366 return SVN_RA_NEON__XML_DECLINE; /* ignore if something else
367 was in there */
369 default:
370 return SVN_RA_NEON__XML_DECLINE;
373 /* NOTREACHED */
376 static svn_error_t *
377 start_element(int *elem, void *baton, int parent,
378 const char *nspace, const char *name, const char **atts)
380 const svn_ra_neon__xml_elm_t *elm
381 = svn_ra_neon__lookup_xml_elem(merge_elements, nspace, name);
382 merge_ctx_t *mc = baton;
384 *elem = elm ? validate_element(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;
385 if (*elem < 1) /* not a valid element */
386 return SVN_NO_ERROR;
388 switch (elm->id)
390 case ELEM_response:
391 mc->response_has_error = FALSE;
393 /* for each response (which corresponds to one resource), note that we
394 haven't seen its resource type yet */
395 mc->rtype = RTYPE_UNKNOWN;
397 /* and we haven't seen these elements yet */
398 mc->href->len = 0;
399 mc->vsn_name->len = 0;
400 mc->vsn_url->len = 0;
402 /* FALLTHROUGH */
404 case ELEM_ignored_set:
405 case ELEM_checked_in:
406 /* if we see an href "soon", then its parent is ELM */
407 mc->href_parent = elm->id;
408 break;
410 case ELEM_updated_set:
411 case ELEM_merged_set:
412 mc->response_parent = elm->id;
413 break;
415 case ELEM_propstat:
416 /* initialize the status so we can figure out if we ever saw a
417 status element in the propstat */
418 mc->status = 0;
419 break;
421 case ELEM_resourcetype:
422 /* we've seen a DAV:resourcetype, so it will be "regular" unless we
423 see something within this element */
424 mc->rtype = RTYPE_REGULAR;
425 break;
427 case ELEM_collection:
428 mc->rtype = RTYPE_COLLECTION;
429 break;
431 case ELEM_baseline:
432 mc->rtype = RTYPE_BASELINE;
433 break;
435 default:
436 /* one of: ELEM_href, ELEM_status, ELEM_prop,
437 ELEM_version_name */
438 break;
441 switch (elm->id)
443 case ELEM_href:
444 case ELEM_status:
445 case ELEM_version_name:
446 case ELEM_post_commit_err:
447 case ELEM_creationdate:
448 case ELEM_creator_displayname:
449 mc->want_cdata = mc->cdata;
450 svn_stringbuf_setempty(mc->cdata);
451 break;
453 default:
454 mc->want_cdata = NULL;
455 break;
459 return SVN_NO_ERROR;
462 static svn_error_t *
463 end_element(void *baton, int state,
464 const char *nspace, const char *name)
466 merge_ctx_t *mc = baton;
468 switch (state)
470 case ELEM_href:
471 switch (mc->href_parent)
473 case ELEM_ignored_set:
474 add_ignored(mc, mc->cdata->data);
475 break;
477 case ELEM_response:
478 /* we're now working on this href... */
479 SVN_ERR(svn_ra_neon__copy_href(mc->href, mc->cdata->data,
480 mc->scratchpool));
481 break;
483 case ELEM_checked_in:
484 SVN_ERR(svn_ra_neon__copy_href(mc->vsn_url, mc->cdata->data,
485 mc->scratchpool));
486 break;
488 break;
490 case ELEM_responsedescription:
491 /* ### I don't think we'll see this right now, due to validate_element */
492 /* ### remember this for error messages? */
493 break;
495 case ELEM_status:
497 ne_status hs;
499 if (ne_parse_statusline(mc->cdata->data, &hs) != 0)
500 mc->response_has_error = TRUE;
501 else
503 mc->status = hs.code;
504 if (hs.code != 200)
506 /* ### create an error structure? */
507 mc->response_has_error = TRUE;
509 free(hs.reason_phrase);
511 if (mc->response_has_error)
513 /* ### fix this error value */
514 return svn_error_create(APR_EGENERAL, NULL,
515 _("The MERGE property response had an "
516 "error status"));
519 break;
521 case ELEM_propstat:
522 /* ### does Neon have a symbol for 200? */
523 if (mc->status == 200 /* OK */)
525 /* ### what to do? reset all the data? */
527 /* ### else issue an error? status==0 means we never saw one */
528 break;
530 case ELEM_response:
532 /* the end of a DAV:response means that we've seen all the information
533 related to this resource. process it. */
534 SVN_ERR(handle_resource(mc, mc->scratchpool));
535 svn_pool_clear(mc->scratchpool);
537 break;
539 case ELEM_checked_in:
540 /* When we leave a DAV:checked-in element, the parents are DAV:prop,
541 DAV:propstat, then DAV:response. If we see a DAV:href "on the way
542 out", then it is going to belong to the DAV:response. */
543 mc->href_parent = ELEM_response;
544 break;
546 case ELEM_version_name:
547 svn_stringbuf_set(mc->vsn_name, mc->cdata->data);
548 break;
550 case ELEM_post_commit_err:
551 svn_stringbuf_set(mc->post_commit_err, mc->cdata->data);
552 break;
554 case ELEM_creationdate:
555 svn_stringbuf_set(mc->committed_date, mc->cdata->data);
556 break;
558 case ELEM_creator_displayname:
559 svn_stringbuf_set(mc->last_author, mc->cdata->data);
560 break;
562 default:
563 /* one of: ELEM_updated_set, ELEM_merged_set, ELEM_ignored_set,
564 ELEM_prop, ELEM_resourcetype, ELEM_collection, ELEM_baseline */
565 break;
568 return SVN_NO_ERROR;
572 svn_error_t * svn_ra_neon__assemble_locktoken_body(svn_stringbuf_t **body,
573 apr_hash_t *lock_tokens,
574 apr_pool_t *pool)
576 apr_hash_index_t *hi;
577 apr_size_t buf_size;
578 const char *closing_tag = "</S:lock-token-list>";
579 apr_size_t closing_tag_size = strlen(closing_tag);
580 apr_pool_t *tmppool = svn_pool_create(pool);
581 apr_hash_t *xml_locks = apr_hash_make(tmppool);
582 svn_stringbuf_t *lockbuf = svn_stringbuf_create
583 ("<S:lock-token-list xmlns:S=\"" SVN_XML_NAMESPACE "\">" DEBUG_CR, pool);
585 buf_size = lockbuf->len;
587 #define SVN_LOCK "<S:lock>" DEBUG_CR
588 #define SVN_LOCK_LEN sizeof(SVN_LOCK)-1
589 #define SVN_LOCK_CLOSE "</S:lock>" DEBUG_CR
590 #define SVN_LOCK_CLOSE_LEN sizeof(SVN_LOCK_CLOSE)-1
591 #define SVN_LOCK_PATH "<S:lock-path>"
592 #define SVN_LOCK_PATH_LEN sizeof(SVN_LOCK_PATH)-1
593 #define SVN_LOCK_PATH_CLOSE "</S:lock-path>" DEBUG_CR
594 #define SVN_LOCK_PATH_CLOSE_LEN sizeof(SVN_LOCK_CLOSE)-1
595 #define SVN_LOCK_TOKEN "<S:lock-token>"
596 #define SVN_LOCK_TOKEN_LEN sizeof(SVN_LOCK_TOKEN)-1
597 #define SVN_LOCK_TOKEN_CLOSE "</S:lock-token>" DEBUG_CR
598 #define SVN_LOCK_TOKEN_CLOSE_LEN sizeof(SVN_LOCK_TOKEN_CLOSE)-1
600 /* First, figure out how much string data we're talking about,
601 and allocate a stringbuf big enough to hold it all... we *never*
602 want have the stringbuf do an auto-reallocation. While here,
603 we'll be copying our hash of paths -> tokens into a hash of
604 xml-escaped-paths -> tokens. */
605 for (hi = apr_hash_first(tmppool, lock_tokens); hi; hi = apr_hash_next(hi))
607 const void *key;
608 void *val;
609 apr_ssize_t klen;
610 svn_string_t lock_path;
611 svn_stringbuf_t *lock_path_xml = NULL;
613 apr_hash_this(hi, &key, &klen, &val);
615 /* XML-escape our key and store it in our temporary hash. */
616 lock_path.data = key;
617 lock_path.len = klen;
618 svn_xml_escape_cdata_string(&lock_path_xml, &lock_path, tmppool);
619 apr_hash_set(xml_locks, lock_path_xml->data, lock_path_xml->len, val);
621 /* Now, on with the stringbuf calculations. */
622 buf_size += SVN_LOCK_LEN;
623 buf_size += SVN_LOCK_PATH_LEN;
624 buf_size += lock_path_xml->len;
625 buf_size += SVN_LOCK_PATH_CLOSE_LEN;
626 buf_size += SVN_LOCK_TOKEN_LEN;
627 buf_size += strlen(val);
628 buf_size += SVN_LOCK_TOKEN_CLOSE_LEN;
629 buf_size += SVN_LOCK_CLOSE_LEN;
632 buf_size += closing_tag_size;
634 svn_stringbuf_ensure(lockbuf, buf_size + 1);
636 /* Now append all the temporary hash's keys and values into the
637 stringbuf. This is better than doing apr_pstrcat() in a loop,
638 because (1) there's no need to constantly re-alloc, and (2) the
639 stringbuf already knows the end of the buffer, so there's no
640 seek-time to the end of the string when appending. */
641 for (hi = apr_hash_first(tmppool, xml_locks); hi; hi = apr_hash_next(hi))
643 const void *key;
644 apr_ssize_t klen;
645 void *val;
647 apr_hash_this(hi, &key, &klen, &val);
649 svn_stringbuf_appendcstr(lockbuf, SVN_LOCK);
650 svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_PATH);
651 svn_stringbuf_appendbytes(lockbuf, key, klen);
652 svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_PATH_CLOSE);
653 svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_TOKEN);
654 svn_stringbuf_appendcstr(lockbuf, val);
655 svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_TOKEN_CLOSE);
656 svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_CLOSE);
659 svn_stringbuf_appendcstr(lockbuf, closing_tag);
661 #undef SVN_LOCK
662 #undef SVN_LOCK_LEN
663 #undef SVN_LOCK_CLOSE
664 #undef SVN_LOCK_CLOSE_LEN
665 #undef SVN_LOCK_PATH
666 #undef SVN_LOCK_PATH_LEN
667 #undef SVN_LOCK_PATH_CLOSE
668 #undef SVN_LOCK_PATH_CLOSE_LEN
669 #undef SVN_LOCK_TOKEN
670 #undef SVN_LOCK_TOKEN_LEN
671 #undef SVN_LOCK_TOKEN_CLOSE
672 #undef SVN_LOCK_TOKEN_CLOSE_LEN
674 *body = lockbuf;
676 svn_pool_destroy(tmppool);
677 return SVN_NO_ERROR;
682 svn_error_t * svn_ra_neon__merge_activity(svn_revnum_t *new_rev,
683 const char **committed_date,
684 const char **committed_author,
685 const char **post_commit_err,
686 svn_ra_neon__session_t *ras,
687 const char *repos_url,
688 const char *activity_url,
689 apr_hash_t *valid_targets,
690 apr_hash_t *lock_tokens,
691 svn_boolean_t keep_locks,
692 svn_boolean_t disable_merge_response,
693 apr_pool_t *pool)
695 merge_ctx_t mc = { 0 };
696 const char *body;
697 apr_hash_t *extra_headers = NULL;
698 svn_stringbuf_t *lockbuf = svn_stringbuf_create("", pool);
700 mc.cdata = svn_stringbuf_create("", pool);
701 mc.pool = pool;
702 mc.scratchpool = svn_pool_create(pool);
703 mc.base_href = repos_url;
704 mc.rev = SVN_INVALID_REVNUM;
706 mc.valid_targets = valid_targets;
707 mc.push_prop = ras->callbacks->push_wc_prop;
708 mc.cb_baton = ras->callback_baton;
710 mc.href = MAKE_BUFFER(pool);
711 mc.vsn_name = MAKE_BUFFER(pool);
712 mc.vsn_url = MAKE_BUFFER(pool);
713 mc.committed_date = MAKE_BUFFER(pool);
714 mc.last_author = MAKE_BUFFER(pool);
715 if (post_commit_err)
716 mc.post_commit_err = MAKE_BUFFER(pool);
718 if (disable_merge_response
719 || (! keep_locks))
721 const char *value;
723 value = apr_psprintf(pool, "%s %s",
724 disable_merge_response ?
725 SVN_DAV_OPTION_NO_MERGE_RESPONSE : "",
726 keep_locks ?
727 "" : SVN_DAV_OPTION_RELEASE_LOCKS);
729 if (! extra_headers)
730 extra_headers = apr_hash_make(pool);
731 apr_hash_set(extra_headers, SVN_DAV_OPTIONS_HEADER, APR_HASH_KEY_STRING,
732 value);
735 /* Need to marshal the whole [path->token] hash to the server as
736 a string within the body of the MERGE request. */
737 if ((lock_tokens != NULL)
738 && (apr_hash_count(lock_tokens) > 0))
739 SVN_ERR(svn_ra_neon__assemble_locktoken_body(&lockbuf, lock_tokens, pool));
741 body = apr_psprintf(pool,
742 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
743 "<D:merge xmlns:D=\"DAV:\">"
744 "<D:source><D:href>%s</D:href></D:source>"
745 "<D:no-auto-merge/><D:no-checkout/>"
746 "<D:prop><D:checked-in/>"
747 "<D:" SVN_DAV__VERSION_NAME "/><D:resourcetype/>"
748 "<D:" SVN_DAV__CREATIONDATE "/><D:creator-displayname/>"
749 "</D:prop>"
750 "%s"
751 "</D:merge>",
752 activity_url, lockbuf->data);
754 SVN_ERR(svn_ra_neon__parsed_request(ras, "MERGE", repos_url,
755 body, 0, NULL,
756 start_element,
757 svn_ra_neon__xml_collect_cdata,
758 end_element, &mc, extra_headers,
759 NULL, FALSE, pool));
761 /* return some commit properties to the caller. */
762 if (new_rev)
763 *new_rev = mc.rev;
764 if (committed_date)
765 *committed_date = mc.committed_date->len
766 ? apr_pstrdup(pool, mc.committed_date->data) : NULL;
767 if (committed_author)
768 *committed_author = mc.last_author->len
769 ? apr_pstrdup(pool, mc.last_author->data) : NULL;
770 if (post_commit_err)
771 *post_commit_err = mc.post_commit_err->len
772 ? apr_pstrdup(pool, mc.post_commit_err->data) : NULL;
774 svn_pool_destroy(mc.scratchpool);
776 return NULL;