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" 37299742Sdim#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 77299742Sdim/* The baton used for a file revision. Lives the entire operation */ 78251881Speterstruct file_rev_baton { 79251881Speter svn_revnum_t start_rev, end_rev; 80299742Sdim 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; 86299742Sdim 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; 101299742Sdim 102299742Sdim svn_boolean_t check_mime_type; 103299742Sdim 104299742Sdim /* When blaming backwards we have to use the changes 105299742Sdim on the *next* revision, as the interesting change 106299742Sdim happens when we move to the previous revision */ 107299742Sdim svn_revnum_t last_revnum; 108299742Sdim apr_hash_t *last_props; 109251881Speter}; 110251881Speter 111299742Sdim/* 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; 117299742Sdim svn_stream_t *source_stream; /* the delta source */ 118251881Speter const char *filename; 119299742Sdim svn_boolean_t is_merged_revision; 120299742Sdim 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, 294299742Sdim svn_cancel_func_t cancel_func, 295299742Sdim 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)); 314299742Sdim SVN_ERR(svn_diff_output2(diff, &diff_baton, &output_fns, 315299742Sdim cancel_func, cancel_baton)); 316251881Speter } 317251881Speter 318251881Speter return SVN_NO_ERROR; 319251881Speter} 320251881Speter 321299742Sdim/* Record the blame information for the revision in BATON->file_rev_baton. 322251881Speter */ 323251881Speterstatic svn_error_t * 324299742Sdimupdate_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 330299742Sdim /* Close the source file used for the delta. 331299742Sdim It is important to do this early, since otherwise, they will be deleted 332299742Sdim before all handles are closed, which leads to failures on some platforms 333299742Sdim when new tempfiles are to be created. */ 334299742Sdim if (dbaton->source_stream) 335299742Sdim 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, 346299742Sdim dbaton->filename, chain, dbaton->rev, 347299742Sdim frb->diff_options, 348299742Sdim frb->ctx->cancel_func, frb->ctx->cancel_baton, 349299742Sdim 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. */ 354299742Sdim 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, 359299742Sdim dbaton->filename, frb->chain, dbaton->rev, 360299742Sdim frb->diff_options, 361299742Sdim frb->ctx->cancel_func, frb->ctx->cancel_baton, 362299742Sdim 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 390299742Sdim/* The delta window handler for the text delta between the previously seen 391299742Sdim * revision and the revision currently being handled. 392299742Sdim * 393299742Sdim * Record the blame information for this revision in BATON->file_rev_baton. 394299742Sdim * 395299742Sdim * Implements svn_txdelta_window_handler_t. 396299742Sdim */ 397299742Sdimstatic svn_error_t * 398299742Sdimwindow_handler(svn_txdelta_window_t *window, void *baton) 399299742Sdim{ 400299742Sdim struct delta_baton *dbaton = baton; 401251881Speter 402299742Sdim /* Call the wrapped handler first. */ 403299742Sdim if (dbaton->wrapped_handler) 404299742Sdim SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton)); 405299742Sdim 406299742Sdim /* We patiently wait for the NULL window marking the end. */ 407299742Sdim if (window) 408299742Sdim return SVN_NO_ERROR; 409299742Sdim 410299742Sdim /* Diff and update blame info. */ 411299742Sdim SVN_ERR(update_blame(baton)); 412299742Sdim 413299742Sdim return SVN_NO_ERROR; 414299742Sdim} 415299742Sdim 416299742Sdim 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 444299742Sdim if (frb->check_mime_type) 445299742Sdim { 446299742Sdim apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool); 447299742Sdim const char *value; 448299742Sdim 449299742Sdim frb->check_mime_type = FALSE; /* Only check first */ 450299742Sdim 451299742Sdim value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); 452299742Sdim 453299742Sdim if (value && svn_mime_type_is_binary(value)) 454299742Sdim { 455299742Sdim return svn_error_createf( 456299742Sdim SVN_ERR_CLIENT_IS_BINARY_FILE, NULL, 457299742Sdim _("Cannot calculate blame information for binary file '%s'"), 458299742Sdim (svn_path_is_url(frb->target) 459299742Sdim ? frb->target 460299742Sdim : svn_dirent_local_style(frb->target, pool))); 461299742Sdim } 462299742Sdim } 463299742Sdim 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 484299742Sdim /* If there were no content changes and no (potential) merges, we couldn't 485299742Sdim care less about this revision now. Note that we checked the mime type 486299742Sdim 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. */ 490299742Sdim if (!content_delta_handler 491299742Sdim && (!frb->include_merged_revisions || merged_revision)) 492251881Speter return SVN_NO_ERROR; 493251881Speter 494251881Speter /* Create delta baton. */ 495299742Sdim delta_baton = apr_pcalloc(frb->currpool, sizeof(*delta_baton)); 496251881Speter 497251881Speter /* Prepare the text delta window handler. */ 498251881Speter if (frb->last_filename) 499299742Sdim SVN_ERR(svn_stream_open_readonly(&delta_baton->source_stream, frb->last_filename, 500251881Speter frb->currpool, pool)); 501251881Speter else 502299742Sdim /* Means empty stream below. */ 503299742Sdim delta_baton->source_stream = NULL; 504299742Sdim last_stream = svn_stream_disown(delta_baton->source_stream, pool); 505251881Speter 506299742Sdim 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, 513299742Sdim filepool, filepool)); 514251881Speter 515251881Speter /* Wrap the window handler with our own. */ 516251881Speter delta_baton->file_rev_baton = frb; 517299742Sdim delta_baton->is_merged_revision = merged_revision; 518251881Speter 519251881Speter /* Create the rev structure. */ 520299742Sdim delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev)); 521251881Speter 522299742Sdim if (frb->backwards) 523251881Speter { 524299742Sdim /* Use from last round... 525299742Sdim SVN_INVALID_REVNUM on first, which is exactly 526299742Sdim what we want */ 527299742Sdim delta_baton->rev->revision = frb->last_revnum; 528299742Sdim delta_baton->rev->rev_props = frb->last_props; 529299742Sdim 530299742Sdim /* Store for next delta */ 531299742Sdim if (revnum >= MIN(frb->start_rev, frb->end_rev)) 532299742Sdim { 533299742Sdim frb->last_revnum = revnum; 534299742Sdim frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool); 535299742Sdim } 536299742Sdim /* Else: Not needed on last rev */ 537299742Sdim } 538299742Sdim else if (merged_revision 539299742Sdim || (revnum >= MIN(frb->start_rev, frb->end_rev))) 540299742Sdim { 541299742Sdim /* 1+ for the "youngest to oldest" blame */ 542299742Sdim SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev)); 543299742Sdim 544299742Sdim /* Set values from revision props. */ 545299742Sdim delta_baton->rev->revision = revnum; 546299742Sdim delta_baton->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool); 547299742Sdim } 548299742Sdim else 549299742Sdim { 550299742Sdim /* We shouldn't get more than one revision outside the 551299742Sdim 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 556299742Sdim lines from this revision (or before). 557299742Sdim 558299742Sdim This revision specifies the state as it was at the start revision */ 559299742Sdim 560299742Sdim delta_baton->rev->revision = SVN_INVALID_REVNUM; 561251881Speter } 562299742Sdim 563299742Sdim if (frb->include_merged_revisions) 564299742Sdim delta_baton->rev->path = apr_pstrdup(frb->mainpool, path); 565299742Sdim 566299742Sdim /* Keep last revision for postprocessing after all changes */ 567299742Sdim frb->last_rev = delta_baton->rev; 568299742Sdim 569299742Sdim /* Handle all delta - even if it is empty. 570299742Sdim We must do the latter to "merge" blame info from other branches. */ 571299742Sdim if (content_delta_handler) 572299742Sdim { 573299742Sdim /* Proper delta - get window handler for applying delta. 574299742Sdim svn_ra_get_file_revs2 will drive the delta editor. */ 575299742Sdim svn_txdelta_apply(last_stream, cur_stream, NULL, NULL, 576299742Sdim frb->currpool, 577299742Sdim &delta_baton->wrapped_handler, 578299742Sdim &delta_baton->wrapped_baton); 579299742Sdim *content_delta_handler = window_handler; 580299742Sdim *content_delta_baton = delta_baton; 581299742Sdim } 582251881Speter else 583251881Speter { 584299742Sdim /* Apply an empty delta, i.e. simply copy the old contents. 585299742Sdim We can't simply use the existing file due to the pool rotation logic. 586299742Sdim Trigger the blame update magic. */ 587299742Sdim SVN_ERR(svn_stream_copy3(last_stream, cur_stream, NULL, NULL, pool)); 588299742Sdim 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 * 659251881Spetersvn_client_blame5(const char *target, 660251881Speter const svn_opt_revision_t *peg_revision, 661251881Speter const svn_opt_revision_t *start, 662251881Speter const svn_opt_revision_t *end, 663251881Speter const svn_diff_file_options_t *diff_options, 664251881Speter svn_boolean_t ignore_mime_type, 665251881Speter svn_boolean_t include_merged_revisions, 666251881Speter svn_client_blame_receiver3_t receiver, 667251881Speter void *receiver_baton, 668251881Speter svn_client_ctx_t *ctx, 669251881Speter apr_pool_t *pool) 670251881Speter{ 671251881Speter struct file_rev_baton frb; 672251881Speter svn_ra_session_t *ra_session; 673251881Speter svn_revnum_t start_revnum, end_revnum; 674251881Speter struct blame *walk, *walk_merged = NULL; 675251881Speter apr_pool_t *iterpool; 676251881Speter svn_stream_t *last_stream; 677251881Speter svn_stream_t *stream; 678251881Speter const char *target_abspath_or_url; 679251881Speter 680251881Speter if (start->kind == svn_opt_revision_unspecified 681251881Speter || end->kind == svn_opt_revision_unspecified) 682251881Speter return svn_error_create 683251881Speter (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); 684251881Speter 685251881Speter if (svn_path_is_url(target)) 686251881Speter target_abspath_or_url = target; 687251881Speter else 688251881Speter SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool)); 689251881Speter 690251881Speter /* Get an RA plugin for this filesystem object. */ 691299742Sdim SVN_ERR(svn_client__ra_session_from_path2(&ra_session, NULL, 692299742Sdim target, NULL, peg_revision, 693299742Sdim peg_revision, 694251881Speter ctx, pool)); 695251881Speter 696251881Speter SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx, 697251881Speter target_abspath_or_url, ra_session, 698251881Speter start, pool)); 699251881Speter 700299742Sdim SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx, 701299742Sdim target_abspath_or_url, ra_session, 702299742Sdim end, pool)); 703251881Speter 704299742Sdim { 705299742Sdim svn_client__pathrev_t *loc; 706299742Sdim svn_opt_revision_t younger_end; 707299742Sdim younger_end.kind = svn_opt_revision_number; 708299742Sdim younger_end.value.number = MAX(start_revnum, end_revnum); 709299742Sdim 710299742Sdim SVN_ERR(svn_client__resolve_rev_and_url(&loc, ra_session, 711299742Sdim target, peg_revision, 712299742Sdim &younger_end, 713299742Sdim ctx, pool)); 714299742Sdim 715299742Sdim /* Make the session point to the real URL. */ 716299742Sdim SVN_ERR(svn_ra_reparent(ra_session, loc->url, pool)); 717299742Sdim } 718299742Sdim 719251881Speter /* We check the mime-type of the yougest revision before getting all 720251881Speter the older revisions. */ 721299742Sdim if (!ignore_mime_type 722299742Sdim && start_revnum < end_revnum) 723251881Speter { 724251881Speter apr_hash_t *props; 725299742Sdim const char *mime_type = NULL; 726251881Speter 727299742Sdim if (svn_path_is_url(target) 728299742Sdim || start_revnum > end_revnum 729299742Sdim || (end->kind != svn_opt_revision_working 730299742Sdim && end->kind != svn_opt_revision_base)) 731299742Sdim { 732299742Sdim SVN_ERR(svn_ra_get_file(ra_session, "", end_revnum, NULL, NULL, 733299742Sdim &props, pool)); 734251881Speter 735299742Sdim mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); 736299742Sdim } 737299742Sdim else 738251881Speter { 739299742Sdim const svn_string_t *value; 740251881Speter 741299742Sdim if (end->kind == svn_opt_revision_working) 742299742Sdim SVN_ERR(svn_wc_prop_get2(&value, ctx->wc_ctx, 743299742Sdim target_abspath_or_url, 744299742Sdim SVN_PROP_MIME_TYPE, 745299742Sdim pool, pool)); 746299742Sdim else 747299742Sdim { 748299742Sdim SVN_ERR(svn_wc_get_pristine_props(&props, ctx->wc_ctx, 749299742Sdim target_abspath_or_url, 750299742Sdim pool, pool)); 751251881Speter 752299742Sdim value = props ? svn_hash_gets(props, SVN_PROP_MIME_TYPE) 753299742Sdim : NULL; 754299742Sdim } 755299742Sdim 756299742Sdim mime_type = value ? value->data : NULL; 757299742Sdim } 758299742Sdim 759299742Sdim if (mime_type) 760299742Sdim { 761299742Sdim if (svn_mime_type_is_binary(mime_type)) 762251881Speter return svn_error_createf 763251881Speter (SVN_ERR_CLIENT_IS_BINARY_FILE, 0, 764251881Speter _("Cannot calculate blame information for binary file '%s'"), 765251881Speter (svn_path_is_url(target) 766251881Speter ? target : svn_dirent_local_style(target, pool))); 767251881Speter } 768251881Speter } 769251881Speter 770251881Speter frb.start_rev = start_revnum; 771251881Speter frb.end_rev = end_revnum; 772251881Speter frb.target = target; 773251881Speter frb.ctx = ctx; 774251881Speter frb.diff_options = diff_options; 775251881Speter frb.include_merged_revisions = include_merged_revisions; 776251881Speter frb.last_filename = NULL; 777299742Sdim frb.last_rev = NULL; 778251881Speter frb.last_original_filename = NULL; 779251881Speter frb.chain = apr_palloc(pool, sizeof(*frb.chain)); 780251881Speter frb.chain->blame = NULL; 781251881Speter frb.chain->avail = NULL; 782251881Speter frb.chain->pool = pool; 783251881Speter if (include_merged_revisions) 784251881Speter { 785251881Speter frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain)); 786251881Speter frb.merged_chain->blame = NULL; 787251881Speter frb.merged_chain->avail = NULL; 788251881Speter frb.merged_chain->pool = pool; 789251881Speter } 790299742Sdim frb.backwards = (frb.start_rev > frb.end_rev); 791299742Sdim frb.last_revnum = SVN_INVALID_REVNUM; 792299742Sdim frb.last_props = NULL; 793299742Sdim frb.check_mime_type = (frb.backwards && !ignore_mime_type); 794251881Speter 795251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool)); 796251881Speter 797251881Speter frb.mainpool = pool; 798251881Speter /* The callback will flip the following two pools, because it needs 799251881Speter information from the previous call. Obviously, it can't rely on 800251881Speter the lifetime of the pool provided by get_file_revs. */ 801251881Speter frb.lastpool = svn_pool_create(pool); 802251881Speter frb.currpool = svn_pool_create(pool); 803251881Speter if (include_merged_revisions) 804251881Speter { 805251881Speter frb.filepool = svn_pool_create(pool); 806251881Speter frb.prevfilepool = svn_pool_create(pool); 807251881Speter } 808251881Speter 809251881Speter /* Collect all blame information. 810251881Speter We need to ensure that we get one revision before the start_rev, 811251881Speter if available so that we can know what was actually changed in the start 812251881Speter revision. */ 813251881Speter SVN_ERR(svn_ra_get_file_revs2(ra_session, "", 814299742Sdim frb.backwards ? start_revnum 815299742Sdim : MAX(0, start_revnum-1), 816299742Sdim end_revnum, 817299742Sdim include_merged_revisions, 818251881Speter file_rev_handler, &frb, pool)); 819251881Speter 820251881Speter if (end->kind == svn_opt_revision_working) 821251881Speter { 822251881Speter /* If the local file is modified we have to call the handler on the 823251881Speter working copy file with keywords unexpanded */ 824251881Speter svn_wc_status3_t *status; 825251881Speter 826251881Speter SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool, 827251881Speter pool)); 828251881Speter 829251881Speter if (status->text_status != svn_wc_status_normal 830251881Speter || (status->prop_status != svn_wc_status_normal 831251881Speter && status->prop_status != svn_wc_status_none)) 832251881Speter { 833251881Speter svn_stream_t *wcfile; 834251881Speter svn_stream_t *tempfile; 835251881Speter svn_opt_revision_t rev; 836251881Speter svn_boolean_t normalize_eols = FALSE; 837251881Speter const char *temppath; 838251881Speter 839251881Speter if (status->prop_status != svn_wc_status_none) 840251881Speter { 841251881Speter const svn_string_t *eol_style; 842251881Speter SVN_ERR(svn_wc_prop_get2(&eol_style, ctx->wc_ctx, 843251881Speter target_abspath_or_url, 844251881Speter SVN_PROP_EOL_STYLE, 845251881Speter pool, pool)); 846251881Speter 847251881Speter if (eol_style) 848251881Speter { 849251881Speter svn_subst_eol_style_t style; 850251881Speter const char *eol; 851251881Speter svn_subst_eol_style_from_value(&style, &eol, eol_style->data); 852251881Speter 853251881Speter normalize_eols = (style == svn_subst_eol_style_native); 854251881Speter } 855251881Speter } 856251881Speter 857251881Speter rev.kind = svn_opt_revision_working; 858251881Speter SVN_ERR(svn_client__get_normalized_stream(&wcfile, ctx->wc_ctx, 859251881Speter target_abspath_or_url, &rev, 860251881Speter FALSE, normalize_eols, 861251881Speter ctx->cancel_func, 862251881Speter ctx->cancel_baton, 863251881Speter pool, pool)); 864251881Speter 865251881Speter SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL, 866251881Speter svn_io_file_del_on_pool_cleanup, 867251881Speter pool, pool)); 868251881Speter 869251881Speter SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func, 870251881Speter ctx->cancel_baton, pool)); 871251881Speter 872251881Speter SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL, 873299742Sdim frb.diff_options, 874299742Sdim ctx->cancel_func, ctx->cancel_baton, pool)); 875251881Speter 876251881Speter frb.last_filename = temppath; 877251881Speter } 878251881Speter } 879251881Speter 880251881Speter /* Report the blame to the caller. */ 881251881Speter 882251881Speter /* The callback has to have been called at least once. */ 883251881Speter SVN_ERR_ASSERT(frb.last_filename != NULL); 884251881Speter 885251881Speter /* Create a pool for the iteration below. */ 886251881Speter iterpool = svn_pool_create(pool); 887251881Speter 888251881Speter /* Open the last file and get a stream. */ 889251881Speter SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename, 890251881Speter pool, pool)); 891251881Speter stream = svn_subst_stream_translated(last_stream, 892251881Speter "\n", TRUE, NULL, FALSE, pool); 893251881Speter 894251881Speter /* Perform optional merged chain normalization. */ 895251881Speter if (include_merged_revisions) 896251881Speter { 897251881Speter /* If we never created any blame for the original chain, create it now, 898251881Speter with the most recent changed revision. This could occur if a file 899251881Speter was created on a branch and them merged to another branch. This is 900251881Speter semanticly a copy, and we want to use the revision on the branch as 901251881Speter the most recently changed revision. ### Is this really what we want 902251881Speter to do here? Do the sematics of copy change? */ 903251881Speter if (!frb.chain->blame) 904299742Sdim frb.chain->blame = blame_create(frb.chain, frb.last_rev, 0); 905251881Speter 906251881Speter normalize_blames(frb.chain, frb.merged_chain, pool); 907251881Speter walk_merged = frb.merged_chain->blame; 908251881Speter } 909251881Speter 910251881Speter /* Process each blame item. */ 911251881Speter for (walk = frb.chain->blame; walk; walk = walk->next) 912251881Speter { 913251881Speter apr_off_t line_no; 914251881Speter svn_revnum_t merged_rev; 915251881Speter const char *merged_path; 916251881Speter apr_hash_t *merged_rev_props; 917251881Speter 918251881Speter if (walk_merged) 919251881Speter { 920251881Speter merged_rev = walk_merged->rev->revision; 921251881Speter merged_rev_props = walk_merged->rev->rev_props; 922251881Speter merged_path = walk_merged->rev->path; 923251881Speter } 924251881Speter else 925251881Speter { 926251881Speter merged_rev = SVN_INVALID_REVNUM; 927251881Speter merged_rev_props = NULL; 928251881Speter merged_path = NULL; 929251881Speter } 930251881Speter 931251881Speter for (line_no = walk->start; 932251881Speter !walk->next || line_no < walk->next->start; 933251881Speter ++line_no) 934251881Speter { 935251881Speter svn_boolean_t eof; 936251881Speter svn_stringbuf_t *sb; 937251881Speter 938251881Speter svn_pool_clear(iterpool); 939251881Speter SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool)); 940251881Speter if (ctx->cancel_func) 941251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 942251881Speter if (!eof || sb->len) 943251881Speter { 944251881Speter if (walk->rev) 945251881Speter SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, 946251881Speter line_no, walk->rev->revision, 947251881Speter walk->rev->rev_props, merged_rev, 948251881Speter merged_rev_props, merged_path, 949251881Speter sb->data, FALSE, iterpool)); 950251881Speter else 951251881Speter SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, 952251881Speter line_no, SVN_INVALID_REVNUM, 953251881Speter NULL, SVN_INVALID_REVNUM, 954251881Speter NULL, NULL, 955251881Speter sb->data, TRUE, iterpool)); 956251881Speter } 957251881Speter if (eof) break; 958251881Speter } 959251881Speter 960251881Speter if (walk_merged) 961251881Speter walk_merged = walk_merged->next; 962251881Speter } 963251881Speter 964251881Speter SVN_ERR(svn_stream_close(stream)); 965251881Speter 966251881Speter svn_pool_destroy(frb.lastpool); 967251881Speter svn_pool_destroy(frb.currpool); 968251881Speter if (include_merged_revisions) 969251881Speter { 970251881Speter svn_pool_destroy(frb.filepool); 971251881Speter svn_pool_destroy(frb.prevfilepool); 972251881Speter } 973251881Speter svn_pool_destroy(iterpool); 974251881Speter 975251881Speter return SVN_NO_ERROR; 976251881Speter} 977