log.c revision 362181
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 */
53enum 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
70typedef 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
103static 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.  */
156static svn_error_t *
157collect_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.  */
192static svn_error_t *
193collect_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  */
238static svn_error_t *
239log_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  */
260static svn_error_t *
261log_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 > 0) && (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 */
411static svn_error_t *
412create_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
508svn_error_t *
509svn_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  if (handler->sline.code != 200)
602    SVN_ERR(svn_ra_serf__unexpected_status(handler));
603
604  return SVN_NO_ERROR;
605}
606