]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_serf/merge.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_ra_serf / merge.c
1 /*
2  * merge.c :  MERGE response parsing functions for ra_serf
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 \f
26 #include <apr_uri.h>
27
28 #include <serf.h>
29
30 #include "svn_hash.h"
31 #include "svn_pools.h"
32 #include "svn_ra.h"
33 #include "svn_dav.h"
34 #include "svn_xml.h"
35 #include "svn_config.h"
36 #include "svn_dirent_uri.h"
37 #include "svn_props.h"
38
39 #include "private/svn_dav_protocol.h"
40 #include "private/svn_fspath.h"
41 #include "svn_private_config.h"
42
43 #include "ra_serf.h"
44 #include "../libsvn_ra/ra_loader.h"
45
46 \f
47 /*
48  * This enum represents the current state of our XML parsing for a MERGE.
49  */
50 typedef enum merge_state_e {
51   INITIAL = XML_STATE_INITIAL,
52   MERGE_RESPONSE,
53   UPDATED_SET,
54   RESPONSE,
55   HREF,
56   PROPSTAT,
57   PROP,
58   RESOURCE_TYPE,
59   BASELINE,
60   COLLECTION,
61   SKIP_HREF,
62   CHECKED_IN,
63   VERSION_NAME,
64   DATE,
65   AUTHOR,
66   POST_COMMIT_ERR,
67
68   STATUS
69 } merge_state_e;
70
71
72 /* Structure associated with a MERGE request. */
73 typedef struct merge_context_t
74 {
75   apr_pool_t *pool;
76
77   svn_ra_serf__session_t *session;
78   svn_ra_serf__handler_t *handler;
79
80   apr_hash_t *lock_tokens;
81   svn_boolean_t keep_locks;
82   svn_boolean_t disable_merge_response;
83
84   const char *merge_resource_url; /* URL of resource to be merged. */
85   const char *merge_url; /* URL at which the MERGE request is aimed. */
86
87   svn_commit_info_t *commit_info;
88
89 } merge_context_t;
90
91
92 #define D_ "DAV:"
93 #define S_ SVN_XML_NAMESPACE
94 static const svn_ra_serf__xml_transition_t merge_ttable[] = {
95   { INITIAL, D_, "merge-response", MERGE_RESPONSE,
96     FALSE, { NULL }, FALSE },
97
98   { MERGE_RESPONSE, D_, "updated-set", UPDATED_SET,
99     FALSE, { NULL }, FALSE },
100
101   { UPDATED_SET, D_, "response", RESPONSE,
102     FALSE, { NULL }, TRUE },
103
104   { RESPONSE, D_, "href", HREF,
105     TRUE, { NULL }, TRUE },
106
107   { RESPONSE, D_, "propstat", PROPSTAT,
108     FALSE, { NULL }, FALSE },
109
110 #if 0
111   /* Not needed.  */
112   { PROPSTAT, D_, "status", STATUS,
113     FALSE, { NULL }, FALSE },
114 #endif
115
116   { PROPSTAT, D_, "prop", PROP,
117     FALSE, { NULL }, FALSE },
118
119   { PROP, D_, "resourcetype", RESOURCE_TYPE,
120     FALSE, { NULL }, FALSE },
121
122   { RESOURCE_TYPE, D_, "baseline", BASELINE,
123     FALSE, { NULL }, TRUE },
124
125   { RESOURCE_TYPE, D_, "collection", COLLECTION,
126     FALSE, { NULL }, TRUE },
127
128   { PROP, D_, "checked-in", SKIP_HREF,
129     FALSE, { NULL }, FALSE },
130
131   { SKIP_HREF, D_, "href", CHECKED_IN,
132     TRUE, { NULL }, TRUE },
133
134   { PROP, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
135     TRUE, { NULL }, TRUE },
136
137   { PROP, D_, SVN_DAV__CREATIONDATE, DATE,
138     TRUE, { NULL }, TRUE },
139
140   { PROP, D_, "creator-displayname", AUTHOR,
141     TRUE, { NULL }, TRUE },
142
143   { PROP, S_, "post-commit-err", POST_COMMIT_ERR,
144     TRUE, { NULL }, TRUE },
145
146   { 0 }
147 };
148
149
150 /* Conforms to svn_ra_serf__xml_closed_t  */
151 static svn_error_t *
152 merge_closed(svn_ra_serf__xml_estate_t *xes,
153              void *baton,
154              int leaving_state,
155              const svn_string_t *cdata,
156              apr_hash_t *attrs,
157              apr_pool_t *scratch_pool)
158 {
159   merge_context_t *merge_ctx = baton;
160
161   if (leaving_state == RESPONSE)
162     {
163       const char *rtype;
164
165       rtype = svn_hash_gets(attrs, "resourcetype");
166
167       /* rtype can only be "baseline" or "collection" (or NULL). We can
168          keep this check simple.  */
169       if (rtype && *rtype == 'b')
170         {
171           const char *rev_str;
172
173           rev_str = svn_hash_gets(attrs, "revision");
174           if (rev_str)
175             {
176               apr_int64_t rev;
177
178               SVN_ERR(svn_cstring_atoi64(&rev, rev_str));
179               merge_ctx->commit_info->revision = (svn_revnum_t)rev;
180             }
181           else
182             merge_ctx->commit_info->revision = SVN_INVALID_REVNUM;
183
184           merge_ctx->commit_info->date =
185               apr_pstrdup(merge_ctx->pool,
186                           svn_hash_gets(attrs, "date"));
187
188           merge_ctx->commit_info->author =
189               apr_pstrdup(merge_ctx->pool,
190                           svn_hash_gets(attrs, "author"));
191
192           merge_ctx->commit_info->post_commit_err =
193              apr_pstrdup(merge_ctx->pool,
194                          svn_hash_gets(attrs, "post-commit-err"));
195         }
196       else
197         {
198           const char *href;
199
200           href = svn_urlpath__skip_ancestor(
201                    merge_ctx->merge_url,
202                    svn_hash_gets(attrs, "href"));
203
204           if (href == NULL)
205             return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
206                                      _("A MERGE response for '%s' is not "
207                                        "a child of the destination ('%s')"),
208                                      href, merge_ctx->merge_url);
209
210           /* We now need to dive all the way into the WC to update the
211              base VCC url.  */
212           if (!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(merge_ctx->session)
213               && merge_ctx->session->wc_callbacks->push_wc_prop)
214             {
215               const char *checked_in;
216               svn_string_t checked_in_str;
217
218               checked_in = svn_hash_gets(attrs, "checked-in");
219               checked_in_str.data = checked_in;
220               checked_in_str.len = strlen(checked_in);
221
222               SVN_ERR(merge_ctx->session->wc_callbacks->push_wc_prop(
223                         merge_ctx->session->wc_callback_baton,
224                         href,
225                         SVN_RA_SERF__WC_CHECKED_IN_URL,
226                         &checked_in_str,
227                         scratch_pool));
228             }
229         }
230     }
231   else if (leaving_state == BASELINE)
232     {
233       svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "baseline");
234     }
235   else if (leaving_state == COLLECTION)
236     {
237       svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "collection");
238     }
239   else
240     {
241       const char *name;
242       const char *value = cdata->data;
243
244       if (leaving_state == HREF)
245         {
246           name = "href";
247           value = svn_urlpath__canonicalize(value, scratch_pool);
248         }
249       else if (leaving_state == CHECKED_IN)
250         {
251           name = "checked-in";
252           value = svn_urlpath__canonicalize(value, scratch_pool);
253         }
254       else if (leaving_state == VERSION_NAME)
255         name = "revision";
256       else if (leaving_state == DATE)
257         name = "date";
258       else if (leaving_state == AUTHOR)
259         name = "author";
260       else if (leaving_state == POST_COMMIT_ERR)
261         name = "post-commit-err";
262       else
263         SVN_ERR_MALFUNCTION();
264
265       svn_ra_serf__xml_note(xes, RESPONSE, name, value);
266     }
267
268   return SVN_NO_ERROR;
269 }
270
271
272 static svn_error_t *
273 setup_merge_headers(serf_bucket_t *headers,
274                     void *baton,
275                     apr_pool_t *pool /* request pool */,
276                     apr_pool_t *scratch_pool)
277 {
278   merge_context_t *ctx = baton;
279   apr_array_header_t *vals = apr_array_make(scratch_pool, 2,
280                                             sizeof(const char *));
281
282   if (!ctx->keep_locks)
283     APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_RELEASE_LOCKS;
284   if (ctx->disable_merge_response)
285     APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_NO_MERGE_RESPONSE;
286
287   if (vals->nelts > 0)
288     serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
289                             svn_cstring_join2(vals, " ", FALSE, scratch_pool));
290
291   return SVN_NO_ERROR;
292 }
293
294 void
295 svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens,
296                                    const char *parent,
297                                    serf_bucket_t *body,
298                                    serf_bucket_alloc_t *alloc,
299                                    apr_pool_t *pool)
300 {
301   apr_hash_index_t *hi;
302
303   if (!lock_tokens || apr_hash_count(lock_tokens) == 0)
304     return;
305
306   svn_ra_serf__add_open_tag_buckets(body, alloc,
307                                     "S:lock-token-list",
308                                     "xmlns:S", SVN_XML_NAMESPACE,
309                                     SVN_VA_NULL);
310
311   for (hi = apr_hash_first(pool, lock_tokens);
312        hi;
313        hi = apr_hash_next(hi))
314     {
315       const void *key;
316       apr_ssize_t klen;
317       void *val;
318       svn_string_t path;
319
320       apr_hash_this(hi, &key, &klen, &val);
321
322       path.data = key;
323       path.len = klen;
324
325       if (parent && !svn_relpath_skip_ancestor(parent, key))
326         continue;
327
328       svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", SVN_VA_NULL);
329
330       svn_ra_serf__add_open_tag_buckets(body, alloc, "lock-path", SVN_VA_NULL);
331       svn_ra_serf__add_cdata_len_buckets(body, alloc, path.data, path.len);
332       svn_ra_serf__add_close_tag_buckets(body, alloc, "lock-path");
333
334       svn_ra_serf__add_tag_buckets(body, "lock-token", val, alloc);
335
336       svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock");
337     }
338
339   svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock-token-list");
340 }
341
342 /* Implements svn_ra_serf__request_body_delegate_t */
343 static svn_error_t*
344 create_merge_body(serf_bucket_t **bkt,
345                   void *baton,
346                   serf_bucket_alloc_t *alloc,
347                   apr_pool_t *pool /* request pool */,
348                   apr_pool_t *scratch_pool)
349 {
350   merge_context_t *ctx = baton;
351   serf_bucket_t *body_bkt;
352
353   body_bkt = serf_bucket_aggregate_create(alloc);
354
355   svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
356   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:merge",
357                                     "xmlns:D", "DAV:",
358                                     SVN_VA_NULL);
359   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:source", SVN_VA_NULL);
360   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", SVN_VA_NULL);
361
362   svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
363                                      ctx->merge_resource_url,
364                                      strlen(ctx->merge_resource_url));
365
366   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
367   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:source");
368
369   svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
370                                      "D:no-auto-merge", SVN_VA_NULL);
371   svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
372                                      "D:no-checkout", SVN_VA_NULL);
373
374   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", SVN_VA_NULL);
375   svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
376                                      "D:checked-in", SVN_VA_NULL);
377   svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
378                                      "D:" SVN_DAV__VERSION_NAME, SVN_VA_NULL);
379   svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
380                                      "D:resourcetype", SVN_VA_NULL);
381   svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
382                                      "D:" SVN_DAV__CREATIONDATE, SVN_VA_NULL);
383   svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
384                                      "D:creator-displayname", SVN_VA_NULL);
385   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
386
387   svn_ra_serf__merge_lock_token_list(ctx->lock_tokens, NULL, body_bkt,
388                                      alloc, pool);
389
390   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:merge");
391
392   *bkt = body_bkt;
393
394   return SVN_NO_ERROR;
395 }
396
397
398 svn_error_t *
399 svn_ra_serf__run_merge(const svn_commit_info_t **commit_info,
400                        svn_ra_serf__session_t *session,
401                        const char *merge_resource_url,
402                        apr_hash_t *lock_tokens,
403                        svn_boolean_t keep_locks,
404                        apr_pool_t *result_pool,
405                        apr_pool_t *scratch_pool)
406 {
407   merge_context_t *merge_ctx;
408   svn_ra_serf__handler_t *handler;
409   svn_ra_serf__xml_context_t *xmlctx;
410
411   merge_ctx = apr_pcalloc(scratch_pool, sizeof(*merge_ctx));
412
413   merge_ctx->pool = result_pool;
414   merge_ctx->session = session;
415
416   merge_ctx->merge_resource_url = merge_resource_url;
417
418   merge_ctx->lock_tokens = lock_tokens;
419   merge_ctx->keep_locks = keep_locks;
420
421   /* We don't need the full merge response when working over HTTPv2.
422    * Over HTTPv1, this response is only required with a non-null
423    * svn_ra_push_wc_prop_func_t callback. */
424   merge_ctx->disable_merge_response =
425     SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session) ||
426     session->wc_callbacks->push_wc_prop == NULL;
427
428   merge_ctx->commit_info = svn_create_commit_info(result_pool);
429
430   merge_ctx->merge_url = session->session_url.path;
431
432   xmlctx = svn_ra_serf__xml_context_create(merge_ttable,
433                                            NULL, merge_closed, NULL,
434                                            merge_ctx,
435                                            scratch_pool);
436   handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
437                                               scratch_pool);
438
439   handler->method = "MERGE";
440   handler->path = merge_ctx->merge_url;
441   handler->body_delegate = create_merge_body;
442   handler->body_delegate_baton = merge_ctx;
443   handler->body_type = "text/xml";
444
445   handler->header_delegate = setup_merge_headers;
446   handler->header_delegate_baton = merge_ctx;
447
448   merge_ctx->handler = handler;
449
450   SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
451
452   if (handler->sline.code != 200)
453     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
454
455   *commit_info = merge_ctx->commit_info;
456
457   /* Sanity check (Reported to be triggered by CodePlex's svnbridge) */
458   if (! SVN_IS_VALID_REVNUM(merge_ctx->commit_info->revision))
459     {
460       return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
461                               _("The MERGE response did not include "
462                                 "a new revision"));
463     }
464
465   merge_ctx->commit_info->repos_root = apr_pstrdup(result_pool,
466                                                    session->repos_root_str);
467
468   return SVN_NO_ERROR;
469 }