1251881Speter/* 2251881Speter * blame.c: return blame messages 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter#include <apr_pools.h> 25251881Speter 26251881Speter#include "client.h" 27251881Speter 28251881Speter#include "svn_client.h" 29251881Speter#include "svn_subst.h" 30251881Speter#include "svn_string.h" 31251881Speter#include "svn_error.h" 32251881Speter#include "svn_diff.h" 33251881Speter#include "svn_pools.h" 34251881Speter#include "svn_dirent_uri.h" 35251881Speter#include "svn_path.h" 36251881Speter#include "svn_props.h" 37289180Speter#include "svn_hash.h" 38251881Speter#include "svn_sorts.h" 39251881Speter 40251881Speter#include "private/svn_wc_private.h" 41251881Speter 42251881Speter#include "svn_private_config.h" 43251881Speter 44251881Speter#include <assert.h> 45251881Speter 46251881Speter/* The metadata associated with a particular revision. */ 47251881Speterstruct rev 48251881Speter{ 49251881Speter svn_revnum_t revision; /* the revision number */ 50251881Speter apr_hash_t *rev_props; /* the revision properties */ 51251881Speter /* Used for merge reporting. */ 52251881Speter const char *path; /* the absolute repository path */ 53251881Speter}; 54251881Speter 55251881Speter/* One chunk of blame */ 56251881Speterstruct blame 57251881Speter{ 58251881Speter const struct rev *rev; /* the responsible revision */ 59251881Speter apr_off_t start; /* the starting diff-token (line) */ 60251881Speter struct blame *next; /* the next chunk */ 61251881Speter}; 62251881Speter 63251881Speter/* A chain of blame chunks */ 64251881Speterstruct blame_chain 65251881Speter{ 66251881Speter struct blame *blame; /* linked list of blame chunks */ 67251881Speter struct blame *avail; /* linked list of free blame chunks */ 68251881Speter struct apr_pool_t *pool; /* Allocate members from this pool. */ 69251881Speter}; 70251881Speter 71251881Speter/* The baton use for the diff output routine. */ 72251881Speterstruct diff_baton { 73251881Speter struct blame_chain *chain; 74251881Speter const struct rev *rev; 75251881Speter}; 76251881Speter 77289180Speter/* The baton used for a file revision. Lives the entire operation */ 78251881Speterstruct file_rev_baton { 79251881Speter svn_revnum_t start_rev, end_rev; 80289180Speter svn_boolean_t backwards; 81251881Speter const char *target; 82251881Speter svn_client_ctx_t *ctx; 83251881Speter const svn_diff_file_options_t *diff_options; 84251881Speter /* name of file containing the previous revision of the file */ 85251881Speter const char *last_filename; 86289180Speter struct rev *last_rev; /* the rev of the last modification */ 87251881Speter struct blame_chain *chain; /* the original blame chain. */ 88251881Speter const char *repos_root_url; /* To construct a url */ 89251881Speter apr_pool_t *mainpool; /* lives during the whole sequence of calls */ 90251881Speter apr_pool_t *lastpool; /* pool used during previous call */ 91251881Speter apr_pool_t *currpool; /* pool used during this call */ 92251881Speter 93251881Speter /* These are used for tracking merged revisions. */ 94251881Speter svn_boolean_t include_merged_revisions; 95251881Speter struct blame_chain *merged_chain; /* the merged blame chain. */ 96251881Speter /* name of file containing the previous merged revision of the file */ 97251881Speter const char *last_original_filename; 98251881Speter /* pools for files which may need to persist for more than one rev. */ 99251881Speter apr_pool_t *filepool; 100251881Speter apr_pool_t *prevfilepool; 101289180Speter 102289180Speter svn_boolean_t check_mime_type; 103289180Speter 104289180Speter /* When blaming backwards we have to use the changes 105289180Speter on the *next* revision, as the interesting change 106289180Speter happens when we move to the previous revision */ 107289180Speter svn_revnum_t last_revnum; 108289180Speter apr_hash_t *last_props; 109251881Speter}; 110251881Speter 111289180Speter/* The baton used by the txdelta window handler. Allocated per revision */ 112251881Speterstruct delta_baton { 113251881Speter /* Our underlying handler/baton that we wrap */ 114251881Speter svn_txdelta_window_handler_t wrapped_handler; 115251881Speter void *wrapped_baton; 116251881Speter struct file_rev_baton *file_rev_baton; 117289180Speter svn_stream_t *source_stream; /* the delta source */ 118251881Speter const char *filename; 119289180Speter svn_boolean_t is_merged_revision; 120289180Speter struct rev *rev; /* the rev struct for the current revision */ 121251881Speter}; 122251881Speter 123251881Speter 124251881Speter 125251881Speter 126251881Speter/* Return a blame chunk associated with REV for a change starting 127251881Speter at token START, and allocated in CHAIN->mainpool. */ 128251881Speterstatic struct blame * 129251881Speterblame_create(struct blame_chain *chain, 130251881Speter const struct rev *rev, 131251881Speter apr_off_t start) 132251881Speter{ 133251881Speter struct blame *blame; 134251881Speter if (chain->avail) 135251881Speter { 136251881Speter blame = chain->avail; 137251881Speter chain->avail = blame->next; 138251881Speter } 139251881Speter else 140251881Speter blame = apr_palloc(chain->pool, sizeof(*blame)); 141251881Speter blame->rev = rev; 142251881Speter blame->start = start; 143251881Speter blame->next = NULL; 144251881Speter return blame; 145251881Speter} 146251881Speter 147251881Speter/* Destroy a blame chunk. */ 148251881Speterstatic void 149251881Speterblame_destroy(struct blame_chain *chain, 150251881Speter struct blame *blame) 151251881Speter{ 152251881Speter blame->next = chain->avail; 153251881Speter chain->avail = blame; 154251881Speter} 155251881Speter 156251881Speter/* Return the blame chunk that contains token OFF, starting the search at 157251881Speter BLAME. */ 158251881Speterstatic struct blame * 159251881Speterblame_find(struct blame *blame, apr_off_t off) 160251881Speter{ 161251881Speter struct blame *prev = NULL; 162251881Speter while (blame) 163251881Speter { 164251881Speter if (blame->start > off) break; 165251881Speter prev = blame; 166251881Speter blame = blame->next; 167251881Speter } 168251881Speter return prev; 169251881Speter} 170251881Speter 171251881Speter/* Shift the start-point of BLAME and all subsequence blame-chunks 172251881Speter by ADJUST tokens */ 173251881Speterstatic void 174251881Speterblame_adjust(struct blame *blame, apr_off_t adjust) 175251881Speter{ 176251881Speter while (blame) 177251881Speter { 178251881Speter blame->start += adjust; 179251881Speter blame = blame->next; 180251881Speter } 181251881Speter} 182251881Speter 183251881Speter/* Delete the blame associated with the region from token START to 184251881Speter START + LENGTH */ 185251881Speterstatic svn_error_t * 186251881Speterblame_delete_range(struct blame_chain *chain, 187251881Speter apr_off_t start, 188251881Speter apr_off_t length) 189251881Speter{ 190251881Speter struct blame *first = blame_find(chain->blame, start); 191251881Speter struct blame *last = blame_find(chain->blame, start + length); 192251881Speter struct blame *tail = last->next; 193251881Speter 194251881Speter if (first != last) 195251881Speter { 196251881Speter struct blame *walk = first->next; 197251881Speter while (walk != last) 198251881Speter { 199251881Speter struct blame *next = walk->next; 200251881Speter blame_destroy(chain, walk); 201251881Speter walk = next; 202251881Speter } 203251881Speter first->next = last; 204251881Speter last->start = start; 205251881Speter if (first->start == start) 206251881Speter { 207251881Speter *first = *last; 208251881Speter blame_destroy(chain, last); 209251881Speter last = first; 210251881Speter } 211251881Speter } 212251881Speter 213251881Speter if (tail && tail->start == last->start + length) 214251881Speter { 215251881Speter *last = *tail; 216251881Speter blame_destroy(chain, tail); 217251881Speter tail = last->next; 218251881Speter } 219251881Speter 220251881Speter blame_adjust(tail, -length); 221251881Speter 222251881Speter return SVN_NO_ERROR; 223251881Speter} 224251881Speter 225251881Speter/* Insert a chunk of blame associated with REV starting 226251881Speter at token START and continuing for LENGTH tokens */ 227251881Speterstatic svn_error_t * 228251881Speterblame_insert_range(struct blame_chain *chain, 229251881Speter const struct rev *rev, 230251881Speter apr_off_t start, 231251881Speter apr_off_t length) 232251881Speter{ 233251881Speter struct blame *head = chain->blame; 234251881Speter struct blame *point = blame_find(head, start); 235251881Speter struct blame *insert; 236251881Speter 237251881Speter if (point->start == start) 238251881Speter { 239251881Speter insert = blame_create(chain, point->rev, point->start + length); 240251881Speter point->rev = rev; 241251881Speter insert->next = point->next; 242251881Speter point->next = insert; 243251881Speter } 244251881Speter else 245251881Speter { 246251881Speter struct blame *middle; 247251881Speter middle = blame_create(chain, rev, start); 248251881Speter insert = blame_create(chain, point->rev, start + length); 249251881Speter middle->next = insert; 250251881Speter insert->next = point->next; 251251881Speter point->next = middle; 252251881Speter } 253251881Speter blame_adjust(insert->next, length); 254251881Speter 255251881Speter return SVN_NO_ERROR; 256251881Speter} 257251881Speter 258251881Speter/* Callback for diff between subsequent revisions */ 259251881Speterstatic svn_error_t * 260251881Speteroutput_diff_modified(void *baton, 261251881Speter apr_off_t original_start, 262251881Speter apr_off_t original_length, 263251881Speter apr_off_t modified_start, 264251881Speter apr_off_t modified_length, 265251881Speter apr_off_t latest_start, 266251881Speter apr_off_t latest_length) 267251881Speter{ 268251881Speter struct diff_baton *db = baton; 269251881Speter 270251881Speter if (original_length) 271251881Speter SVN_ERR(blame_delete_range(db->chain, modified_start, original_length)); 272251881Speter 273251881Speter if (modified_length) 274251881Speter SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start, 275251881Speter modified_length)); 276251881Speter 277251881Speter return SVN_NO_ERROR; 278251881Speter} 279251881Speter 280251881Speterstatic const svn_diff_output_fns_t output_fns = { 281251881Speter NULL, 282251881Speter output_diff_modified 283251881Speter}; 284251881Speter 285251881Speter/* Add the blame for the diffs between LAST_FILE and CUR_FILE to CHAIN, 286251881Speter for revision REV. LAST_FILE may be NULL in which 287251881Speter case blame is added for every line of CUR_FILE. */ 288251881Speterstatic svn_error_t * 289251881Speteradd_file_blame(const char *last_file, 290251881Speter const char *cur_file, 291251881Speter struct blame_chain *chain, 292251881Speter struct rev *rev, 293251881Speter const svn_diff_file_options_t *diff_options, 294289180Speter svn_cancel_func_t cancel_func, 295289180Speter void *cancel_baton, 296251881Speter apr_pool_t *pool) 297251881Speter{ 298251881Speter if (!last_file) 299251881Speter { 300251881Speter SVN_ERR_ASSERT(chain->blame == NULL); 301251881Speter chain->blame = blame_create(chain, rev, 0); 302251881Speter } 303251881Speter else 304251881Speter { 305251881Speter svn_diff_t *diff; 306251881Speter struct diff_baton diff_baton; 307251881Speter 308251881Speter diff_baton.chain = chain; 309251881Speter diff_baton.rev = rev; 310251881Speter 311251881Speter /* We have a previous file. Get the diff and adjust blame info. */ 312251881Speter SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file, 313251881Speter diff_options, pool)); 314289180Speter SVN_ERR(svn_diff_output2(diff, &diff_baton, &output_fns, 315289180Speter cancel_func, cancel_baton)); 316251881Speter } 317251881Speter 318251881Speter return SVN_NO_ERROR; 319251881Speter} 320251881Speter 321289180Speter/* Record the blame information for the revision in BATON->file_rev_baton. 322251881Speter */ 323251881Speterstatic svn_error_t * 324289180Speterupdate_blame(void *baton) 325251881Speter{ 326251881Speter struct delta_baton *dbaton = baton; 327251881Speter struct file_rev_baton *frb = dbaton->file_rev_baton; 328251881Speter struct blame_chain *chain; 329251881Speter 330289180Speter /* Close the source file used for the delta. 331289180Speter It is important to do this early, since otherwise, they will be deleted 332289180Speter before all handles are closed, which leads to failures on some platforms 333289180Speter when new tempfiles are to be created. */ 334289180Speter if (dbaton->source_stream) 335289180Speter SVN_ERR(svn_stream_close(dbaton->source_stream)); 336251881Speter 337251881Speter /* If we are including merged revisions, we need to add each rev to the 338251881Speter merged chain. */ 339251881Speter if (frb->include_merged_revisions) 340251881Speter chain = frb->merged_chain; 341251881Speter else 342251881Speter chain = frb->chain; 343251881Speter 344251881Speter /* Process this file. */ 345251881Speter SVN_ERR(add_file_blame(frb->last_filename, 346289180Speter dbaton->filename, chain, dbaton->rev, 347289180Speter frb->diff_options, 348289180Speter frb->ctx->cancel_func, frb->ctx->cancel_baton, 349289180Speter frb->currpool)); 350251881Speter 351251881Speter /* If we are including merged revisions, and the current revision is not a 352251881Speter merged one, we need to add its blame info to the chain for the original 353251881Speter line of history. */ 354289180Speter if (frb->include_merged_revisions && ! dbaton->is_merged_revision) 355251881Speter { 356251881Speter apr_pool_t *tmppool; 357251881Speter 358251881Speter SVN_ERR(add_file_blame(frb->last_original_filename, 359289180Speter dbaton->filename, frb->chain, dbaton->rev, 360289180Speter frb->diff_options, 361289180Speter frb->ctx->cancel_func, frb->ctx->cancel_baton, 362289180Speter frb->currpool)); 363251881Speter 364251881Speter /* This filename could be around for a while, potentially, so 365251881Speter use the longer lifetime pool, and switch it with the previous one*/ 366251881Speter svn_pool_clear(frb->prevfilepool); 367251881Speter tmppool = frb->filepool; 368251881Speter frb->filepool = frb->prevfilepool; 369251881Speter frb->prevfilepool = tmppool; 370251881Speter 371251881Speter frb->last_original_filename = apr_pstrdup(frb->filepool, 372251881Speter dbaton->filename); 373251881Speter } 374251881Speter 375251881Speter /* Prepare for next revision. */ 376251881Speter 377251881Speter /* Remember the file name so we can diff it with the next revision. */ 378251881Speter frb->last_filename = dbaton->filename; 379251881Speter 380251881Speter /* Switch pools. */ 381251881Speter { 382251881Speter apr_pool_t *tmp_pool = frb->lastpool; 383251881Speter frb->lastpool = frb->currpool; 384251881Speter frb->currpool = tmp_pool; 385251881Speter } 386251881Speter 387251881Speter return SVN_NO_ERROR; 388251881Speter} 389251881Speter 390289180Speter/* The delta window handler for the text delta between the previously seen 391289180Speter * revision and the revision currently being handled. 392289180Speter * 393289180Speter * Record the blame information for this revision in BATON->file_rev_baton. 394289180Speter * 395289180Speter * Implements svn_txdelta_window_handler_t. 396289180Speter */ 397289180Speterstatic svn_error_t * 398289180Speterwindow_handler(svn_txdelta_window_t *window, void *baton) 399289180Speter{ 400289180Speter struct delta_baton *dbaton = baton; 401251881Speter 402289180Speter /* Call the wrapped handler first. */ 403289180Speter if (dbaton->wrapped_handler) 404289180Speter SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton)); 405289180Speter 406289180Speter /* We patiently wait for the NULL window marking the end. */ 407289180Speter if (window) 408289180Speter return SVN_NO_ERROR; 409289180Speter 410289180Speter /* Diff and update blame info. */ 411289180Speter SVN_ERR(update_blame(baton)); 412289180Speter 413289180Speter return SVN_NO_ERROR; 414289180Speter} 415289180Speter 416289180Speter 417251881Speter/* Calculate and record blame information for one revision of the file, 418251881Speter * by comparing the file content against the previously seen revision. 419251881Speter * 420251881Speter * This handler is called once for each interesting revision of the file. 421251881Speter * 422251881Speter * Record the blame information for this revision in (file_rev_baton) BATON. 423251881Speter * 424251881Speter * Implements svn_file_rev_handler_t. 425251881Speter */ 426251881Speterstatic svn_error_t * 427251881Speterfile_rev_handler(void *baton, const char *path, svn_revnum_t revnum, 428251881Speter apr_hash_t *rev_props, 429251881Speter svn_boolean_t merged_revision, 430251881Speter svn_txdelta_window_handler_t *content_delta_handler, 431251881Speter void **content_delta_baton, 432251881Speter apr_array_header_t *prop_diffs, 433251881Speter apr_pool_t *pool) 434251881Speter{ 435251881Speter struct file_rev_baton *frb = baton; 436251881Speter svn_stream_t *last_stream; 437251881Speter svn_stream_t *cur_stream; 438251881Speter struct delta_baton *delta_baton; 439251881Speter apr_pool_t *filepool; 440251881Speter 441251881Speter /* Clear the current pool. */ 442251881Speter svn_pool_clear(frb->currpool); 443251881Speter 444289180Speter if (frb->check_mime_type) 445289180Speter { 446289180Speter apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool); 447289180Speter const char *value; 448289180Speter 449289180Speter frb->check_mime_type = FALSE; /* Only check first */ 450289180Speter 451289180Speter value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); 452289180Speter 453289180Speter if (value && svn_mime_type_is_binary(value)) 454289180Speter { 455289180Speter return svn_error_createf( 456289180Speter SVN_ERR_CLIENT_IS_BINARY_FILE, NULL, 457289180Speter _("Cannot calculate blame information for binary file '%s'"), 458289180Speter (svn_path_is_url(frb->target) 459362181Sdim ? frb->target 460289180Speter : svn_dirent_local_style(frb->target, pool))); 461289180Speter } 462289180Speter } 463289180Speter 464251881Speter if (frb->ctx->notify_func2) 465251881Speter { 466251881Speter svn_wc_notify_t *notify 467251881Speter = svn_wc_create_notify_url( 468251881Speter svn_path_url_add_component2(frb->repos_root_url, 469251881Speter path+1, pool), 470251881Speter svn_wc_notify_blame_revision, pool); 471251881Speter notify->path = path; 472251881Speter notify->kind = svn_node_none; 473251881Speter notify->content_state = notify->prop_state 474251881Speter = svn_wc_notify_state_inapplicable; 475251881Speter notify->lock_state = svn_wc_notify_lock_state_inapplicable; 476251881Speter notify->revision = revnum; 477251881Speter notify->rev_props = rev_props; 478251881Speter frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool); 479251881Speter } 480251881Speter 481251881Speter if (frb->ctx->cancel_func) 482251881Speter SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton)); 483251881Speter 484289180Speter /* If there were no content changes and no (potential) merges, we couldn't 485289180Speter care less about this revision now. Note that we checked the mime type 486289180Speter above, so things work if the user just changes the mime type in a commit. 487251881Speter Also note that we don't switch the pools in this case. This is important, 488251881Speter since the tempfile will be removed by the pool and we need the tempfile 489251881Speter from the last revision with content changes. */ 490289180Speter if (!content_delta_handler 491289180Speter && (!frb->include_merged_revisions || merged_revision)) 492251881Speter return SVN_NO_ERROR; 493251881Speter 494251881Speter /* Create delta baton. */ 495289180Speter delta_baton = apr_pcalloc(frb->currpool, sizeof(*delta_baton)); 496251881Speter 497251881Speter /* Prepare the text delta window handler. */ 498251881Speter if (frb->last_filename) 499289180Speter SVN_ERR(svn_stream_open_readonly(&delta_baton->source_stream, frb->last_filename, 500251881Speter frb->currpool, pool)); 501251881Speter else 502289180Speter /* Means empty stream below. */ 503289180Speter delta_baton->source_stream = NULL; 504289180Speter last_stream = svn_stream_disown(delta_baton->source_stream, pool); 505251881Speter 506289180Speter if (frb->include_merged_revisions && !merged_revision) 507251881Speter filepool = frb->filepool; 508251881Speter else 509251881Speter filepool = frb->currpool; 510251881Speter 511251881Speter SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL, 512251881Speter svn_io_file_del_on_pool_cleanup, 513289180Speter filepool, filepool)); 514251881Speter 515251881Speter /* Wrap the window handler with our own. */ 516251881Speter delta_baton->file_rev_baton = frb; 517289180Speter delta_baton->is_merged_revision = merged_revision; 518251881Speter 519251881Speter /* Create the rev structure. */ 520289180Speter delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev)); 521251881Speter 522289180Speter if (frb->backwards) 523251881Speter { 524289180Speter /* Use from last round... 525289180Speter SVN_INVALID_REVNUM on first, which is exactly 526289180Speter what we want */ 527289180Speter delta_baton->rev->revision = frb->last_revnum; 528289180Speter delta_baton->rev->rev_props = frb->last_props; 529289180Speter 530289180Speter /* Store for next delta */ 531289180Speter if (revnum >= MIN(frb->start_rev, frb->end_rev)) 532289180Speter { 533289180Speter frb->last_revnum = revnum; 534289180Speter frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool); 535289180Speter } 536289180Speter /* Else: Not needed on last rev */ 537289180Speter } 538289180Speter else if (merged_revision 539289180Speter || (revnum >= MIN(frb->start_rev, frb->end_rev))) 540289180Speter { 541289180Speter /* 1+ for the "youngest to oldest" blame */ 542289180Speter SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev)); 543289180Speter 544289180Speter /* Set values from revision props. */ 545289180Speter delta_baton->rev->revision = revnum; 546289180Speter delta_baton->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool); 547289180Speter } 548289180Speter else 549289180Speter { 550289180Speter /* We shouldn't get more than one revision outside the 551289180Speter specified range (unless we alsoe receive merged revisions) */ 552251881Speter SVN_ERR_ASSERT((frb->last_filename == NULL) 553251881Speter || frb->include_merged_revisions); 554251881Speter 555251881Speter /* The file existed before start_rev; generate no blame info for 556362181Sdim lines from this revision (or before). 557289180Speter 558289180Speter This revision specifies the state as it was at the start revision */ 559289180Speter 560289180Speter delta_baton->rev->revision = SVN_INVALID_REVNUM; 561251881Speter } 562289180Speter 563289180Speter if (frb->include_merged_revisions) 564289180Speter delta_baton->rev->path = apr_pstrdup(frb->mainpool, path); 565289180Speter 566289180Speter /* Keep last revision for postprocessing after all changes */ 567289180Speter frb->last_rev = delta_baton->rev; 568289180Speter 569289180Speter /* Handle all delta - even if it is empty. 570289180Speter We must do the latter to "merge" blame info from other branches. */ 571289180Speter if (content_delta_handler) 572289180Speter { 573289180Speter /* Proper delta - get window handler for applying delta. 574289180Speter svn_ra_get_file_revs2 will drive the delta editor. */ 575289180Speter svn_txdelta_apply(last_stream, cur_stream, NULL, NULL, 576289180Speter frb->currpool, 577289180Speter &delta_baton->wrapped_handler, 578289180Speter &delta_baton->wrapped_baton); 579289180Speter *content_delta_handler = window_handler; 580289180Speter *content_delta_baton = delta_baton; 581289180Speter } 582251881Speter else 583251881Speter { 584289180Speter /* Apply an empty delta, i.e. simply copy the old contents. 585289180Speter We can't simply use the existing file due to the pool rotation logic. 586289180Speter Trigger the blame update magic. */ 587289180Speter SVN_ERR(svn_stream_copy3(last_stream, cur_stream, NULL, NULL, pool)); 588289180Speter SVN_ERR(update_blame(delta_baton)); 589251881Speter } 590251881Speter 591251881Speter return SVN_NO_ERROR; 592251881Speter} 593251881Speter 594251881Speter/* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks, 595251881Speter and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the 596251881Speter same starting value. Both CHAIN_ORIG and CHAIN_MERGED should not be 597251881Speter NULL. */ 598251881Speterstatic void 599251881Speternormalize_blames(struct blame_chain *chain, 600251881Speter struct blame_chain *chain_merged, 601251881Speter apr_pool_t *pool) 602251881Speter{ 603251881Speter struct blame *walk, *walk_merged; 604251881Speter 605251881Speter /* Walk over the CHAIN's blame chunks and CHAIN_MERGED's blame chunks, 606251881Speter creating new chunks as needed. */ 607251881Speter for (walk = chain->blame, walk_merged = chain_merged->blame; 608251881Speter walk->next && walk_merged->next; 609251881Speter walk = walk->next, walk_merged = walk_merged->next) 610251881Speter { 611251881Speter /* The current chunks should always be starting at the same offset. */ 612251881Speter assert(walk->start == walk_merged->start); 613251881Speter 614251881Speter if (walk->next->start < walk_merged->next->start) 615251881Speter { 616251881Speter /* insert a new chunk in CHAIN_MERGED. */ 617251881Speter struct blame *tmp = blame_create(chain_merged, walk_merged->rev, 618251881Speter walk->next->start); 619251881Speter tmp->next = walk_merged->next; 620251881Speter walk_merged->next = tmp; 621251881Speter } 622251881Speter 623251881Speter if (walk->next->start > walk_merged->next->start) 624251881Speter { 625251881Speter /* insert a new chunk in CHAIN. */ 626251881Speter struct blame *tmp = blame_create(chain, walk->rev, 627251881Speter walk_merged->next->start); 628251881Speter tmp->next = walk->next; 629251881Speter walk->next = tmp; 630251881Speter } 631251881Speter } 632251881Speter 633251881Speter /* If both NEXT pointers are null, the lists are equally long, otherwise 634251881Speter we need to extend one of them. If CHAIN is longer, append new chunks 635251881Speter to CHAIN_MERGED until its length matches that of CHAIN. */ 636251881Speter while (walk->next != NULL) 637251881Speter { 638251881Speter struct blame *tmp = blame_create(chain_merged, walk_merged->rev, 639251881Speter walk->next->start); 640251881Speter walk_merged->next = tmp; 641251881Speter 642251881Speter walk_merged = walk_merged->next; 643251881Speter walk = walk->next; 644251881Speter } 645251881Speter 646251881Speter /* Same as above, only extend CHAIN to match CHAIN_MERGED. */ 647251881Speter while (walk_merged->next != NULL) 648251881Speter { 649251881Speter struct blame *tmp = blame_create(chain, walk->rev, 650251881Speter walk_merged->next->start); 651251881Speter walk->next = tmp; 652251881Speter 653251881Speter walk = walk->next; 654251881Speter walk_merged = walk_merged->next; 655251881Speter } 656251881Speter} 657251881Speter 658251881Spetersvn_error_t * 659362181Sdimsvn_client_blame6(svn_revnum_t *start_revnum_p, 660362181Sdim svn_revnum_t *end_revnum_p, 661362181Sdim const char *target, 662251881Speter const svn_opt_revision_t *peg_revision, 663251881Speter const svn_opt_revision_t *start, 664251881Speter const svn_opt_revision_t *end, 665251881Speter const svn_diff_file_options_t *diff_options, 666251881Speter svn_boolean_t ignore_mime_type, 667251881Speter svn_boolean_t include_merged_revisions, 668362181Sdim svn_client_blame_receiver4_t receiver, 669251881Speter void *receiver_baton, 670251881Speter svn_client_ctx_t *ctx, 671251881Speter apr_pool_t *pool) 672251881Speter{ 673251881Speter struct file_rev_baton frb; 674251881Speter svn_ra_session_t *ra_session; 675251881Speter svn_revnum_t start_revnum, end_revnum; 676251881Speter struct blame *walk, *walk_merged = NULL; 677251881Speter apr_pool_t *iterpool; 678251881Speter svn_stream_t *last_stream; 679251881Speter svn_stream_t *stream; 680251881Speter const char *target_abspath_or_url; 681251881Speter 682251881Speter if (start->kind == svn_opt_revision_unspecified 683251881Speter || end->kind == svn_opt_revision_unspecified) 684251881Speter return svn_error_create 685251881Speter (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); 686251881Speter 687251881Speter if (svn_path_is_url(target)) 688251881Speter target_abspath_or_url = target; 689251881Speter else 690251881Speter SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool)); 691251881Speter 692251881Speter /* Get an RA plugin for this filesystem object. */ 693289180Speter SVN_ERR(svn_client__ra_session_from_path2(&ra_session, NULL, 694289180Speter target, NULL, peg_revision, 695289180Speter peg_revision, 696251881Speter ctx, pool)); 697251881Speter 698251881Speter SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx, 699251881Speter target_abspath_or_url, ra_session, 700251881Speter start, pool)); 701362181Sdim if (start_revnum_p) 702362181Sdim *start_revnum_p = start_revnum; 703289180Speter SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx, 704289180Speter target_abspath_or_url, ra_session, 705289180Speter end, pool)); 706362181Sdim if (end_revnum_p) 707362181Sdim *end_revnum_p = end_revnum; 708251881Speter 709289180Speter { 710289180Speter svn_client__pathrev_t *loc; 711289180Speter svn_opt_revision_t younger_end; 712289180Speter younger_end.kind = svn_opt_revision_number; 713289180Speter younger_end.value.number = MAX(start_revnum, end_revnum); 714289180Speter 715289180Speter SVN_ERR(svn_client__resolve_rev_and_url(&loc, ra_session, 716289180Speter target, peg_revision, 717289180Speter &younger_end, 718289180Speter ctx, pool)); 719289180Speter 720289180Speter /* Make the session point to the real URL. */ 721289180Speter SVN_ERR(svn_ra_reparent(ra_session, loc->url, pool)); 722289180Speter } 723289180Speter 724251881Speter /* We check the mime-type of the yougest revision before getting all 725251881Speter the older revisions. */ 726289180Speter if (!ignore_mime_type 727289180Speter && start_revnum < end_revnum) 728251881Speter { 729251881Speter apr_hash_t *props; 730289180Speter const char *mime_type = NULL; 731251881Speter 732289180Speter if (svn_path_is_url(target) 733289180Speter || start_revnum > end_revnum 734289180Speter || (end->kind != svn_opt_revision_working 735289180Speter && end->kind != svn_opt_revision_base)) 736289180Speter { 737289180Speter SVN_ERR(svn_ra_get_file(ra_session, "", end_revnum, NULL, NULL, 738289180Speter &props, pool)); 739251881Speter 740289180Speter mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); 741289180Speter } 742362181Sdim else 743251881Speter { 744289180Speter const svn_string_t *value; 745251881Speter 746289180Speter if (end->kind == svn_opt_revision_working) 747289180Speter SVN_ERR(svn_wc_prop_get2(&value, ctx->wc_ctx, 748289180Speter target_abspath_or_url, 749289180Speter SVN_PROP_MIME_TYPE, 750289180Speter pool, pool)); 751289180Speter else 752289180Speter { 753289180Speter SVN_ERR(svn_wc_get_pristine_props(&props, ctx->wc_ctx, 754289180Speter target_abspath_or_url, 755289180Speter pool, pool)); 756251881Speter 757289180Speter value = props ? svn_hash_gets(props, SVN_PROP_MIME_TYPE) 758289180Speter : NULL; 759289180Speter } 760289180Speter 761289180Speter mime_type = value ? value->data : NULL; 762289180Speter } 763289180Speter 764289180Speter if (mime_type) 765289180Speter { 766289180Speter if (svn_mime_type_is_binary(mime_type)) 767251881Speter return svn_error_createf 768251881Speter (SVN_ERR_CLIENT_IS_BINARY_FILE, 0, 769251881Speter _("Cannot calculate blame information for binary file '%s'"), 770251881Speter (svn_path_is_url(target) 771251881Speter ? target : svn_dirent_local_style(target, pool))); 772251881Speter } 773251881Speter } 774251881Speter 775251881Speter frb.start_rev = start_revnum; 776251881Speter frb.end_rev = end_revnum; 777251881Speter frb.target = target; 778251881Speter frb.ctx = ctx; 779251881Speter frb.diff_options = diff_options; 780251881Speter frb.include_merged_revisions = include_merged_revisions; 781251881Speter frb.last_filename = NULL; 782289180Speter frb.last_rev = NULL; 783251881Speter frb.last_original_filename = NULL; 784251881Speter frb.chain = apr_palloc(pool, sizeof(*frb.chain)); 785251881Speter frb.chain->blame = NULL; 786251881Speter frb.chain->avail = NULL; 787251881Speter frb.chain->pool = pool; 788251881Speter if (include_merged_revisions) 789251881Speter { 790251881Speter frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain)); 791251881Speter frb.merged_chain->blame = NULL; 792251881Speter frb.merged_chain->avail = NULL; 793251881Speter frb.merged_chain->pool = pool; 794251881Speter } 795289180Speter frb.backwards = (frb.start_rev > frb.end_rev); 796289180Speter frb.last_revnum = SVN_INVALID_REVNUM; 797289180Speter frb.last_props = NULL; 798289180Speter frb.check_mime_type = (frb.backwards && !ignore_mime_type); 799251881Speter 800251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool)); 801251881Speter 802251881Speter frb.mainpool = pool; 803251881Speter /* The callback will flip the following two pools, because it needs 804251881Speter information from the previous call. Obviously, it can't rely on 805251881Speter the lifetime of the pool provided by get_file_revs. */ 806251881Speter frb.lastpool = svn_pool_create(pool); 807251881Speter frb.currpool = svn_pool_create(pool); 808251881Speter if (include_merged_revisions) 809251881Speter { 810251881Speter frb.filepool = svn_pool_create(pool); 811251881Speter frb.prevfilepool = svn_pool_create(pool); 812251881Speter } 813251881Speter 814251881Speter /* Collect all blame information. 815251881Speter We need to ensure that we get one revision before the start_rev, 816251881Speter if available so that we can know what was actually changed in the start 817251881Speter revision. */ 818251881Speter SVN_ERR(svn_ra_get_file_revs2(ra_session, "", 819289180Speter frb.backwards ? start_revnum 820289180Speter : MAX(0, start_revnum-1), 821289180Speter end_revnum, 822289180Speter include_merged_revisions, 823251881Speter file_rev_handler, &frb, pool)); 824251881Speter 825251881Speter if (end->kind == svn_opt_revision_working) 826251881Speter { 827251881Speter /* If the local file is modified we have to call the handler on the 828251881Speter working copy file with keywords unexpanded */ 829251881Speter svn_wc_status3_t *status; 830251881Speter 831251881Speter SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool, 832251881Speter pool)); 833251881Speter 834251881Speter if (status->text_status != svn_wc_status_normal 835251881Speter || (status->prop_status != svn_wc_status_normal 836251881Speter && status->prop_status != svn_wc_status_none)) 837251881Speter { 838251881Speter svn_stream_t *wcfile; 839251881Speter svn_stream_t *tempfile; 840251881Speter svn_opt_revision_t rev; 841251881Speter svn_boolean_t normalize_eols = FALSE; 842251881Speter const char *temppath; 843251881Speter 844251881Speter if (status->prop_status != svn_wc_status_none) 845251881Speter { 846251881Speter const svn_string_t *eol_style; 847251881Speter SVN_ERR(svn_wc_prop_get2(&eol_style, ctx->wc_ctx, 848251881Speter target_abspath_or_url, 849251881Speter SVN_PROP_EOL_STYLE, 850251881Speter pool, pool)); 851251881Speter 852251881Speter if (eol_style) 853251881Speter { 854251881Speter svn_subst_eol_style_t style; 855251881Speter const char *eol; 856251881Speter svn_subst_eol_style_from_value(&style, &eol, eol_style->data); 857251881Speter 858251881Speter normalize_eols = (style == svn_subst_eol_style_native); 859251881Speter } 860251881Speter } 861251881Speter 862251881Speter rev.kind = svn_opt_revision_working; 863251881Speter SVN_ERR(svn_client__get_normalized_stream(&wcfile, ctx->wc_ctx, 864251881Speter target_abspath_or_url, &rev, 865251881Speter FALSE, normalize_eols, 866251881Speter ctx->cancel_func, 867251881Speter ctx->cancel_baton, 868251881Speter pool, pool)); 869251881Speter 870251881Speter SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL, 871251881Speter svn_io_file_del_on_pool_cleanup, 872251881Speter pool, pool)); 873251881Speter 874251881Speter SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func, 875251881Speter ctx->cancel_baton, pool)); 876251881Speter 877251881Speter SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL, 878289180Speter frb.diff_options, 879289180Speter ctx->cancel_func, ctx->cancel_baton, pool)); 880251881Speter 881251881Speter frb.last_filename = temppath; 882251881Speter } 883251881Speter } 884251881Speter 885251881Speter /* Report the blame to the caller. */ 886251881Speter 887251881Speter /* The callback has to have been called at least once. */ 888251881Speter SVN_ERR_ASSERT(frb.last_filename != NULL); 889251881Speter 890251881Speter /* Create a pool for the iteration below. */ 891251881Speter iterpool = svn_pool_create(pool); 892251881Speter 893251881Speter /* Open the last file and get a stream. */ 894251881Speter SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename, 895251881Speter pool, pool)); 896251881Speter stream = svn_subst_stream_translated(last_stream, 897251881Speter "\n", TRUE, NULL, FALSE, pool); 898251881Speter 899251881Speter /* Perform optional merged chain normalization. */ 900251881Speter if (include_merged_revisions) 901251881Speter { 902251881Speter /* If we never created any blame for the original chain, create it now, 903251881Speter with the most recent changed revision. This could occur if a file 904251881Speter was created on a branch and them merged to another branch. This is 905251881Speter semanticly a copy, and we want to use the revision on the branch as 906251881Speter the most recently changed revision. ### Is this really what we want 907251881Speter to do here? Do the sematics of copy change? */ 908251881Speter if (!frb.chain->blame) 909289180Speter frb.chain->blame = blame_create(frb.chain, frb.last_rev, 0); 910251881Speter 911251881Speter normalize_blames(frb.chain, frb.merged_chain, pool); 912251881Speter walk_merged = frb.merged_chain->blame; 913251881Speter } 914251881Speter 915251881Speter /* Process each blame item. */ 916251881Speter for (walk = frb.chain->blame; walk; walk = walk->next) 917251881Speter { 918251881Speter apr_off_t line_no; 919251881Speter svn_revnum_t merged_rev; 920251881Speter const char *merged_path; 921251881Speter apr_hash_t *merged_rev_props; 922251881Speter 923251881Speter if (walk_merged) 924251881Speter { 925251881Speter merged_rev = walk_merged->rev->revision; 926251881Speter merged_rev_props = walk_merged->rev->rev_props; 927251881Speter merged_path = walk_merged->rev->path; 928251881Speter } 929251881Speter else 930251881Speter { 931251881Speter merged_rev = SVN_INVALID_REVNUM; 932251881Speter merged_rev_props = NULL; 933251881Speter merged_path = NULL; 934251881Speter } 935251881Speter 936251881Speter for (line_no = walk->start; 937251881Speter !walk->next || line_no < walk->next->start; 938251881Speter ++line_no) 939251881Speter { 940251881Speter svn_boolean_t eof; 941251881Speter svn_stringbuf_t *sb; 942251881Speter 943251881Speter svn_pool_clear(iterpool); 944251881Speter SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool)); 945251881Speter if (ctx->cancel_func) 946251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 947251881Speter if (!eof || sb->len) 948251881Speter { 949362181Sdim svn_string_t line; 950362181Sdim line.data = sb->data; 951362181Sdim line.len = sb->len; 952251881Speter if (walk->rev) 953362181Sdim SVN_ERR(receiver(receiver_baton, 954251881Speter line_no, walk->rev->revision, 955251881Speter walk->rev->rev_props, merged_rev, 956251881Speter merged_rev_props, merged_path, 957362181Sdim &line, FALSE, iterpool)); 958251881Speter else 959362181Sdim SVN_ERR(receiver(receiver_baton, 960251881Speter line_no, SVN_INVALID_REVNUM, 961251881Speter NULL, SVN_INVALID_REVNUM, 962251881Speter NULL, NULL, 963362181Sdim &line, TRUE, iterpool)); 964251881Speter } 965251881Speter if (eof) break; 966251881Speter } 967251881Speter 968251881Speter if (walk_merged) 969251881Speter walk_merged = walk_merged->next; 970251881Speter } 971251881Speter 972251881Speter SVN_ERR(svn_stream_close(stream)); 973251881Speter 974251881Speter svn_pool_destroy(frb.lastpool); 975251881Speter svn_pool_destroy(frb.currpool); 976251881Speter if (include_merged_revisions) 977251881Speter { 978251881Speter svn_pool_destroy(frb.filepool); 979251881Speter svn_pool_destroy(frb.prevfilepool); 980251881Speter } 981251881Speter svn_pool_destroy(iterpool); 982251881Speter 983251881Speter return SVN_NO_ERROR; 984251881Speter} 985