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