]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/log.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_serf / log.c
1 /*
2  * log.c :  entry point for log RA 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
26
27 #include <apr_uri.h>
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_base64.h"
35 #include "svn_xml.h"
36 #include "svn_config.h"
37 #include "svn_path.h"
38 #include "svn_props.h"
39
40 #include "private/svn_dav_protocol.h"
41 #include "private/svn_string_private.h"
42 #include "private/svn_subr_private.h"
43 #include "svn_private_config.h"
44
45 #include "ra_serf.h"
46 #include "../libsvn_ra/ra_loader.h"
47
48
49
50 /*
51  * This enum represents the current state of our XML parsing for a REPORT.
52  */
53 enum log_state_e {
54   INITIAL = XML_STATE_INITIAL,
55   REPORT,
56   ITEM,
57   VERSION,
58   CREATOR,
59   DATE,
60   COMMENT,
61   REVPROP,
62   HAS_CHILDREN,
63   ADDED_PATH,
64   REPLACED_PATH,
65   DELETED_PATH,
66   MODIFIED_PATH,
67   SUBTRACTIVE_MERGE
68 };
69
70 typedef struct log_context_t {
71   apr_pool_t *pool;
72
73   /* parameters set by our caller */
74   const apr_array_header_t *paths;
75   svn_revnum_t start;
76   svn_revnum_t end;
77   int limit;
78   svn_boolean_t changed_paths;
79   svn_boolean_t strict_node_history;
80   svn_boolean_t include_merged_revisions;
81   const apr_array_header_t *revprops;
82   int nest_level; /* used to track mergeinfo nesting levels */
83   int count; /* only incremented when nest_level == 0 */
84
85   /* Collect information for storage into a log entry. Most of the entry
86      members are collected by individual states. revprops and paths are
87      N datapoints per entry.  */
88   apr_hash_t *collect_revprops;
89   apr_hash_t *collect_paths;
90
91   /* log receiver function and baton */
92   svn_log_entry_receiver_t receiver;
93   void *receiver_baton;
94
95   /* pre-1.5 compatibility */
96   svn_boolean_t want_author;
97   svn_boolean_t want_date;
98   svn_boolean_t want_message;
99 } log_context_t;
100
101 #define D_ "DAV:"
102 #define S_ SVN_XML_NAMESPACE
103 static const svn_ra_serf__xml_transition_t log_ttable[] = {
104   { INITIAL, S_, "log-report", REPORT,
105     FALSE, { NULL }, FALSE },
106
107   /* Note that we have an opener here. We need to construct a new LOG_ENTRY
108      to record multiple paths.  */
109   { REPORT, S_, "log-item", ITEM,
110     FALSE, { NULL }, TRUE },
111
112   { ITEM, D_, SVN_DAV__VERSION_NAME, VERSION,
113     TRUE, { NULL }, TRUE },
114
115   { ITEM, D_, "creator-displayname", CREATOR,
116     TRUE, { "?encoding", NULL }, TRUE },
117
118   { ITEM, S_, "date", DATE,
119     TRUE, { "?encoding", NULL }, TRUE },
120
121   { ITEM, D_, "comment", COMMENT,
122     TRUE, { "?encoding", NULL }, TRUE },
123
124   { ITEM, S_, "revprop", REVPROP,
125     TRUE, { "name", "?encoding", NULL }, TRUE },
126
127   { ITEM, S_, "has-children", HAS_CHILDREN,
128     FALSE, { NULL }, TRUE },
129
130   { ITEM, S_, "subtractive-merge", SUBTRACTIVE_MERGE,
131     FALSE, { NULL }, TRUE },
132
133   { ITEM, S_, "added-path", ADDED_PATH,
134     TRUE, { "?node-kind", "?text-mods", "?prop-mods",
135             "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
136
137   { ITEM, S_, "replaced-path", REPLACED_PATH,
138     TRUE, { "?node-kind", "?text-mods", "?prop-mods",
139             "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
140
141   { ITEM, S_, "deleted-path", DELETED_PATH,
142     TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
143
144   { ITEM, S_, "modified-path", MODIFIED_PATH,
145     TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
146
147   { 0 }
148 };
149
150
151
152 /* Store CDATA into REVPROPS, associated with PROPNAME. If ENCODING is not
153    NULL, then it must base "base64" and CDATA will be decoded first.
154
155    NOTE: PROPNAME must live longer than REVPROPS.  */
156 static svn_error_t *
157 collect_revprop(apr_hash_t *revprops,
158                 const char *propname,
159                 const svn_string_t *cdata,
160                 const char *encoding)
161 {
162   apr_pool_t *result_pool = apr_hash_pool_get(revprops);
163   const svn_string_t *decoded;
164
165   if (encoding)
166     {
167       /* Check for a known encoding type.  This is easy -- there's
168          only one.  */
169       if (strcmp(encoding, "base64") != 0)
170         {
171           return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
172                                    _("Unsupported encoding '%s'"),
173                                    encoding);
174         }
175
176       decoded = svn_base64_decode_string(cdata, result_pool);
177     }
178   else
179     {
180       decoded = svn_string_dup(cdata, result_pool);
181     }
182
183   /* Caller has ensured PROPNAME has sufficient lifetime.  */
184   svn_hash_sets(revprops, propname, decoded);
185
186   return SVN_NO_ERROR;
187 }
188
189
190 /* Record ACTION on the path in CDATA into PATHS. Other properties about
191    the action are pulled from ATTRS.  */
192 static svn_error_t *
193 collect_path(apr_hash_t *paths,
194              char action,
195              const svn_string_t *cdata,
196              apr_hash_t *attrs)
197 {
198   apr_pool_t *result_pool = apr_hash_pool_get(paths);
199   svn_log_changed_path2_t *lcp;
200   const char *copyfrom_path;
201   const char *copyfrom_rev;
202   const char *path;
203
204   lcp = svn_log_changed_path2_create(result_pool);
205   lcp->action = action;
206   lcp->copyfrom_rev = SVN_INVALID_REVNUM;
207
208   /* COPYFROM_* are only recorded for ADDED_PATH and REPLACED_PATH.  */
209   copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
210   copyfrom_rev = svn_hash_gets(attrs, "copyfrom-rev");
211   if (copyfrom_path && copyfrom_rev)
212     {
213       apr_int64_t rev;
214
215       SVN_ERR(svn_cstring_atoi64(&rev, copyfrom_rev));
216
217       if (SVN_IS_VALID_REVNUM((svn_revnum_t)rev))
218         {
219           lcp->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
220           lcp->copyfrom_rev = (svn_revnum_t)rev;
221         }
222     }
223
224   lcp->node_kind = svn_node_kind_from_word(svn_hash_gets(attrs, "node-kind"));
225   lcp->text_modified = svn_tristate__from_word(svn_hash_gets(attrs,
226                                                              "text-mods"));
227   lcp->props_modified = svn_tristate__from_word(svn_hash_gets(attrs,
228                                                               "prop-mods"));
229
230   path = apr_pstrmemdup(result_pool, cdata->data, cdata->len);
231   svn_hash_sets(paths, path, lcp);
232
233   return SVN_NO_ERROR;
234 }
235
236
237 /* Conforms to svn_ra_serf__xml_opened_t  */
238 static svn_error_t *
239 log_opened(svn_ra_serf__xml_estate_t *xes,
240            void *baton,
241            int entered_state,
242            const svn_ra_serf__dav_props_t *tag,
243            apr_pool_t *scratch_pool)
244 {
245   log_context_t *log_ctx = baton;
246
247   if (entered_state == ITEM)
248     {
249       apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
250
251       log_ctx->collect_revprops = apr_hash_make(state_pool);
252       log_ctx->collect_paths = apr_hash_make(state_pool);
253     }
254
255   return SVN_NO_ERROR;
256 }
257
258
259 /* Conforms to svn_ra_serf__xml_closed_t  */
260 static svn_error_t *
261 log_closed(svn_ra_serf__xml_estate_t *xes,
262            void *baton,
263            int leaving_state,
264            const svn_string_t *cdata,
265            apr_hash_t *attrs,
266            apr_pool_t *scratch_pool)
267 {
268   log_context_t *log_ctx = baton;
269
270   if (leaving_state == ITEM)
271     {
272       svn_log_entry_t *log_entry;
273       const char *rev_str;
274
275       if (log_ctx->limit && (log_ctx->nest_level == 0)
276           && (++log_ctx->count > log_ctx->limit))
277         {
278           return SVN_NO_ERROR;
279         }
280
281       log_entry = svn_log_entry_create(scratch_pool);
282
283       /* Pick up the paths from the context. These have the same lifetime
284          as this state. That is long enough for us to pass the paths to
285          the receiver callback.  */
286       if (apr_hash_count(log_ctx->collect_paths) > 0)
287         {
288           log_entry->changed_paths = log_ctx->collect_paths;
289           log_entry->changed_paths2 = log_ctx->collect_paths;
290         }
291
292       /* ... and same story for the collected revprops.  */
293       log_entry->revprops = log_ctx->collect_revprops;
294
295       log_entry->has_children = svn_hash__get_bool(attrs,
296                                                    "has-children",
297                                                    FALSE);
298       log_entry->subtractive_merge = svn_hash__get_bool(attrs,
299                                                         "subtractive-merge",
300                                                         FALSE);
301
302       rev_str = svn_hash_gets(attrs, "revision");
303       if (rev_str)
304         {
305           apr_int64_t rev;
306
307           SVN_ERR(svn_cstring_atoi64(&rev, rev_str));
308           log_entry->revision = (svn_revnum_t)rev;
309         }
310       else
311         log_entry->revision = SVN_INVALID_REVNUM;
312
313       /* Give the info to the reporter */
314       SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
315                                 log_entry,
316                                 scratch_pool));
317
318       if (log_entry->has_children)
319         {
320           log_ctx->nest_level++;
321         }
322       if (! SVN_IS_VALID_REVNUM(log_entry->revision))
323         {
324           SVN_ERR_ASSERT(log_ctx->nest_level);
325           log_ctx->nest_level--;
326         }
327
328       /* These hash tables are going to be unusable once this state's
329          pool is destroyed. But let's not leave stale pointers in
330          structures that have a longer life.  */
331       log_ctx->collect_revprops = NULL;
332       log_ctx->collect_paths = NULL;
333     }
334   else if (leaving_state == VERSION)
335     {
336       svn_ra_serf__xml_note(xes, ITEM, "revision", cdata->data);
337     }
338   else if (leaving_state == CREATOR)
339     {
340       if (log_ctx->want_author)
341         {
342           SVN_ERR(collect_revprop(log_ctx->collect_revprops,
343                                   SVN_PROP_REVISION_AUTHOR,
344                                   cdata,
345                                   svn_hash_gets(attrs, "encoding")));
346         }
347     }
348   else if (leaving_state == DATE)
349     {
350       if (log_ctx->want_date)
351         {
352           SVN_ERR(collect_revprop(log_ctx->collect_revprops,
353                                   SVN_PROP_REVISION_DATE,
354                                   cdata,
355                                   svn_hash_gets(attrs, "encoding")));
356         }
357     }
358   else if (leaving_state == COMMENT)
359     {
360       if (log_ctx->want_message)
361         {
362           SVN_ERR(collect_revprop(log_ctx->collect_revprops,
363                                   SVN_PROP_REVISION_LOG,
364                                   cdata,
365                                   svn_hash_gets(attrs, "encoding")));
366         }
367     }
368   else if (leaving_state == REVPROP)
369     {
370       apr_pool_t *result_pool = apr_hash_pool_get(log_ctx->collect_revprops);
371
372       SVN_ERR(collect_revprop(
373                 log_ctx->collect_revprops,
374                 apr_pstrdup(result_pool,
375                             svn_hash_gets(attrs, "name")),
376                 cdata,
377                 svn_hash_gets(attrs, "encoding")
378                 ));
379     }
380   else if (leaving_state == HAS_CHILDREN)
381     {
382       svn_ra_serf__xml_note(xes, ITEM, "has-children", "yes");
383     }
384   else if (leaving_state == SUBTRACTIVE_MERGE)
385     {
386       svn_ra_serf__xml_note(xes, ITEM, "subtractive-merge", "yes");
387     }
388   else
389     {
390       char action;
391
392       if (leaving_state == ADDED_PATH)
393         action = 'A';
394       else if (leaving_state == REPLACED_PATH)
395         action = 'R';
396       else if (leaving_state == DELETED_PATH)
397         action = 'D';
398       else
399         {
400           SVN_ERR_ASSERT(leaving_state == MODIFIED_PATH);
401           action = 'M';
402         }
403
404       SVN_ERR(collect_path(log_ctx->collect_paths, action, cdata, attrs));
405     }
406
407   return SVN_NO_ERROR;
408 }
409
410 /* Implements svn_ra_serf__request_body_delegate_t */
411 static svn_error_t *
412 create_log_body(serf_bucket_t **body_bkt,
413                 void *baton,
414                 serf_bucket_alloc_t *alloc,
415                 apr_pool_t *pool /* request pool */,
416                 apr_pool_t *scratch_pool)
417 {
418   serf_bucket_t *buckets;
419   log_context_t *log_ctx = baton;
420
421   buckets = serf_bucket_aggregate_create(alloc);
422
423   svn_ra_serf__add_open_tag_buckets(buckets, alloc,
424                                     "S:log-report",
425                                     "xmlns:S", SVN_XML_NAMESPACE,
426                                     SVN_VA_NULL);
427
428   svn_ra_serf__add_tag_buckets(buckets,
429                                "S:start-revision",
430                                apr_ltoa(pool, log_ctx->start),
431                                alloc);
432   svn_ra_serf__add_tag_buckets(buckets,
433                                "S:end-revision",
434                                apr_ltoa(pool, log_ctx->end),
435                                alloc);
436
437   if (log_ctx->limit)
438     {
439       svn_ra_serf__add_tag_buckets(buckets,
440                                    "S:limit", apr_ltoa(pool, log_ctx->limit),
441                                    alloc);
442     }
443
444   if (log_ctx->changed_paths)
445     {
446       svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
447                                          "S:discover-changed-paths",
448                                          SVN_VA_NULL);
449     }
450
451   if (log_ctx->strict_node_history)
452     {
453       svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
454                                          "S:strict-node-history", SVN_VA_NULL);
455     }
456
457   if (log_ctx->include_merged_revisions)
458     {
459       svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
460                                          "S:include-merged-revisions",
461                                          SVN_VA_NULL);
462     }
463
464   if (log_ctx->revprops)
465     {
466       int i;
467       for (i = 0; i < log_ctx->revprops->nelts; i++)
468         {
469           char *name = APR_ARRAY_IDX(log_ctx->revprops, i, char *);
470           svn_ra_serf__add_tag_buckets(buckets,
471                                        "S:revprop", name,
472                                        alloc);
473         }
474       if (log_ctx->revprops->nelts == 0)
475         {
476           svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
477                                              "S:no-revprops", SVN_VA_NULL);
478         }
479     }
480   else
481     {
482       svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
483                                          "S:all-revprops", SVN_VA_NULL);
484     }
485
486   if (log_ctx->paths)
487     {
488       int i;
489       for (i = 0; i < log_ctx->paths->nelts; i++)
490         {
491           svn_ra_serf__add_tag_buckets(buckets,
492                                        "S:path", APR_ARRAY_IDX(log_ctx->paths, i,
493                                                                const char*),
494                                        alloc);
495         }
496     }
497
498   svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
499                                      "S:encode-binary-props", SVN_VA_NULL);
500
501   svn_ra_serf__add_close_tag_buckets(buckets, alloc,
502                                      "S:log-report");
503
504   *body_bkt = buckets;
505   return SVN_NO_ERROR;
506 }
507
508 svn_error_t *
509 svn_ra_serf__get_log(svn_ra_session_t *ra_session,
510                      const apr_array_header_t *paths,
511                      svn_revnum_t start,
512                      svn_revnum_t end,
513                      int limit,
514                      svn_boolean_t discover_changed_paths,
515                      svn_boolean_t strict_node_history,
516                      svn_boolean_t include_merged_revisions,
517                      const apr_array_header_t *revprops,
518                      svn_log_entry_receiver_t receiver,
519                      void *receiver_baton,
520                      apr_pool_t *pool)
521 {
522   log_context_t *log_ctx;
523   svn_ra_serf__session_t *session = ra_session->priv;
524   svn_ra_serf__handler_t *handler;
525   svn_ra_serf__xml_context_t *xmlctx;
526   svn_boolean_t want_custom_revprops;
527   svn_revnum_t peg_rev;
528   const char *req_url;
529
530   log_ctx = apr_pcalloc(pool, sizeof(*log_ctx));
531   log_ctx->pool = pool;
532   log_ctx->receiver = receiver;
533   log_ctx->receiver_baton = receiver_baton;
534   log_ctx->paths = paths;
535   log_ctx->start = start;
536   log_ctx->end = end;
537   log_ctx->limit = limit;
538   log_ctx->changed_paths = discover_changed_paths;
539   log_ctx->strict_node_history = strict_node_history;
540   log_ctx->include_merged_revisions = include_merged_revisions;
541   log_ctx->revprops = revprops;
542   log_ctx->nest_level = 0;
543
544   want_custom_revprops = FALSE;
545   if (revprops)
546     {
547       int i;
548       for (i = 0; i < revprops->nelts; i++)
549         {
550           char *name = APR_ARRAY_IDX(revprops, i, char *);
551           if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
552             log_ctx->want_author = TRUE;
553           else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
554             log_ctx->want_date = TRUE;
555           else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
556             log_ctx->want_message = TRUE;
557           else
558             want_custom_revprops = TRUE;
559         }
560     }
561   else
562     {
563       log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
564       want_custom_revprops = TRUE;
565     }
566
567   if (want_custom_revprops)
568     {
569       svn_boolean_t has_log_revprops;
570       SVN_ERR(svn_ra_serf__has_capability(ra_session, &has_log_revprops,
571                                           SVN_RA_CAPABILITY_LOG_REVPROPS, pool));
572       if (!has_log_revprops)
573         return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
574                                 _("Server does not support custom revprops"
575                                   " via log"));
576     }
577   /* At this point, we may have a deleted file.  So, we'll match ra_neon's
578    * behavior and use the larger of start or end as our 'peg' rev.
579    */
580   peg_rev = (start == SVN_INVALID_REVNUM || start > end) ? start : end;
581
582   SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
583                                       session,
584                                       NULL /* url */, peg_rev,
585                                       pool, pool));
586
587   xmlctx = svn_ra_serf__xml_context_create(log_ttable,
588                                            log_opened, log_closed, NULL,
589                                            log_ctx,
590                                            pool);
591   handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
592
593   handler->method = "REPORT";
594   handler->path = req_url;
595   handler->body_delegate = create_log_body;
596   handler->body_delegate_baton = log_ctx;
597   handler->body_type = "text/xml";
598
599   SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
600
601   return svn_error_trace(
602               svn_ra_serf__error_on_status(handler->sline,
603                                            req_url,
604                                            handler->location));
605 }