1/* 2 * log.c: return log messages 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#define APR_WANT_STRFUNC 25#include <apr_want.h> 26 27#include <apr_strings.h> 28#include <apr_pools.h> 29 30#include "svn_pools.h" 31#include "svn_client.h" 32#include "svn_compat.h" 33#include "svn_error.h" 34#include "svn_dirent_uri.h" 35#include "svn_hash.h" 36#include "svn_path.h" 37#include "svn_sorts.h" 38#include "svn_props.h" 39 40#include "client.h" 41 42#include "svn_private_config.h" 43#include "private/svn_wc_private.h" 44 45#include <assert.h> 46 47/*** Getting misc. information ***/ 48 49/* The baton for use with copyfrom_info_receiver(). */ 50typedef struct copyfrom_info_t 51{ 52 svn_boolean_t is_first; 53 const char *path; 54 svn_revnum_t rev; 55 apr_pool_t *pool; 56} copyfrom_info_t; 57 58/* A location segment callback for obtaining the copy source of 59 a node at a path and storing it in *BATON (a struct copyfrom_info_t *). 60 Implements svn_location_segment_receiver_t. */ 61static svn_error_t * 62copyfrom_info_receiver(svn_location_segment_t *segment, 63 void *baton, 64 apr_pool_t *pool) 65{ 66 copyfrom_info_t *copyfrom_info = baton; 67 68 /* If we've already identified the copy source, there's nothing more 69 to do. 70 ### FIXME: We *should* be able to send */ 71 if (copyfrom_info->path) 72 return SVN_NO_ERROR; 73 74 /* If this is the first segment, it's not of interest to us. Otherwise 75 (so long as this segment doesn't represent a history gap), it holds 76 our path's previous location (from which it was last copied). */ 77 if (copyfrom_info->is_first) 78 { 79 copyfrom_info->is_first = FALSE; 80 } 81 else if (segment->path) 82 { 83 /* The end of the second non-gap segment is the location copied from. */ 84 copyfrom_info->path = apr_pstrdup(copyfrom_info->pool, segment->path); 85 copyfrom_info->rev = segment->range_end; 86 87 /* ### FIXME: We *should* be able to return SVN_ERR_CEASE_INVOCATION 88 ### here so we don't get called anymore. */ 89 } 90 91 return SVN_NO_ERROR; 92} 93 94svn_error_t * 95svn_client__get_copy_source(const char **original_repos_relpath, 96 svn_revnum_t *original_revision, 97 const char *path_or_url, 98 const svn_opt_revision_t *revision, 99 svn_client_ctx_t *ctx, 100 apr_pool_t *result_pool, 101 apr_pool_t *scratch_pool) 102{ 103 svn_error_t *err; 104 copyfrom_info_t copyfrom_info = { 0 }; 105 apr_pool_t *sesspool = svn_pool_create(scratch_pool); 106 svn_ra_session_t *ra_session; 107 svn_client__pathrev_t *at_loc; 108 109 copyfrom_info.is_first = TRUE; 110 copyfrom_info.path = NULL; 111 copyfrom_info.rev = SVN_INVALID_REVNUM; 112 copyfrom_info.pool = result_pool; 113 114 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &at_loc, 115 path_or_url, NULL, 116 revision, revision, 117 ctx, sesspool)); 118 119 /* Find the copy source. Walk the location segments to find the revision 120 at which this node was created (copied or added). */ 121 122 err = svn_ra_get_location_segments(ra_session, "", at_loc->rev, at_loc->rev, 123 SVN_INVALID_REVNUM, 124 copyfrom_info_receiver, ©from_info, 125 scratch_pool); 126 127 svn_pool_destroy(sesspool); 128 129 if (err) 130 { 131 if (err->apr_err == SVN_ERR_FS_NOT_FOUND || 132 err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED) 133 { 134 /* A locally-added but uncommitted versioned resource won't 135 exist in the repository. */ 136 svn_error_clear(err); 137 err = SVN_NO_ERROR; 138 139 *original_repos_relpath = NULL; 140 *original_revision = SVN_INVALID_REVNUM; 141 } 142 return svn_error_trace(err); 143 } 144 145 *original_repos_relpath = copyfrom_info.path; 146 *original_revision = copyfrom_info.rev; 147 return SVN_NO_ERROR; 148} 149 150 151/* compatibility with pre-1.5 servers, which send only author/date/log 152 *revprops in log entries */ 153typedef struct pre_15_receiver_baton_t 154{ 155 svn_client_ctx_t *ctx; 156 /* ra session for retrieving revprops from old servers */ 157 svn_ra_session_t *ra_session; 158 /* caller's list of requested revprops, receiver, and baton */ 159 const char *ra_session_url; 160 apr_pool_t *ra_session_pool; 161 const apr_array_header_t *revprops; 162 svn_log_entry_receiver_t receiver; 163 void *baton; 164} pre_15_receiver_baton_t; 165 166static svn_error_t * 167pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) 168{ 169 pre_15_receiver_baton_t *rb = baton; 170 171 if (log_entry->revision == SVN_INVALID_REVNUM) 172 return rb->receiver(rb->baton, log_entry, pool); 173 174 /* If only some revprops are requested, get them one at a time on the 175 second ra connection. If all are requested, get them all with 176 svn_ra_rev_proplist. This avoids getting unrequested revprops (which 177 may be arbitrarily large), but means one round-trip per requested 178 revprop. epg isn't entirely sure which should be optimized for. */ 179 if (rb->revprops) 180 { 181 int i; 182 svn_boolean_t want_author, want_date, want_log; 183 want_author = want_date = want_log = FALSE; 184 for (i = 0; i < rb->revprops->nelts; i++) 185 { 186 const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *); 187 svn_string_t *value; 188 189 /* If a standard revprop is requested, we know it is already in 190 log_entry->revprops if available. */ 191 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0) 192 { 193 want_author = TRUE; 194 continue; 195 } 196 if (strcmp(name, SVN_PROP_REVISION_DATE) == 0) 197 { 198 want_date = TRUE; 199 continue; 200 } 201 if (strcmp(name, SVN_PROP_REVISION_LOG) == 0) 202 { 203 want_log = TRUE; 204 continue; 205 } 206 207 if (rb->ra_session == NULL) 208 SVN_ERR(svn_client_open_ra_session2(&rb->ra_session, 209 rb->ra_session_url, NULL, 210 rb->ctx, rb->ra_session_pool, 211 pool)); 212 213 SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision, 214 name, &value, pool)); 215 if (log_entry->revprops == NULL) 216 log_entry->revprops = apr_hash_make(pool); 217 svn_hash_sets(log_entry->revprops, name, value); 218 } 219 if (log_entry->revprops) 220 { 221 /* Pre-1.5 servers send the standard revprops unconditionally; 222 clear those the caller doesn't want. */ 223 if (!want_author) 224 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, NULL); 225 if (!want_date) 226 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, NULL); 227 if (!want_log) 228 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, NULL); 229 } 230 } 231 else 232 { 233 if (rb->ra_session == NULL) 234 SVN_ERR(svn_client_open_ra_session2(&rb->ra_session, 235 rb->ra_session_url, NULL, 236 rb->ctx, rb->ra_session_pool, 237 pool)); 238 239 SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision, 240 &log_entry->revprops, pool)); 241 } 242 243 return rb->receiver(rb->baton, log_entry, pool); 244} 245 246/* limit receiver */ 247typedef struct limit_receiver_baton_t 248{ 249 int limit; 250 svn_log_entry_receiver_t receiver; 251 void *baton; 252} limit_receiver_baton_t; 253 254static svn_error_t * 255limit_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) 256{ 257 limit_receiver_baton_t *rb = baton; 258 259 rb->limit--; 260 261 return rb->receiver(rb->baton, log_entry, pool); 262} 263 264/* Resolve the URLs or WC path in TARGETS as per the svn_client_log5 API. 265 266 The limitations on TARGETS specified by svn_client_log5 are enforced here. 267 So TARGETS can only contain a single WC path or a URL and zero or more 268 relative paths -- anything else will raise an error. 269 270 PEG_REVISION, TARGETS, and CTX are as per svn_client_log5. 271 272 If TARGETS contains a single WC path then set *RA_TARGET to the absolute 273 path of that single path if PEG_REVISION is dependent on the working copy 274 (e.g. PREV). Otherwise set *RA_TARGET to the corresponding URL for the 275 single WC path. Set *RELATIVE_TARGETS to an array with a single 276 element "". 277 278 If TARGETS contains only a single URL, then set *RA_TARGET to a copy of 279 that URL and *RELATIVE_TARGETS to an array with a single element "". 280 281 If TARGETS contains a single URL and one or more relative paths, then 282 set *RA_TARGET to a copy of that URL and *RELATIVE_TARGETS to a copy of 283 each relative path after the URL. 284 285 If *PEG_REVISION is svn_opt_revision_unspecified, then *PEG_REVISION is 286 set to svn_opt_revision_head for URLs or svn_opt_revision_working for a 287 WC path. 288 289 *RA_TARGET and *RELATIVE_TARGETS are allocated in RESULT_POOL. */ 290static svn_error_t * 291resolve_log_targets(apr_array_header_t **relative_targets, 292 const char **ra_target, 293 svn_opt_revision_t *peg_revision, 294 const apr_array_header_t *targets, 295 svn_client_ctx_t *ctx, 296 apr_pool_t *result_pool, 297 apr_pool_t *scratch_pool) 298{ 299 int i; 300 svn_boolean_t url_targets; 301 302 /* Per svn_client_log5, TARGETS contains either a URL followed by zero or 303 more relative paths, or one working copy path. */ 304 const char *url_or_path = APR_ARRAY_IDX(targets, 0, const char *); 305 306 /* svn_client_log5 requires at least one target. */ 307 if (targets->nelts == 0) 308 return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, 309 _("No valid target found")); 310 311 /* Initialize the output array. At a minimum, we need room for one 312 (possibly empty) relpath. Otherwise, we have to hold a relpath 313 for every item in TARGETS except the first. */ 314 *relative_targets = apr_array_make(result_pool, 315 MAX(1, targets->nelts - 1), 316 sizeof(const char *)); 317 318 if (svn_path_is_url(url_or_path)) 319 { 320 /* An unspecified PEG_REVISION for a URL path defaults 321 to svn_opt_revision_head. */ 322 if (peg_revision->kind == svn_opt_revision_unspecified) 323 peg_revision->kind = svn_opt_revision_head; 324 325 /* The logic here is this: If we get passed one argument, we assume 326 it is the full URL to a file/dir we want log info for. If we get 327 a URL plus some paths, then we assume that the URL is the base, 328 and that the paths passed are relative to it. */ 329 if (targets->nelts > 1) 330 { 331 /* We have some paths, let's use them. Start after the URL. */ 332 for (i = 1; i < targets->nelts; i++) 333 { 334 const char *target; 335 336 target = APR_ARRAY_IDX(targets, i, const char *); 337 338 if (svn_path_is_url(target) || svn_dirent_is_absolute(target)) 339 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 340 _("'%s' is not a relative path"), 341 target); 342 343 APR_ARRAY_PUSH(*relative_targets, const char *) = 344 apr_pstrdup(result_pool, target); 345 } 346 } 347 else 348 { 349 /* If we have a single URL, then the session will be rooted at 350 it, so just send an empty string for the paths we are 351 interested in. */ 352 APR_ARRAY_PUSH(*relative_targets, const char *) = ""; 353 } 354 355 /* Remember that our targets are URLs. */ 356 url_targets = TRUE; 357 } 358 else /* WC path target. */ 359 { 360 const char *target; 361 const char *target_abspath; 362 363 url_targets = FALSE; 364 if (targets->nelts > 1) 365 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 366 _("When specifying working copy paths, only " 367 "one target may be given")); 368 369 /* An unspecified PEG_REVISION for a working copy path defaults 370 to svn_opt_revision_working. */ 371 if (peg_revision->kind == svn_opt_revision_unspecified) 372 peg_revision->kind = svn_opt_revision_working; 373 374 /* Get URLs for each target */ 375 target = APR_ARRAY_IDX(targets, 0, const char *); 376 377 SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, scratch_pool)); 378 SVN_ERR(svn_wc__node_get_url(&url_or_path, ctx->wc_ctx, target_abspath, 379 scratch_pool, scratch_pool)); 380 APR_ARRAY_PUSH(*relative_targets, const char *) = ""; 381 } 382 383 /* If this is a revision type that requires access to the working copy, 384 * we use our initial target path to figure out where to root the RA 385 * session, otherwise we use our URL. */ 386 if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)) 387 { 388 if (url_targets) 389 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, 390 _("PREV, BASE, or COMMITTED revision " 391 "keywords are invalid for URL")); 392 393 else 394 SVN_ERR(svn_dirent_get_absolute( 395 ra_target, APR_ARRAY_IDX(targets, 0, const char *), result_pool)); 396 } 397 else 398 { 399 *ra_target = apr_pstrdup(result_pool, url_or_path); 400 } 401 402 return SVN_NO_ERROR; 403} 404 405/* Keep track of oldest and youngest opt revs found. 406 407 If REV is younger than *YOUNGEST_REV, or *YOUNGEST_REV is 408 svn_opt_revision_unspecified, then set *YOUNGEST_REV equal to REV. 409 410 If REV is older than *OLDEST_REV, or *OLDEST_REV is 411 svn_opt_revision_unspecified, then set *OLDEST_REV equal to REV. */ 412static void 413find_youngest_and_oldest_revs(svn_revnum_t *youngest_rev, 414 svn_revnum_t *oldest_rev, 415 svn_revnum_t rev) 416{ 417 /* Is REV younger than YOUNGEST_REV? */ 418 if (! SVN_IS_VALID_REVNUM(*youngest_rev) 419 || rev > *youngest_rev) 420 *youngest_rev = rev; 421 422 if (! SVN_IS_VALID_REVNUM(*oldest_rev) 423 || rev < *oldest_rev) 424 *oldest_rev = rev; 425} 426 427typedef struct rev_range_t 428{ 429 svn_revnum_t range_start; 430 svn_revnum_t range_end; 431} rev_range_t; 432 433/* Convert array of svn_opt_revision_t ranges to an array of svn_revnum_t 434 ranges. 435 436 Given a log target URL_OR_ABSPATH@PEG_REV and an array of 437 svn_opt_revision_range_t's OPT_REV_RANGES, resolve the opt revs in 438 OPT_REV_RANGES to svn_revnum_t's and return these in *REVISION_RANGES, an 439 array of rev_range_t *. 440 441 Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions 442 found in *REVISION_RANGES. 443 444 If the repository needs to be contacted to resolve svn_opt_revision_date or 445 svn_opt_revision_head revisions, then the session used to do this is 446 RA_SESSION; it must be an open session to any URL in the right repository. 447*/ 448static svn_error_t* 449convert_opt_rev_array_to_rev_range_array( 450 apr_array_header_t **revision_ranges, 451 svn_revnum_t *youngest_rev, 452 svn_revnum_t *oldest_rev, 453 svn_ra_session_t *ra_session, 454 const char *url_or_abspath, 455 const apr_array_header_t *opt_rev_ranges, 456 const svn_opt_revision_t *peg_rev, 457 svn_client_ctx_t *ctx, 458 apr_pool_t *result_pool, 459 apr_pool_t *scratch_pool) 460{ 461 int i; 462 svn_revnum_t head_rev = SVN_INVALID_REVNUM; 463 464 /* Initialize the input/output parameters. */ 465 *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM; 466 467 /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest 468 and oldest revision range that spans all of OPT_REV_RANGES. */ 469 *revision_ranges = apr_array_make(result_pool, opt_rev_ranges->nelts, 470 sizeof(rev_range_t *)); 471 472 for (i = 0; i < opt_rev_ranges->nelts; i++) 473 { 474 svn_opt_revision_range_t *range; 475 rev_range_t *rev_range; 476 svn_boolean_t start_same_as_end = FALSE; 477 478 range = APR_ARRAY_IDX(opt_rev_ranges, i, svn_opt_revision_range_t *); 479 480 /* Right now RANGE can be any valid pair of svn_opt_revision_t's. We 481 will now convert all RANGEs in place to the corresponding 482 svn_opt_revision_number kind. */ 483 if ((range->start.kind != svn_opt_revision_unspecified) 484 && (range->end.kind == svn_opt_revision_unspecified)) 485 { 486 /* If the user specified exactly one revision, then start rev is 487 * set but end is not. We show the log message for just that 488 * revision by making end equal to start. 489 * 490 * Note that if the user requested a single dated revision, then 491 * this will cause the same date to be resolved twice. The 492 * extra code complexity to get around this slight inefficiency 493 * doesn't seem worth it, however. */ 494 range->end = range->start; 495 } 496 else if (range->start.kind == svn_opt_revision_unspecified) 497 { 498 /* Default to any specified peg revision. Otherwise, if the 499 * first target is a URL, then we default to HEAD:0. Lastly, 500 * the default is BASE:0 since WC@HEAD may not exist. */ 501 if (peg_rev->kind == svn_opt_revision_unspecified) 502 { 503 if (svn_path_is_url(url_or_abspath)) 504 range->start.kind = svn_opt_revision_head; 505 else 506 range->start.kind = svn_opt_revision_base; 507 } 508 else 509 range->start = *peg_rev; 510 511 if (range->end.kind == svn_opt_revision_unspecified) 512 { 513 range->end.kind = svn_opt_revision_number; 514 range->end.value.number = 0; 515 } 516 } 517 518 if ((range->start.kind == svn_opt_revision_unspecified) 519 || (range->end.kind == svn_opt_revision_unspecified)) 520 { 521 return svn_error_create 522 (SVN_ERR_CLIENT_BAD_REVISION, NULL, 523 _("Missing required revision specification")); 524 } 525 526 /* Does RANGE describe a single svn_opt_revision_t? */ 527 if (range->start.kind == range->end.kind) 528 { 529 if (range->start.kind == svn_opt_revision_number) 530 { 531 if (range->start.value.number == range->end.value.number) 532 start_same_as_end = TRUE; 533 } 534 else if (range->start.kind == svn_opt_revision_date) 535 { 536 if (range->start.value.date == range->end.value.date) 537 start_same_as_end = TRUE; 538 } 539 else 540 { 541 start_same_as_end = TRUE; 542 } 543 } 544 545 rev_range = apr_palloc(result_pool, sizeof(*rev_range)); 546 SVN_ERR(svn_client__get_revision_number( 547 &rev_range->range_start, &head_rev, 548 ctx->wc_ctx, url_or_abspath, ra_session, 549 &range->start, scratch_pool)); 550 if (start_same_as_end) 551 rev_range->range_end = rev_range->range_start; 552 else 553 SVN_ERR(svn_client__get_revision_number( 554 &rev_range->range_end, &head_rev, 555 ctx->wc_ctx, url_or_abspath, ra_session, 556 &range->end, scratch_pool)); 557 558 /* Possibly update the oldest and youngest revisions requested. */ 559 find_youngest_and_oldest_revs(youngest_rev, 560 oldest_rev, 561 rev_range->range_start); 562 find_youngest_and_oldest_revs(youngest_rev, 563 oldest_rev, 564 rev_range->range_end); 565 APR_ARRAY_PUSH(*revision_ranges, rev_range_t *) = rev_range; 566 } 567 568 return SVN_NO_ERROR; 569} 570 571static int 572compare_rev_to_segment(const void *key_p, 573 const void *element_p) 574{ 575 svn_revnum_t rev = 576 * (svn_revnum_t *)key_p; 577 const svn_location_segment_t *segment = 578 *((const svn_location_segment_t * const *) element_p); 579 580 if (rev < segment->range_start) 581 return -1; 582 else if (rev > segment->range_end) 583 return 1; 584 else 585 return 0; 586} 587 588/* Run svn_ra_get_log2 for PATHS, one or more paths relative to RA_SESSION's 589 common parent, for each revision in REVISION_RANGES, an array of 590 rev_range_t. 591 592 RA_SESSION is an open session pointing to ACTUAL_LOC. 593 594 LOG_SEGMENTS is an array of svn_location_segment_t * items representing the 595 history of PATHS from the oldest to youngest revisions found in 596 REVISION_RANGES. 597 598 The TARGETS, LIMIT, DISCOVER_CHANGED_PATHS, STRICT_NODE_HISTORY, 599 INCLUDE_MERGED_REVISIONS, REVPROPS, REAL_RECEIVER, and REAL_RECEIVER_BATON 600 parameters are all as per the svn_client_log5 API. */ 601static svn_error_t * 602run_ra_get_log(apr_array_header_t *revision_ranges, 603 apr_array_header_t *paths, 604 apr_array_header_t *log_segments, 605 svn_client__pathrev_t *actual_loc, 606 svn_ra_session_t *ra_session, 607 /* The following are as per svn_client_log5. */ 608 const apr_array_header_t *targets, 609 int limit, 610 svn_boolean_t discover_changed_paths, 611 svn_boolean_t strict_node_history, 612 svn_boolean_t include_merged_revisions, 613 const apr_array_header_t *revprops, 614 svn_log_entry_receiver_t real_receiver, 615 void *real_receiver_baton, 616 svn_client_ctx_t *ctx, 617 apr_pool_t *scratch_pool) 618{ 619 int i; 620 pre_15_receiver_baton_t rb = {0}; 621 apr_pool_t *iterpool; 622 svn_boolean_t has_log_revprops; 623 624 SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops, 625 SVN_RA_CAPABILITY_LOG_REVPROPS, 626 scratch_pool)); 627 628 if (!has_log_revprops) 629 { 630 /* See above pre-1.5 notes. */ 631 rb.ctx = ctx; 632 633 /* Create ra session on first use */ 634 rb.ra_session_pool = scratch_pool; 635 rb.ra_session_url = actual_loc->url; 636 } 637 638 /* It's a bit complex to correctly handle the special revision words 639 * such as "BASE", "COMMITTED", and "PREV". For example, if the 640 * user runs 641 * 642 * $ svn log -rCOMMITTED foo.txt bar.c 643 * 644 * which committed rev should be used? The younger of the two? The 645 * first one? Should we just error? 646 * 647 * None of the above, I think. Rather, the committed rev of each 648 * target in turn should be used. This is what most users would 649 * expect, and is the most useful interpretation. Of course, this 650 * goes for the other dynamic (i.e., local) revision words too. 651 * 652 * Note that the code to do this is a bit more complex than a simple 653 * loop, because the user might run 654 * 655 * $ svn log -rCOMMITTED:42 foo.txt bar.c 656 * 657 * in which case we want to avoid recomputing the static revision on 658 * every iteration. 659 * 660 * ### FIXME: However, we can't yet handle multiple wc targets anyway. 661 * 662 * We used to iterate over each target in turn, getting the logs for 663 * the named range. This led to revisions being printed in strange 664 * order or being printed more than once. This is issue 1550. 665 * 666 * In r851673, jpieper blocked multiple wc targets in svn/log-cmd.c, 667 * meaning this block not only doesn't work right in that case, but isn't 668 * even testable that way (svn has no unit test suite; we can only test 669 * via the svn command). So, that check is now moved into this function 670 * (see above). 671 * 672 * kfogel ponders future enhancements in r844260: 673 * I think that's okay behavior, since the sense of the command is 674 * that one wants a particular range of logs for *this* file, then 675 * another range for *that* file, and so on. But we should 676 * probably put some sort of separator header between the log 677 * groups. Of course, libsvn_client can't just print stuff out -- 678 * it has to take a callback from the client to do that. So we 679 * need to define that callback interface, then have the command 680 * line client pass one down here. 681 * 682 * epg wonders if the repository could send a unified stream of log 683 * entries if the paths and revisions were passed down. 684 */ 685 iterpool = svn_pool_create(scratch_pool); 686 for (i = 0; i < revision_ranges->nelts; i++) 687 { 688 const char *old_session_url; 689 const char *path = APR_ARRAY_IDX(targets, 0, const char *); 690 const char *local_abspath_or_url; 691 rev_range_t *range; 692 limit_receiver_baton_t lb; 693 svn_log_entry_receiver_t passed_receiver; 694 void *passed_receiver_baton; 695 const apr_array_header_t *passed_receiver_revprops; 696 svn_location_segment_t **matching_segment; 697 svn_revnum_t younger_rev; 698 699 svn_pool_clear(iterpool); 700 701 if (!svn_path_is_url(path)) 702 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, 703 iterpool)); 704 else 705 local_abspath_or_url = path; 706 707 range = APR_ARRAY_IDX(revision_ranges, i, rev_range_t *); 708 709 /* Issue #4355: Account for renames spanning requested 710 revision ranges. */ 711 younger_rev = MAX(range->range_start, range->range_end); 712 matching_segment = bsearch(&younger_rev, log_segments->elts, 713 log_segments->nelts, log_segments->elt_size, 714 compare_rev_to_segment); 715 /* LOG_SEGMENTS is supposed to represent the history of PATHS from 716 the oldest to youngest revs in REVISION_RANGES. This function's 717 current sole caller svn_client_log5 *should* be providing 718 LOG_SEGMENTS that span the oldest to youngest revs in 719 REVISION_RANGES, even if one or more of the svn_location_segment_t's 720 returned have NULL path members indicating a gap in the history. So 721 MATCHING_SEGMENT should never be NULL, but clearly sometimes it is, 722 see http://svn.haxx.se/dev/archive-2013-06/0522.shtml 723 So to be safe we handle that case. */ 724 if (matching_segment == NULL) 725 continue; 726 727 /* A segment with a NULL path means there is gap in the history. 728 We'll just proceed and let svn_ra_get_log2 fail with a useful 729 error...*/ 730 if ((*matching_segment)->path != NULL) 731 { 732 /* ...but if there is history, then we must account for issue 733 #4355 and make sure our RA session is pointing at the correct 734 location. */ 735 const char *segment_url = svn_path_url_add_component2( 736 actual_loc->repos_root_url, (*matching_segment)->path, 737 scratch_pool); 738 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, 739 ra_session, 740 segment_url, 741 scratch_pool)); 742 } 743 744 if (has_log_revprops) 745 { 746 passed_receiver = real_receiver; 747 passed_receiver_baton = real_receiver_baton; 748 passed_receiver_revprops = revprops; 749 } 750 else 751 { 752 rb.revprops = revprops; 753 rb.receiver = real_receiver; 754 rb.baton = real_receiver_baton; 755 756 passed_receiver = pre_15_receiver; 757 passed_receiver_baton = &rb; 758 passed_receiver_revprops = svn_compat_log_revprops_in(iterpool); 759 } 760 761 if (limit && revision_ranges->nelts > 1) 762 { 763 lb.limit = limit; 764 lb.receiver = passed_receiver; 765 lb.baton = passed_receiver_baton; 766 767 passed_receiver = limit_receiver; 768 passed_receiver_baton = &lb; 769 } 770 771 SVN_ERR(svn_ra_get_log2(ra_session, 772 paths, 773 range->range_start, 774 range->range_end, 775 limit, 776 discover_changed_paths, 777 strict_node_history, 778 include_merged_revisions, 779 passed_receiver_revprops, 780 passed_receiver, 781 passed_receiver_baton, 782 iterpool)); 783 784 if (limit && revision_ranges->nelts > 1) 785 { 786 limit = lb.limit; 787 if (limit == 0) 788 { 789 return SVN_NO_ERROR; 790 } 791 } 792 } 793 svn_pool_destroy(iterpool); 794 795 return SVN_NO_ERROR; 796} 797 798/*** Public Interface. ***/ 799 800svn_error_t * 801svn_client_log5(const apr_array_header_t *targets, 802 const svn_opt_revision_t *peg_revision, 803 const apr_array_header_t *opt_rev_ranges, 804 int limit, 805 svn_boolean_t discover_changed_paths, 806 svn_boolean_t strict_node_history, 807 svn_boolean_t include_merged_revisions, 808 const apr_array_header_t *revprops, 809 svn_log_entry_receiver_t real_receiver, 810 void *real_receiver_baton, 811 svn_client_ctx_t *ctx, 812 apr_pool_t *pool) 813{ 814 svn_ra_session_t *ra_session; 815 const char *old_session_url; 816 const char *ra_target; 817 svn_opt_revision_t youngest_opt_rev; 818 svn_revnum_t youngest_rev; 819 svn_revnum_t oldest_rev; 820 svn_opt_revision_t peg_rev; 821 svn_client__pathrev_t *actual_loc; 822 apr_array_header_t *log_segments; 823 apr_array_header_t *revision_ranges; 824 apr_array_header_t *relative_targets; 825 826 if (opt_rev_ranges->nelts == 0) 827 { 828 return svn_error_create 829 (SVN_ERR_CLIENT_BAD_REVISION, NULL, 830 _("Missing required revision specification")); 831 } 832 833 /* Make a copy of PEG_REVISION, we may need to change it to a 834 default value. */ 835 peg_rev = *peg_revision; 836 837 SVN_ERR(resolve_log_targets(&relative_targets, &ra_target, &peg_rev, 838 targets, ctx, pool, pool)); 839 840 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &actual_loc, 841 ra_target, NULL, &peg_rev, &peg_rev, 842 ctx, pool)); 843 844 /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest 845 and oldest revision range that spans all of OPT_REV_RANGES. */ 846 SVN_ERR(convert_opt_rev_array_to_rev_range_array(&revision_ranges, 847 &youngest_rev, 848 &oldest_rev, 849 ra_session, 850 ra_target, 851 opt_rev_ranges, &peg_rev, 852 ctx, pool, pool)); 853 854 /* Make ACTUAL_LOC and RA_SESSION point to the youngest operative rev. */ 855 youngest_opt_rev.kind = svn_opt_revision_number; 856 youngest_opt_rev.value.number = youngest_rev; 857 SVN_ERR(svn_client__resolve_rev_and_url(&actual_loc, ra_session, 858 ra_target, &peg_rev, 859 &youngest_opt_rev, ctx, pool)); 860 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, 861 actual_loc->url, pool)); 862 863 /* Save us an RA layer round trip if we are on the repository root and 864 know the result in advance, or if we don't need multiple ranges. 865 All the revision data has already been validated. 866 */ 867 if (strcmp(actual_loc->url, actual_loc->repos_root_url) == 0 868 || opt_rev_ranges->nelts <= 1) 869 { 870 svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment)); 871 log_segments = apr_array_make(pool, 1, sizeof(segment)); 872 873 segment->range_start = oldest_rev; 874 segment->range_end = actual_loc->rev; 875 segment->path = svn_uri_skip_ancestor(actual_loc->repos_root_url, 876 actual_loc->url, pool); 877 APR_ARRAY_PUSH(log_segments, svn_location_segment_t *) = segment; 878 } 879 else 880 { 881 /* Get the svn_location_segment_t's representing the requested log 882 * ranges. */ 883 SVN_ERR(svn_client__repos_location_segments(&log_segments, ra_session, 884 actual_loc->url, 885 actual_loc->rev, /* peg */ 886 actual_loc->rev, /* start */ 887 oldest_rev, /* end */ 888 ctx, pool)); 889 } 890 891 892 SVN_ERR(run_ra_get_log(revision_ranges, relative_targets, log_segments, 893 actual_loc, ra_session, targets, limit, 894 discover_changed_paths, strict_node_history, 895 include_merged_revisions, revprops, real_receiver, 896 real_receiver_baton, ctx, pool)); 897 898 return SVN_NO_ERROR; 899} 900