1/* 2 * blame.c: return blame 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#include <apr_pools.h> 25 26#include "client.h" 27 28#include "svn_client.h" 29#include "svn_subst.h" 30#include "svn_string.h" 31#include "svn_error.h" 32#include "svn_diff.h" 33#include "svn_pools.h" 34#include "svn_dirent_uri.h" 35#include "svn_path.h" 36#include "svn_props.h" 37#include "svn_hash.h" 38#include "svn_sorts.h" 39 40#include "private/svn_wc_private.h" 41 42#include "svn_private_config.h" 43 44#include <assert.h> 45 46/* The metadata associated with a particular revision. */ 47struct rev 48{ 49 svn_revnum_t revision; /* the revision number */ 50 apr_hash_t *rev_props; /* the revision properties */ 51 /* Used for merge reporting. */ 52 const char *path; /* the absolute repository path */ 53}; 54 55/* One chunk of blame */ 56struct blame 57{ 58 const struct rev *rev; /* the responsible revision */ 59 apr_off_t start; /* the starting diff-token (line) */ 60 struct blame *next; /* the next chunk */ 61}; 62 63/* A chain of blame chunks */ 64struct blame_chain 65{ 66 struct blame *blame; /* linked list of blame chunks */ 67 struct blame *avail; /* linked list of free blame chunks */ 68 struct apr_pool_t *pool; /* Allocate members from this pool. */ 69}; 70 71/* The baton use for the diff output routine. */ 72struct diff_baton { 73 struct blame_chain *chain; 74 const struct rev *rev; 75}; 76 77/* The baton used for a file revision. Lives the entire operation */ 78struct file_rev_baton { 79 svn_revnum_t start_rev, end_rev; 80 svn_boolean_t backwards; 81 const char *target; 82 svn_client_ctx_t *ctx; 83 const svn_diff_file_options_t *diff_options; 84 /* name of file containing the previous revision of the file */ 85 const char *last_filename; 86 struct rev *last_rev; /* the rev of the last modification */ 87 struct blame_chain *chain; /* the original blame chain. */ 88 const char *repos_root_url; /* To construct a url */ 89 apr_pool_t *mainpool; /* lives during the whole sequence of calls */ 90 apr_pool_t *lastpool; /* pool used during previous call */ 91 apr_pool_t *currpool; /* pool used during this call */ 92 93 /* These are used for tracking merged revisions. */ 94 svn_boolean_t include_merged_revisions; 95 struct blame_chain *merged_chain; /* the merged blame chain. */ 96 /* name of file containing the previous merged revision of the file */ 97 const char *last_original_filename; 98 /* pools for files which may need to persist for more than one rev. */ 99 apr_pool_t *filepool; 100 apr_pool_t *prevfilepool; 101 102 svn_boolean_t check_mime_type; 103 104 /* When blaming backwards we have to use the changes 105 on the *next* revision, as the interesting change 106 happens when we move to the previous revision */ 107 svn_revnum_t last_revnum; 108 apr_hash_t *last_props; 109}; 110 111/* The baton used by the txdelta window handler. Allocated per revision */ 112struct delta_baton { 113 /* Our underlying handler/baton that we wrap */ 114 svn_txdelta_window_handler_t wrapped_handler; 115 void *wrapped_baton; 116 struct file_rev_baton *file_rev_baton; 117 svn_stream_t *source_stream; /* the delta source */ 118 const char *filename; 119 svn_boolean_t is_merged_revision; 120 struct rev *rev; /* the rev struct for the current revision */ 121}; 122 123 124 125 126/* Return a blame chunk associated with REV for a change starting 127 at token START, and allocated in CHAIN->mainpool. */ 128static struct blame * 129blame_create(struct blame_chain *chain, 130 const struct rev *rev, 131 apr_off_t start) 132{ 133 struct blame *blame; 134 if (chain->avail) 135 { 136 blame = chain->avail; 137 chain->avail = blame->next; 138 } 139 else 140 blame = apr_palloc(chain->pool, sizeof(*blame)); 141 blame->rev = rev; 142 blame->start = start; 143 blame->next = NULL; 144 return blame; 145} 146 147/* Destroy a blame chunk. */ 148static void 149blame_destroy(struct blame_chain *chain, 150 struct blame *blame) 151{ 152 blame->next = chain->avail; 153 chain->avail = blame; 154} 155 156/* Return the blame chunk that contains token OFF, starting the search at 157 BLAME. */ 158static struct blame * 159blame_find(struct blame *blame, apr_off_t off) 160{ 161 struct blame *prev = NULL; 162 while (blame) 163 { 164 if (blame->start > off) break; 165 prev = blame; 166 blame = blame->next; 167 } 168 return prev; 169} 170 171/* Shift the start-point of BLAME and all subsequence blame-chunks 172 by ADJUST tokens */ 173static void 174blame_adjust(struct blame *blame, apr_off_t adjust) 175{ 176 while (blame) 177 { 178 blame->start += adjust; 179 blame = blame->next; 180 } 181} 182 183/* Delete the blame associated with the region from token START to 184 START + LENGTH */ 185static svn_error_t * 186blame_delete_range(struct blame_chain *chain, 187 apr_off_t start, 188 apr_off_t length) 189{ 190 struct blame *first = blame_find(chain->blame, start); 191 struct blame *last = blame_find(chain->blame, start + length); 192 struct blame *tail = last->next; 193 194 if (first != last) 195 { 196 struct blame *walk = first->next; 197 while (walk != last) 198 { 199 struct blame *next = walk->next; 200 blame_destroy(chain, walk); 201 walk = next; 202 } 203 first->next = last; 204 last->start = start; 205 if (first->start == start) 206 { 207 *first = *last; 208 blame_destroy(chain, last); 209 last = first; 210 } 211 } 212 213 if (tail && tail->start == last->start + length) 214 { 215 *last = *tail; 216 blame_destroy(chain, tail); 217 tail = last->next; 218 } 219 220 blame_adjust(tail, -length); 221 222 return SVN_NO_ERROR; 223} 224 225/* Insert a chunk of blame associated with REV starting 226 at token START and continuing for LENGTH tokens */ 227static svn_error_t * 228blame_insert_range(struct blame_chain *chain, 229 const struct rev *rev, 230 apr_off_t start, 231 apr_off_t length) 232{ 233 struct blame *head = chain->blame; 234 struct blame *point = blame_find(head, start); 235 struct blame *insert; 236 237 if (point->start == start) 238 { 239 insert = blame_create(chain, point->rev, point->start + length); 240 point->rev = rev; 241 insert->next = point->next; 242 point->next = insert; 243 } 244 else 245 { 246 struct blame *middle; 247 middle = blame_create(chain, rev, start); 248 insert = blame_create(chain, point->rev, start + length); 249 middle->next = insert; 250 insert->next = point->next; 251 point->next = middle; 252 } 253 blame_adjust(insert->next, length); 254 255 return SVN_NO_ERROR; 256} 257 258/* Callback for diff between subsequent revisions */ 259static svn_error_t * 260output_diff_modified(void *baton, 261 apr_off_t original_start, 262 apr_off_t original_length, 263 apr_off_t modified_start, 264 apr_off_t modified_length, 265 apr_off_t latest_start, 266 apr_off_t latest_length) 267{ 268 struct diff_baton *db = baton; 269 270 if (original_length) 271 SVN_ERR(blame_delete_range(db->chain, modified_start, original_length)); 272 273 if (modified_length) 274 SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start, 275 modified_length)); 276 277 return SVN_NO_ERROR; 278} 279 280static const svn_diff_output_fns_t output_fns = { 281 NULL, 282 output_diff_modified 283}; 284 285/* Add the blame for the diffs between LAST_FILE and CUR_FILE to CHAIN, 286 for revision REV. LAST_FILE may be NULL in which 287 case blame is added for every line of CUR_FILE. */ 288static svn_error_t * 289add_file_blame(const char *last_file, 290 const char *cur_file, 291 struct blame_chain *chain, 292 struct rev *rev, 293 const svn_diff_file_options_t *diff_options, 294 svn_cancel_func_t cancel_func, 295 void *cancel_baton, 296 apr_pool_t *pool) 297{ 298 if (!last_file) 299 { 300 SVN_ERR_ASSERT(chain->blame == NULL); 301 chain->blame = blame_create(chain, rev, 0); 302 } 303 else 304 { 305 svn_diff_t *diff; 306 struct diff_baton diff_baton; 307 308 diff_baton.chain = chain; 309 diff_baton.rev = rev; 310 311 /* We have a previous file. Get the diff and adjust blame info. */ 312 SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file, 313 diff_options, pool)); 314 SVN_ERR(svn_diff_output2(diff, &diff_baton, &output_fns, 315 cancel_func, cancel_baton)); 316 } 317 318 return SVN_NO_ERROR; 319} 320 321/* Record the blame information for the revision in BATON->file_rev_baton. 322 */ 323static svn_error_t * 324update_blame(void *baton) 325{ 326 struct delta_baton *dbaton = baton; 327 struct file_rev_baton *frb = dbaton->file_rev_baton; 328 struct blame_chain *chain; 329 330 /* Close the source file used for the delta. 331 It is important to do this early, since otherwise, they will be deleted 332 before all handles are closed, which leads to failures on some platforms 333 when new tempfiles are to be created. */ 334 if (dbaton->source_stream) 335 SVN_ERR(svn_stream_close(dbaton->source_stream)); 336 337 /* If we are including merged revisions, we need to add each rev to the 338 merged chain. */ 339 if (frb->include_merged_revisions) 340 chain = frb->merged_chain; 341 else 342 chain = frb->chain; 343 344 /* Process this file. */ 345 SVN_ERR(add_file_blame(frb->last_filename, 346 dbaton->filename, chain, dbaton->rev, 347 frb->diff_options, 348 frb->ctx->cancel_func, frb->ctx->cancel_baton, 349 frb->currpool)); 350 351 /* If we are including merged revisions, and the current revision is not a 352 merged one, we need to add its blame info to the chain for the original 353 line of history. */ 354 if (frb->include_merged_revisions && ! dbaton->is_merged_revision) 355 { 356 apr_pool_t *tmppool; 357 358 SVN_ERR(add_file_blame(frb->last_original_filename, 359 dbaton->filename, frb->chain, dbaton->rev, 360 frb->diff_options, 361 frb->ctx->cancel_func, frb->ctx->cancel_baton, 362 frb->currpool)); 363 364 /* This filename could be around for a while, potentially, so 365 use the longer lifetime pool, and switch it with the previous one*/ 366 svn_pool_clear(frb->prevfilepool); 367 tmppool = frb->filepool; 368 frb->filepool = frb->prevfilepool; 369 frb->prevfilepool = tmppool; 370 371 frb->last_original_filename = apr_pstrdup(frb->filepool, 372 dbaton->filename); 373 } 374 375 /* Prepare for next revision. */ 376 377 /* Remember the file name so we can diff it with the next revision. */ 378 frb->last_filename = dbaton->filename; 379 380 /* Switch pools. */ 381 { 382 apr_pool_t *tmp_pool = frb->lastpool; 383 frb->lastpool = frb->currpool; 384 frb->currpool = tmp_pool; 385 } 386 387 return SVN_NO_ERROR; 388} 389 390/* The delta window handler for the text delta between the previously seen 391 * revision and the revision currently being handled. 392 * 393 * Record the blame information for this revision in BATON->file_rev_baton. 394 * 395 * Implements svn_txdelta_window_handler_t. 396 */ 397static svn_error_t * 398window_handler(svn_txdelta_window_t *window, void *baton) 399{ 400 struct delta_baton *dbaton = baton; 401 402 /* Call the wrapped handler first. */ 403 if (dbaton->wrapped_handler) 404 SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton)); 405 406 /* We patiently wait for the NULL window marking the end. */ 407 if (window) 408 return SVN_NO_ERROR; 409 410 /* Diff and update blame info. */ 411 SVN_ERR(update_blame(baton)); 412 413 return SVN_NO_ERROR; 414} 415 416 417/* Calculate and record blame information for one revision of the file, 418 * by comparing the file content against the previously seen revision. 419 * 420 * This handler is called once for each interesting revision of the file. 421 * 422 * Record the blame information for this revision in (file_rev_baton) BATON. 423 * 424 * Implements svn_file_rev_handler_t. 425 */ 426static svn_error_t * 427file_rev_handler(void *baton, const char *path, svn_revnum_t revnum, 428 apr_hash_t *rev_props, 429 svn_boolean_t merged_revision, 430 svn_txdelta_window_handler_t *content_delta_handler, 431 void **content_delta_baton, 432 apr_array_header_t *prop_diffs, 433 apr_pool_t *pool) 434{ 435 struct file_rev_baton *frb = baton; 436 svn_stream_t *last_stream; 437 svn_stream_t *cur_stream; 438 struct delta_baton *delta_baton; 439 apr_pool_t *filepool; 440 441 /* Clear the current pool. */ 442 svn_pool_clear(frb->currpool); 443 444 if (frb->check_mime_type) 445 { 446 apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool); 447 const char *value; 448 449 frb->check_mime_type = FALSE; /* Only check first */ 450 451 value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); 452 453 if (value && svn_mime_type_is_binary(value)) 454 { 455 return svn_error_createf( 456 SVN_ERR_CLIENT_IS_BINARY_FILE, NULL, 457 _("Cannot calculate blame information for binary file '%s'"), 458 (svn_path_is_url(frb->target) 459 ? frb->target 460 : svn_dirent_local_style(frb->target, pool))); 461 } 462 } 463 464 if (frb->ctx->notify_func2) 465 { 466 svn_wc_notify_t *notify 467 = svn_wc_create_notify_url( 468 svn_path_url_add_component2(frb->repos_root_url, 469 path+1, pool), 470 svn_wc_notify_blame_revision, pool); 471 notify->path = path; 472 notify->kind = svn_node_none; 473 notify->content_state = notify->prop_state 474 = svn_wc_notify_state_inapplicable; 475 notify->lock_state = svn_wc_notify_lock_state_inapplicable; 476 notify->revision = revnum; 477 notify->rev_props = rev_props; 478 frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool); 479 } 480 481 if (frb->ctx->cancel_func) 482 SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton)); 483 484 /* If there were no content changes and no (potential) merges, we couldn't 485 care less about this revision now. Note that we checked the mime type 486 above, so things work if the user just changes the mime type in a commit. 487 Also note that we don't switch the pools in this case. This is important, 488 since the tempfile will be removed by the pool and we need the tempfile 489 from the last revision with content changes. */ 490 if (!content_delta_handler 491 && (!frb->include_merged_revisions || merged_revision)) 492 return SVN_NO_ERROR; 493 494 /* Create delta baton. */ 495 delta_baton = apr_pcalloc(frb->currpool, sizeof(*delta_baton)); 496 497 /* Prepare the text delta window handler. */ 498 if (frb->last_filename) 499 SVN_ERR(svn_stream_open_readonly(&delta_baton->source_stream, frb->last_filename, 500 frb->currpool, pool)); 501 else 502 /* Means empty stream below. */ 503 delta_baton->source_stream = NULL; 504 last_stream = svn_stream_disown(delta_baton->source_stream, pool); 505 506 if (frb->include_merged_revisions && !merged_revision) 507 filepool = frb->filepool; 508 else 509 filepool = frb->currpool; 510 511 SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL, 512 svn_io_file_del_on_pool_cleanup, 513 filepool, filepool)); 514 515 /* Wrap the window handler with our own. */ 516 delta_baton->file_rev_baton = frb; 517 delta_baton->is_merged_revision = merged_revision; 518 519 /* Create the rev structure. */ 520 delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev)); 521 522 if (frb->backwards) 523 { 524 /* Use from last round... 525 SVN_INVALID_REVNUM on first, which is exactly 526 what we want */ 527 delta_baton->rev->revision = frb->last_revnum; 528 delta_baton->rev->rev_props = frb->last_props; 529 530 /* Store for next delta */ 531 if (revnum >= MIN(frb->start_rev, frb->end_rev)) 532 { 533 frb->last_revnum = revnum; 534 frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool); 535 } 536 /* Else: Not needed on last rev */ 537 } 538 else if (merged_revision 539 || (revnum >= MIN(frb->start_rev, frb->end_rev))) 540 { 541 /* 1+ for the "youngest to oldest" blame */ 542 SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev)); 543 544 /* Set values from revision props. */ 545 delta_baton->rev->revision = revnum; 546 delta_baton->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool); 547 } 548 else 549 { 550 /* We shouldn't get more than one revision outside the 551 specified range (unless we alsoe receive merged revisions) */ 552 SVN_ERR_ASSERT((frb->last_filename == NULL) 553 || frb->include_merged_revisions); 554 555 /* The file existed before start_rev; generate no blame info for 556 lines from this revision (or before). 557 558 This revision specifies the state as it was at the start revision */ 559 560 delta_baton->rev->revision = SVN_INVALID_REVNUM; 561 } 562 563 if (frb->include_merged_revisions) 564 delta_baton->rev->path = apr_pstrdup(frb->mainpool, path); 565 566 /* Keep last revision for postprocessing after all changes */ 567 frb->last_rev = delta_baton->rev; 568 569 /* Handle all delta - even if it is empty. 570 We must do the latter to "merge" blame info from other branches. */ 571 if (content_delta_handler) 572 { 573 /* Proper delta - get window handler for applying delta. 574 svn_ra_get_file_revs2 will drive the delta editor. */ 575 svn_txdelta_apply(last_stream, cur_stream, NULL, NULL, 576 frb->currpool, 577 &delta_baton->wrapped_handler, 578 &delta_baton->wrapped_baton); 579 *content_delta_handler = window_handler; 580 *content_delta_baton = delta_baton; 581 } 582 else 583 { 584 /* Apply an empty delta, i.e. simply copy the old contents. 585 We can't simply use the existing file due to the pool rotation logic. 586 Trigger the blame update magic. */ 587 SVN_ERR(svn_stream_copy3(last_stream, cur_stream, NULL, NULL, pool)); 588 SVN_ERR(update_blame(delta_baton)); 589 } 590 591 return SVN_NO_ERROR; 592} 593 594/* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks, 595 and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the 596 same starting value. Both CHAIN_ORIG and CHAIN_MERGED should not be 597 NULL. */ 598static void 599normalize_blames(struct blame_chain *chain, 600 struct blame_chain *chain_merged, 601 apr_pool_t *pool) 602{ 603 struct blame *walk, *walk_merged; 604 605 /* Walk over the CHAIN's blame chunks and CHAIN_MERGED's blame chunks, 606 creating new chunks as needed. */ 607 for (walk = chain->blame, walk_merged = chain_merged->blame; 608 walk->next && walk_merged->next; 609 walk = walk->next, walk_merged = walk_merged->next) 610 { 611 /* The current chunks should always be starting at the same offset. */ 612 assert(walk->start == walk_merged->start); 613 614 if (walk->next->start < walk_merged->next->start) 615 { 616 /* insert a new chunk in CHAIN_MERGED. */ 617 struct blame *tmp = blame_create(chain_merged, walk_merged->rev, 618 walk->next->start); 619 tmp->next = walk_merged->next; 620 walk_merged->next = tmp; 621 } 622 623 if (walk->next->start > walk_merged->next->start) 624 { 625 /* insert a new chunk in CHAIN. */ 626 struct blame *tmp = blame_create(chain, walk->rev, 627 walk_merged->next->start); 628 tmp->next = walk->next; 629 walk->next = tmp; 630 } 631 } 632 633 /* If both NEXT pointers are null, the lists are equally long, otherwise 634 we need to extend one of them. If CHAIN is longer, append new chunks 635 to CHAIN_MERGED until its length matches that of CHAIN. */ 636 while (walk->next != NULL) 637 { 638 struct blame *tmp = blame_create(chain_merged, walk_merged->rev, 639 walk->next->start); 640 walk_merged->next = tmp; 641 642 walk_merged = walk_merged->next; 643 walk = walk->next; 644 } 645 646 /* Same as above, only extend CHAIN to match CHAIN_MERGED. */ 647 while (walk_merged->next != NULL) 648 { 649 struct blame *tmp = blame_create(chain, walk->rev, 650 walk_merged->next->start); 651 walk->next = tmp; 652 653 walk = walk->next; 654 walk_merged = walk_merged->next; 655 } 656} 657 658svn_error_t * 659svn_client_blame5(const char *target, 660 const svn_opt_revision_t *peg_revision, 661 const svn_opt_revision_t *start, 662 const svn_opt_revision_t *end, 663 const svn_diff_file_options_t *diff_options, 664 svn_boolean_t ignore_mime_type, 665 svn_boolean_t include_merged_revisions, 666 svn_client_blame_receiver3_t receiver, 667 void *receiver_baton, 668 svn_client_ctx_t *ctx, 669 apr_pool_t *pool) 670{ 671 struct file_rev_baton frb; 672 svn_ra_session_t *ra_session; 673 svn_revnum_t start_revnum, end_revnum; 674 struct blame *walk, *walk_merged = NULL; 675 apr_pool_t *iterpool; 676 svn_stream_t *last_stream; 677 svn_stream_t *stream; 678 const char *target_abspath_or_url; 679 680 if (start->kind == svn_opt_revision_unspecified 681 || end->kind == svn_opt_revision_unspecified) 682 return svn_error_create 683 (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); 684 685 if (svn_path_is_url(target)) 686 target_abspath_or_url = target; 687 else 688 SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool)); 689 690 /* Get an RA plugin for this filesystem object. */ 691 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, NULL, 692 target, NULL, peg_revision, 693 peg_revision, 694 ctx, pool)); 695 696 SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx, 697 target_abspath_or_url, ra_session, 698 start, pool)); 699 700 SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx, 701 target_abspath_or_url, ra_session, 702 end, pool)); 703 704 { 705 svn_client__pathrev_t *loc; 706 svn_opt_revision_t younger_end; 707 younger_end.kind = svn_opt_revision_number; 708 younger_end.value.number = MAX(start_revnum, end_revnum); 709 710 SVN_ERR(svn_client__resolve_rev_and_url(&loc, ra_session, 711 target, peg_revision, 712 &younger_end, 713 ctx, pool)); 714 715 /* Make the session point to the real URL. */ 716 SVN_ERR(svn_ra_reparent(ra_session, loc->url, pool)); 717 } 718 719 /* We check the mime-type of the yougest revision before getting all 720 the older revisions. */ 721 if (!ignore_mime_type 722 && start_revnum < end_revnum) 723 { 724 apr_hash_t *props; 725 const char *mime_type = NULL; 726 727 if (svn_path_is_url(target) 728 || start_revnum > end_revnum 729 || (end->kind != svn_opt_revision_working 730 && end->kind != svn_opt_revision_base)) 731 { 732 SVN_ERR(svn_ra_get_file(ra_session, "", end_revnum, NULL, NULL, 733 &props, pool)); 734 735 mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); 736 } 737 else 738 { 739 const svn_string_t *value; 740 741 if (end->kind == svn_opt_revision_working) 742 SVN_ERR(svn_wc_prop_get2(&value, ctx->wc_ctx, 743 target_abspath_or_url, 744 SVN_PROP_MIME_TYPE, 745 pool, pool)); 746 else 747 { 748 SVN_ERR(svn_wc_get_pristine_props(&props, ctx->wc_ctx, 749 target_abspath_or_url, 750 pool, pool)); 751 752 value = props ? svn_hash_gets(props, SVN_PROP_MIME_TYPE) 753 : NULL; 754 } 755 756 mime_type = value ? value->data : NULL; 757 } 758 759 if (mime_type) 760 { 761 if (svn_mime_type_is_binary(mime_type)) 762 return svn_error_createf 763 (SVN_ERR_CLIENT_IS_BINARY_FILE, 0, 764 _("Cannot calculate blame information for binary file '%s'"), 765 (svn_path_is_url(target) 766 ? target : svn_dirent_local_style(target, pool))); 767 } 768 } 769 770 frb.start_rev = start_revnum; 771 frb.end_rev = end_revnum; 772 frb.target = target; 773 frb.ctx = ctx; 774 frb.diff_options = diff_options; 775 frb.include_merged_revisions = include_merged_revisions; 776 frb.last_filename = NULL; 777 frb.last_rev = NULL; 778 frb.last_original_filename = NULL; 779 frb.chain = apr_palloc(pool, sizeof(*frb.chain)); 780 frb.chain->blame = NULL; 781 frb.chain->avail = NULL; 782 frb.chain->pool = pool; 783 if (include_merged_revisions) 784 { 785 frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain)); 786 frb.merged_chain->blame = NULL; 787 frb.merged_chain->avail = NULL; 788 frb.merged_chain->pool = pool; 789 } 790 frb.backwards = (frb.start_rev > frb.end_rev); 791 frb.last_revnum = SVN_INVALID_REVNUM; 792 frb.last_props = NULL; 793 frb.check_mime_type = (frb.backwards && !ignore_mime_type); 794 795 SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool)); 796 797 frb.mainpool = pool; 798 /* The callback will flip the following two pools, because it needs 799 information from the previous call. Obviously, it can't rely on 800 the lifetime of the pool provided by get_file_revs. */ 801 frb.lastpool = svn_pool_create(pool); 802 frb.currpool = svn_pool_create(pool); 803 if (include_merged_revisions) 804 { 805 frb.filepool = svn_pool_create(pool); 806 frb.prevfilepool = svn_pool_create(pool); 807 } 808 809 /* Collect all blame information. 810 We need to ensure that we get one revision before the start_rev, 811 if available so that we can know what was actually changed in the start 812 revision. */ 813 SVN_ERR(svn_ra_get_file_revs2(ra_session, "", 814 frb.backwards ? start_revnum 815 : MAX(0, start_revnum-1), 816 end_revnum, 817 include_merged_revisions, 818 file_rev_handler, &frb, pool)); 819 820 if (end->kind == svn_opt_revision_working) 821 { 822 /* If the local file is modified we have to call the handler on the 823 working copy file with keywords unexpanded */ 824 svn_wc_status3_t *status; 825 826 SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool, 827 pool)); 828 829 if (status->text_status != svn_wc_status_normal 830 || (status->prop_status != svn_wc_status_normal 831 && status->prop_status != svn_wc_status_none)) 832 { 833 svn_stream_t *wcfile; 834 svn_stream_t *tempfile; 835 svn_opt_revision_t rev; 836 svn_boolean_t normalize_eols = FALSE; 837 const char *temppath; 838 839 if (status->prop_status != svn_wc_status_none) 840 { 841 const svn_string_t *eol_style; 842 SVN_ERR(svn_wc_prop_get2(&eol_style, ctx->wc_ctx, 843 target_abspath_or_url, 844 SVN_PROP_EOL_STYLE, 845 pool, pool)); 846 847 if (eol_style) 848 { 849 svn_subst_eol_style_t style; 850 const char *eol; 851 svn_subst_eol_style_from_value(&style, &eol, eol_style->data); 852 853 normalize_eols = (style == svn_subst_eol_style_native); 854 } 855 } 856 857 rev.kind = svn_opt_revision_working; 858 SVN_ERR(svn_client__get_normalized_stream(&wcfile, ctx->wc_ctx, 859 target_abspath_or_url, &rev, 860 FALSE, normalize_eols, 861 ctx->cancel_func, 862 ctx->cancel_baton, 863 pool, pool)); 864 865 SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL, 866 svn_io_file_del_on_pool_cleanup, 867 pool, pool)); 868 869 SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func, 870 ctx->cancel_baton, pool)); 871 872 SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL, 873 frb.diff_options, 874 ctx->cancel_func, ctx->cancel_baton, pool)); 875 876 frb.last_filename = temppath; 877 } 878 } 879 880 /* Report the blame to the caller. */ 881 882 /* The callback has to have been called at least once. */ 883 SVN_ERR_ASSERT(frb.last_filename != NULL); 884 885 /* Create a pool for the iteration below. */ 886 iterpool = svn_pool_create(pool); 887 888 /* Open the last file and get a stream. */ 889 SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename, 890 pool, pool)); 891 stream = svn_subst_stream_translated(last_stream, 892 "\n", TRUE, NULL, FALSE, pool); 893 894 /* Perform optional merged chain normalization. */ 895 if (include_merged_revisions) 896 { 897 /* If we never created any blame for the original chain, create it now, 898 with the most recent changed revision. This could occur if a file 899 was created on a branch and them merged to another branch. This is 900 semanticly a copy, and we want to use the revision on the branch as 901 the most recently changed revision. ### Is this really what we want 902 to do here? Do the sematics of copy change? */ 903 if (!frb.chain->blame) 904 frb.chain->blame = blame_create(frb.chain, frb.last_rev, 0); 905 906 normalize_blames(frb.chain, frb.merged_chain, pool); 907 walk_merged = frb.merged_chain->blame; 908 } 909 910 /* Process each blame item. */ 911 for (walk = frb.chain->blame; walk; walk = walk->next) 912 { 913 apr_off_t line_no; 914 svn_revnum_t merged_rev; 915 const char *merged_path; 916 apr_hash_t *merged_rev_props; 917 918 if (walk_merged) 919 { 920 merged_rev = walk_merged->rev->revision; 921 merged_rev_props = walk_merged->rev->rev_props; 922 merged_path = walk_merged->rev->path; 923 } 924 else 925 { 926 merged_rev = SVN_INVALID_REVNUM; 927 merged_rev_props = NULL; 928 merged_path = NULL; 929 } 930 931 for (line_no = walk->start; 932 !walk->next || line_no < walk->next->start; 933 ++line_no) 934 { 935 svn_boolean_t eof; 936 svn_stringbuf_t *sb; 937 938 svn_pool_clear(iterpool); 939 SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool)); 940 if (ctx->cancel_func) 941 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 942 if (!eof || sb->len) 943 { 944 if (walk->rev) 945 SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, 946 line_no, walk->rev->revision, 947 walk->rev->rev_props, merged_rev, 948 merged_rev_props, merged_path, 949 sb->data, FALSE, iterpool)); 950 else 951 SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, 952 line_no, SVN_INVALID_REVNUM, 953 NULL, SVN_INVALID_REVNUM, 954 NULL, NULL, 955 sb->data, TRUE, iterpool)); 956 } 957 if (eof) break; 958 } 959 960 if (walk_merged) 961 walk_merged = walk_merged->next; 962 } 963 964 SVN_ERR(svn_stream_close(stream)); 965 966 svn_pool_destroy(frb.lastpool); 967 svn_pool_destroy(frb.currpool); 968 if (include_merged_revisions) 969 { 970 svn_pool_destroy(frb.filepool); 971 svn_pool_destroy(frb.prevfilepool); 972 } 973 svn_pool_destroy(iterpool); 974 975 return SVN_NO_ERROR; 976} 977