log.c revision 251881
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#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 48/* 49 * This enum represents the current state of our XML parsing for a REPORT. 50 */ 51enum { 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 68typedef 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 101static 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 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. */ 153static svn_error_t * 154collect_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. */ 189static svn_error_t * 190collect_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 */ 233static svn_error_t * 234log_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 */ 255static svn_error_t * 256log_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 401static svn_error_t * 402create_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 501svn_error_t * 502svn_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.code, 599 req_url, 600 handler->location), 601 err)); 602 603 return SVN_NO_ERROR; 604} 605