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 * ====================================================================
31 #include "svn_pools.h"
34 #include "svn_base64.h"
36 #include "svn_config.h"
38 #include "svn_props.h"
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"
46 #include "../libsvn_ra/ra_loader.h"
51 * This enum represents the current state of our XML parsing for a REPORT.
54 INITIAL = XML_STATE_INITIAL,
70 typedef struct log_context_t {
73 /* parameters set by our caller */
74 const apr_array_header_t *paths;
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 */
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;
91 /* log receiver function and baton */
92 svn_log_entry_receiver_t receiver;
95 /* pre-1.5 compatibility */
96 svn_boolean_t want_author;
97 svn_boolean_t want_date;
98 svn_boolean_t want_message;
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 },
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 },
112 { ITEM, D_, SVN_DAV__VERSION_NAME, VERSION,
113 TRUE, { NULL }, TRUE },
115 { ITEM, D_, "creator-displayname", CREATOR,
116 TRUE, { "?encoding", NULL }, TRUE },
118 { ITEM, S_, "date", DATE,
119 TRUE, { "?encoding", NULL }, TRUE },
121 { ITEM, D_, "comment", COMMENT,
122 TRUE, { "?encoding", NULL }, TRUE },
124 { ITEM, S_, "revprop", REVPROP,
125 TRUE, { "name", "?encoding", NULL }, TRUE },
127 { ITEM, S_, "has-children", HAS_CHILDREN,
128 FALSE, { NULL }, TRUE },
130 { ITEM, S_, "subtractive-merge", SUBTRACTIVE_MERGE,
131 FALSE, { NULL }, TRUE },
133 { ITEM, S_, "added-path", ADDED_PATH,
134 TRUE, { "?node-kind", "?text-mods", "?prop-mods",
135 "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
137 { ITEM, S_, "replaced-path", REPLACED_PATH,
138 TRUE, { "?node-kind", "?text-mods", "?prop-mods",
139 "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
141 { ITEM, S_, "deleted-path", DELETED_PATH,
142 TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
144 { ITEM, S_, "modified-path", MODIFIED_PATH,
145 TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
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.
155 NOTE: PROPNAME must live longer than REVPROPS. */
157 collect_revprop(apr_hash_t *revprops,
158 const char *propname,
159 const svn_string_t *cdata,
160 const char *encoding)
162 apr_pool_t *result_pool = apr_hash_pool_get(revprops);
163 const svn_string_t *decoded;
167 /* Check for a known encoding type. This is easy -- there's
169 if (strcmp(encoding, "base64") != 0)
171 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
172 _("Unsupported encoding '%s'"),
176 decoded = svn_base64_decode_string(cdata, result_pool);
180 decoded = svn_string_dup(cdata, result_pool);
183 /* Caller has ensured PROPNAME has sufficient lifetime. */
184 svn_hash_sets(revprops, propname, decoded);
190 /* Record ACTION on the path in CDATA into PATHS. Other properties about
191 the action are pulled from ATTRS. */
193 collect_path(apr_hash_t *paths,
195 const svn_string_t *cdata,
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;
204 lcp = svn_log_changed_path2_create(result_pool);
205 lcp->action = action;
206 lcp->copyfrom_rev = SVN_INVALID_REVNUM;
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)
215 SVN_ERR(svn_cstring_atoi64(&rev, copyfrom_rev));
217 if (SVN_IS_VALID_REVNUM((svn_revnum_t)rev))
219 lcp->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
220 lcp->copyfrom_rev = (svn_revnum_t)rev;
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,
227 lcp->props_modified = svn_tristate__from_word(svn_hash_gets(attrs,
230 path = apr_pstrmemdup(result_pool, cdata->data, cdata->len);
231 svn_hash_sets(paths, path, lcp);
237 /* Conforms to svn_ra_serf__xml_opened_t */
239 log_opened(svn_ra_serf__xml_estate_t *xes,
242 const svn_ra_serf__dav_props_t *tag,
243 apr_pool_t *scratch_pool)
245 log_context_t *log_ctx = baton;
247 if (entered_state == ITEM)
249 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
251 log_ctx->collect_revprops = apr_hash_make(state_pool);
252 log_ctx->collect_paths = apr_hash_make(state_pool);
259 /* Conforms to svn_ra_serf__xml_closed_t */
261 log_closed(svn_ra_serf__xml_estate_t *xes,
264 const svn_string_t *cdata,
266 apr_pool_t *scratch_pool)
268 log_context_t *log_ctx = baton;
270 if (leaving_state == ITEM)
272 svn_log_entry_t *log_entry;
275 if (log_ctx->limit && (log_ctx->nest_level == 0)
276 && (++log_ctx->count > log_ctx->limit))
281 log_entry = svn_log_entry_create(scratch_pool);
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)
288 log_entry->changed_paths = log_ctx->collect_paths;
289 log_entry->changed_paths2 = log_ctx->collect_paths;
292 /* ... and same story for the collected revprops. */
293 log_entry->revprops = log_ctx->collect_revprops;
295 log_entry->has_children = svn_hash__get_bool(attrs,
298 log_entry->subtractive_merge = svn_hash__get_bool(attrs,
302 rev_str = svn_hash_gets(attrs, "revision");
307 SVN_ERR(svn_cstring_atoi64(&rev, rev_str));
308 log_entry->revision = (svn_revnum_t)rev;
311 log_entry->revision = SVN_INVALID_REVNUM;
313 /* Give the info to the reporter */
314 SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
318 if (log_entry->has_children)
320 log_ctx->nest_level++;
322 if (! SVN_IS_VALID_REVNUM(log_entry->revision))
324 SVN_ERR_ASSERT(log_ctx->nest_level);
325 log_ctx->nest_level--;
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;
334 else if (leaving_state == VERSION)
336 svn_ra_serf__xml_note(xes, ITEM, "revision", cdata->data);
338 else if (leaving_state == CREATOR)
340 if (log_ctx->want_author)
342 SVN_ERR(collect_revprop(log_ctx->collect_revprops,
343 SVN_PROP_REVISION_AUTHOR,
345 svn_hash_gets(attrs, "encoding")));
348 else if (leaving_state == DATE)
350 if (log_ctx->want_date)
352 SVN_ERR(collect_revprop(log_ctx->collect_revprops,
353 SVN_PROP_REVISION_DATE,
355 svn_hash_gets(attrs, "encoding")));
358 else if (leaving_state == COMMENT)
360 if (log_ctx->want_message)
362 SVN_ERR(collect_revprop(log_ctx->collect_revprops,
363 SVN_PROP_REVISION_LOG,
365 svn_hash_gets(attrs, "encoding")));
368 else if (leaving_state == REVPROP)
370 apr_pool_t *result_pool = apr_hash_pool_get(log_ctx->collect_revprops);
372 SVN_ERR(collect_revprop(
373 log_ctx->collect_revprops,
374 apr_pstrdup(result_pool,
375 svn_hash_gets(attrs, "name")),
377 svn_hash_gets(attrs, "encoding")
380 else if (leaving_state == HAS_CHILDREN)
382 svn_ra_serf__xml_note(xes, ITEM, "has-children", "yes");
384 else if (leaving_state == SUBTRACTIVE_MERGE)
386 svn_ra_serf__xml_note(xes, ITEM, "subtractive-merge", "yes");
392 if (leaving_state == ADDED_PATH)
394 else if (leaving_state == REPLACED_PATH)
396 else if (leaving_state == DELETED_PATH)
400 SVN_ERR_ASSERT(leaving_state == MODIFIED_PATH);
404 SVN_ERR(collect_path(log_ctx->collect_paths, action, cdata, attrs));
410 /* Implements svn_ra_serf__request_body_delegate_t */
412 create_log_body(serf_bucket_t **body_bkt,
414 serf_bucket_alloc_t *alloc,
415 apr_pool_t *pool /* request pool */,
416 apr_pool_t *scratch_pool)
418 serf_bucket_t *buckets;
419 log_context_t *log_ctx = baton;
421 buckets = serf_bucket_aggregate_create(alloc);
423 svn_ra_serf__add_open_tag_buckets(buckets, alloc,
425 "xmlns:S", SVN_XML_NAMESPACE,
428 svn_ra_serf__add_tag_buckets(buckets,
430 apr_ltoa(pool, log_ctx->start),
432 svn_ra_serf__add_tag_buckets(buckets,
434 apr_ltoa(pool, log_ctx->end),
439 svn_ra_serf__add_tag_buckets(buckets,
440 "S:limit", apr_ltoa(pool, log_ctx->limit),
444 if (log_ctx->changed_paths)
446 svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
447 "S:discover-changed-paths",
451 if (log_ctx->strict_node_history)
453 svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
454 "S:strict-node-history", SVN_VA_NULL);
457 if (log_ctx->include_merged_revisions)
459 svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
460 "S:include-merged-revisions",
464 if (log_ctx->revprops)
467 for (i = 0; i < log_ctx->revprops->nelts; i++)
469 char *name = APR_ARRAY_IDX(log_ctx->revprops, i, char *);
470 svn_ra_serf__add_tag_buckets(buckets,
474 if (log_ctx->revprops->nelts == 0)
476 svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
477 "S:no-revprops", SVN_VA_NULL);
482 svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
483 "S:all-revprops", SVN_VA_NULL);
489 for (i = 0; i < log_ctx->paths->nelts; i++)
491 svn_ra_serf__add_tag_buckets(buckets,
492 "S:path", APR_ARRAY_IDX(log_ctx->paths, i,
498 svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
499 "S:encode-binary-props", SVN_VA_NULL);
501 svn_ra_serf__add_close_tag_buckets(buckets, alloc,
509 svn_ra_serf__get_log(svn_ra_session_t *ra_session,
510 const apr_array_header_t *paths,
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,
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;
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;
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;
544 want_custom_revprops = FALSE;
548 for (i = 0; i < revprops->nelts; i++)
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;
558 want_custom_revprops = TRUE;
563 log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
564 want_custom_revprops = TRUE;
567 if (want_custom_revprops)
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"
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.
580 peg_rev = (start == SVN_INVALID_REVNUM || start > end) ? start : end;
582 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
584 NULL /* url */, peg_rev,
587 xmlctx = svn_ra_serf__xml_context_create(log_ttable,
588 log_opened, log_closed, NULL,
591 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
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";
599 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
601 return svn_error_trace(
602 svn_ra_serf__error_on_status(handler->sline,