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