2 * log.c : entry point for log RA functions for ra_serf
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
30 #include "svn_pools.h"
33 #include "svn_base64.h"
35 #include "svn_config.h"
37 #include "svn_props.h"
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"
45 #include "../libsvn_ra/ra_loader.h"
49 * This enum represents the current state of our XML parsing for a REPORT.
68 typedef struct log_context_t {
71 /* parameters set by our caller */
72 const apr_array_header_t *paths;
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 */
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;
89 /* log receiver function and baton */
90 svn_log_entry_receiver_t receiver;
93 /* pre-1.5 compatibility */
94 svn_boolean_t want_author;
95 svn_boolean_t want_date;
96 svn_boolean_t want_message;
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 },
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 },
110 { ITEM, D_, SVN_DAV__VERSION_NAME, VERSION,
111 TRUE, { NULL }, TRUE },
113 { ITEM, D_, "creator-displayname", CREATOR,
114 TRUE, { "?encoding", NULL }, TRUE },
116 { ITEM, S_, "date", DATE,
117 TRUE, { "?encoding", NULL }, TRUE },
119 { ITEM, D_, "comment", COMMENT,
120 TRUE, { "?encoding", NULL }, TRUE },
122 { ITEM, S_, "revprop", REVPROP,
123 TRUE, { "name", "?encoding", NULL }, TRUE },
125 { ITEM, S_, "has-children", HAS_CHILDREN,
126 FALSE, { NULL }, TRUE },
128 { ITEM, S_, "subtractive-merge", SUBTRACTIVE_MERGE,
129 FALSE, { NULL }, TRUE },
131 { ITEM, S_, "added-path", ADDED_PATH,
132 TRUE, { "?node-kind", "?text-mods", "?prop-mods",
133 "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
135 { ITEM, S_, "replaced-path", REPLACED_PATH,
136 TRUE, { "?node-kind", "?text-mods", "?prop-mods",
137 "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
139 { ITEM, S_, "deleted-path", DELETED_PATH,
140 TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
142 { ITEM, S_, "modified-path", MODIFIED_PATH,
143 TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
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.
152 NOTE: PROPNAME must live longer than REVPROPS. */
154 collect_revprop(apr_hash_t *revprops,
155 const char *propname,
156 const svn_string_t *cdata,
157 const char *encoding)
159 apr_pool_t *result_pool = apr_hash_pool_get(revprops);
160 const svn_string_t *decoded;
164 /* Check for a known encoding type. This is easy -- there's
166 if (strcmp(encoding, "base64") != 0)
168 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
169 _("Unsupported encoding '%s'"),
173 decoded = svn_base64_decode_string(cdata, result_pool);
177 decoded = svn_string_dup(cdata, result_pool);
180 /* Caller has ensured PROPNAME has sufficient lifetime. */
181 svn_hash_sets(revprops, propname, decoded);
187 /* Record ACTION on the path in CDATA into PATHS. Other properties about
188 the action are pulled from ATTRS. */
190 collect_path(apr_hash_t *paths,
192 const svn_string_t *cdata,
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;
201 lcp = svn_log_changed_path2_create(result_pool);
202 lcp->action = action;
203 lcp->copyfrom_rev = SVN_INVALID_REVNUM;
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)
210 svn_revnum_t rev = SVN_STR_TO_REV(copyfrom_rev);
212 if (SVN_IS_VALID_REVNUM(rev))
214 lcp->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
215 lcp->copyfrom_rev = rev;
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,
222 lcp->props_modified = svn_tristate__from_word(svn_hash_gets(attrs,
225 path = apr_pstrmemdup(result_pool, cdata->data, cdata->len);
226 svn_hash_sets(paths, path, lcp);
232 /* Conforms to svn_ra_serf__xml_opened_t */
234 log_opened(svn_ra_serf__xml_estate_t *xes,
237 const svn_ra_serf__dav_props_t *tag,
238 apr_pool_t *scratch_pool)
240 log_context_t *log_ctx = baton;
242 if (entered_state == ITEM)
244 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
246 log_ctx->collect_revprops = apr_hash_make(state_pool);
247 log_ctx->collect_paths = apr_hash_make(state_pool);
254 /* Conforms to svn_ra_serf__xml_closed_t */
256 log_closed(svn_ra_serf__xml_estate_t *xes,
259 const svn_string_t *cdata,
261 apr_pool_t *scratch_pool)
263 log_context_t *log_ctx = baton;
265 if (leaving_state == ITEM)
267 svn_log_entry_t *log_entry;
270 if (log_ctx->limit && (log_ctx->nest_level == 0)
271 && (++log_ctx->count > log_ctx->limit))
276 log_entry = svn_log_entry_create(scratch_pool);
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)
283 log_entry->changed_paths = log_ctx->collect_paths;
284 log_entry->changed_paths2 = log_ctx->collect_paths;
287 /* ... and same story for the collected revprops. */
288 log_entry->revprops = log_ctx->collect_revprops;
290 log_entry->has_children = svn_hash__get_bool(attrs,
293 log_entry->subtractive_merge = svn_hash__get_bool(attrs,
297 rev_str = svn_hash_gets(attrs, "revision");
299 log_entry->revision = SVN_STR_TO_REV(rev_str);
301 log_entry->revision = SVN_INVALID_REVNUM;
303 /* Give the info to the reporter */
304 SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
308 if (log_entry->has_children)
310 log_ctx->nest_level++;
312 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
314 SVN_ERR_ASSERT(log_ctx->nest_level);
315 log_ctx->nest_level--;
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;
324 else if (leaving_state == VERSION)
326 svn_ra_serf__xml_note(xes, ITEM, "revision", cdata->data);
328 else if (leaving_state == CREATOR)
330 if (log_ctx->want_author)
332 SVN_ERR(collect_revprop(log_ctx->collect_revprops,
333 SVN_PROP_REVISION_AUTHOR,
335 svn_hash_gets(attrs, "encoding")));
338 else if (leaving_state == DATE)
340 if (log_ctx->want_date)
342 SVN_ERR(collect_revprop(log_ctx->collect_revprops,
343 SVN_PROP_REVISION_DATE,
345 svn_hash_gets(attrs, "encoding")));
348 else if (leaving_state == COMMENT)
350 if (log_ctx->want_message)
352 SVN_ERR(collect_revprop(log_ctx->collect_revprops,
353 SVN_PROP_REVISION_LOG,
355 svn_hash_gets(attrs, "encoding")));
358 else if (leaving_state == REVPROP)
360 apr_pool_t *result_pool = apr_hash_pool_get(log_ctx->collect_revprops);
362 SVN_ERR(collect_revprop(
363 log_ctx->collect_revprops,
364 apr_pstrdup(result_pool,
365 svn_hash_gets(attrs, "name")),
367 svn_hash_gets(attrs, "encoding")
370 else if (leaving_state == HAS_CHILDREN)
372 svn_ra_serf__xml_note(xes, ITEM, "has-children", "yes");
374 else if (leaving_state == SUBTRACTIVE_MERGE)
376 svn_ra_serf__xml_note(xes, ITEM, "subtractive-merge", "yes");
382 if (leaving_state == ADDED_PATH)
384 else if (leaving_state == REPLACED_PATH)
386 else if (leaving_state == DELETED_PATH)
390 SVN_ERR_ASSERT(leaving_state == MODIFIED_PATH);
394 SVN_ERR(collect_path(log_ctx->collect_paths, action, cdata, attrs));
402 create_log_body(serf_bucket_t **body_bkt,
404 serf_bucket_alloc_t *alloc,
407 serf_bucket_t *buckets;
408 log_context_t *log_ctx = baton;
410 buckets = serf_bucket_aggregate_create(alloc);
412 svn_ra_serf__add_open_tag_buckets(buckets, alloc,
414 "xmlns:S", SVN_XML_NAMESPACE,
417 svn_ra_serf__add_tag_buckets(buckets,
419 apr_ltoa(pool, log_ctx->start),
421 svn_ra_serf__add_tag_buckets(buckets,
423 apr_ltoa(pool, log_ctx->end),
428 svn_ra_serf__add_tag_buckets(buckets,
429 "S:limit", apr_ltoa(pool, log_ctx->limit),
433 if (log_ctx->changed_paths)
435 svn_ra_serf__add_tag_buckets(buckets,
436 "S:discover-changed-paths", NULL,
440 if (log_ctx->strict_node_history)
442 svn_ra_serf__add_tag_buckets(buckets,
443 "S:strict-node-history", NULL,
447 if (log_ctx->include_merged_revisions)
449 svn_ra_serf__add_tag_buckets(buckets,
450 "S:include-merged-revisions", NULL,
454 if (log_ctx->revprops)
457 for (i = 0; i < log_ctx->revprops->nelts; i++)
459 char *name = APR_ARRAY_IDX(log_ctx->revprops, i, char *);
460 svn_ra_serf__add_tag_buckets(buckets,
464 if (log_ctx->revprops->nelts == 0)
466 svn_ra_serf__add_tag_buckets(buckets,
467 "S:no-revprops", NULL,
473 svn_ra_serf__add_tag_buckets(buckets,
474 "S:all-revprops", NULL,
481 for (i = 0; i < log_ctx->paths->nelts; i++)
483 svn_ra_serf__add_tag_buckets(buckets,
484 "S:path", APR_ARRAY_IDX(log_ctx->paths, i,
490 svn_ra_serf__add_tag_buckets(buckets,
491 "S:encode-binary-props", NULL,
494 svn_ra_serf__add_close_tag_buckets(buckets, alloc,
502 svn_ra_serf__get_log(svn_ra_session_t *ra_session,
503 const apr_array_header_t *paths,
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,
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;
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;
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;
538 want_custom_revprops = FALSE;
542 for (i = 0; i < revprops->nelts; i++)
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;
552 want_custom_revprops = TRUE;
557 log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
558 want_custom_revprops = TRUE;
561 if (want_custom_revprops)
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"
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.
574 peg_rev = (start > end) ? start : end;
576 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
577 session, NULL /* conn */,
578 NULL /* url */, peg_rev,
581 xmlctx = svn_ra_serf__xml_context_create(log_ttable,
582 log_opened, log_closed, NULL,
585 handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
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;
595 err = svn_ra_serf__context_run_one(handler, pool);
597 SVN_ERR(svn_error_compose_create(
598 svn_ra_serf__error_on_status(handler->sline,