blame.c revision 299742
11590Srgrimes/* 21590Srgrimes * blame.c: return blame messages 31590Srgrimes * 41590Srgrimes * ==================================================================== 51590Srgrimes * Licensed to the Apache Software Foundation (ASF) under one 61590Srgrimes * or more contributor license agreements. See the NOTICE file 71590Srgrimes * distributed with this work for additional information 81590Srgrimes * regarding copyright ownership. The ASF licenses this file 91590Srgrimes * to you under the Apache License, Version 2.0 (the 101590Srgrimes * "License"); you may not use this file except in compliance 111590Srgrimes * with the License. You may obtain a copy of the License at 121590Srgrimes * 131590Srgrimes * http://www.apache.org/licenses/LICENSE-2.0 141590Srgrimes * 151590Srgrimes * Unless required by applicable law or agreed to in writing, 161590Srgrimes * software distributed under the License is distributed on an 171590Srgrimes * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 181590Srgrimes * KIND, either express or implied. See the License for the 191590Srgrimes * specific language governing permissions and limitations 201590Srgrimes * under the License. 211590Srgrimes * ==================================================================== 221590Srgrimes */ 231590Srgrimes 241590Srgrimes#include <apr_pools.h> 251590Srgrimes 261590Srgrimes#include "client.h" 271590Srgrimes 281590Srgrimes#include "svn_client.h" 291590Srgrimes#include "svn_subst.h" 301590Srgrimes#include "svn_string.h" 311590Srgrimes#include "svn_error.h" 321590Srgrimes#include "svn_diff.h" 331590Srgrimes#include "svn_pools.h" 341590Srgrimes#include "svn_dirent_uri.h" 351590Srgrimes#include "svn_path.h" 361590Srgrimes#include "svn_props.h" 371590Srgrimes#include "svn_hash.h" 381590Srgrimes#include "svn_sorts.h" 3927422Scharnier 401590Srgrimes#include "private/svn_wc_private.h" 411590Srgrimes 421590Srgrimes#include "svn_private_config.h" 431590Srgrimes 441590Srgrimes#include <assert.h> 4527422Scharnier 4623694Speter/* The metadata associated with a particular revision. */ 4727422Scharnierstruct rev 481590Srgrimes{ 4999112Sobrien svn_revnum_t revision; /* the revision number */ 5099112Sobrien apr_hash_t *rev_props; /* the revision properties */ 511590Srgrimes /* Used for merge reporting. */ 521590Srgrimes const char *path; /* the absolute repository path */ 531590Srgrimes}; 541590Srgrimes 551590Srgrimes/* One chunk of blame */ 5695096Stjrstruct blame 571590Srgrimes{ 581590Srgrimes const struct rev *rev; /* the responsible revision */ 591590Srgrimes apr_off_t start; /* the starting diff-token (line) */ 6023694Speter struct blame *next; /* the next chunk */ 611590Srgrimes}; 621590Srgrimes 631590Srgrimes/* A chain of blame chunks */ 641590Srgrimesstruct blame_chain 651590Srgrimes{ 661590Srgrimes struct blame *blame; /* linked list of blame chunks */ 671590Srgrimes struct blame *avail; /* linked list of free blame chunks */ 681590Srgrimes struct apr_pool_t *pool; /* Allocate members from this pool. */ 691590Srgrimes}; 701590Srgrimes 711590Srgrimes/* The baton use for the diff output routine. */ 721590Srgrimesstruct diff_baton { 731590Srgrimes struct blame_chain *chain; 741590Srgrimes const struct rev *rev; 751590Srgrimes}; 761590Srgrimes 771590Srgrimes/* The baton used for a file revision. Lives the entire operation */ 781590Srgrimesstruct file_rev_baton { 791590Srgrimes svn_revnum_t start_rev, end_rev; 80102944Sdwmalone svn_boolean_t backwards; 811590Srgrimes const char *target; 821590Srgrimes svn_client_ctx_t *ctx; 831590Srgrimes const svn_diff_file_options_t *diff_options; 841590Srgrimes /* name of file containing the previous revision of the file */ 851590Srgrimes const char *last_filename; 861590Srgrimes struct rev *last_rev; /* the rev of the last modification */ 871590Srgrimes struct blame_chain *chain; /* the original blame chain. */ 8895650Smarkm const char *repos_root_url; /* To construct a url */ 8995650Smarkm apr_pool_t *mainpool; /* lives during the whole sequence of calls */ 901590Srgrimes apr_pool_t *lastpool; /* pool used during previous call */ 911590Srgrimes apr_pool_t *currpool; /* pool used during this call */ 921590Srgrimes 931590Srgrimes /* These are used for tracking merged revisions. */ 941590Srgrimes svn_boolean_t include_merged_revisions; 951590Srgrimes struct blame_chain *merged_chain; /* the merged blame chain. */ 961590Srgrimes /* name of file containing the previous merged revision of the file */ 971590Srgrimes const char *last_original_filename; 981590Srgrimes /* pools for files which may need to persist for more than one rev. */ 991590Srgrimes apr_pool_t *filepool; 1001590Srgrimes apr_pool_t *prevfilepool; 1011590Srgrimes 1021590Srgrimes svn_boolean_t check_mime_type; 10395650Smarkm 10495650Smarkm /* When blaming backwards we have to use the changes 1051590Srgrimes on the *next* revision, as the interesting change 10692920Simp happens when we move to the previous revision */ 10792920Simp svn_revnum_t last_revnum; 10892920Simp apr_hash_t *last_props; 10992920Simp}; 11092920Simp 11192920Simp/* The baton used by the txdelta window handler. Allocated per revision */ 11292920Simpstruct delta_baton { 11392920Simp /* Our underlying handler/baton that we wrap */ 11492920Simp svn_txdelta_window_handler_t wrapped_handler; 1151590Srgrimes void *wrapped_baton; 1161590Srgrimes struct file_rev_baton *file_rev_baton; 117102944Sdwmalone svn_stream_t *source_stream; /* the delta source */ 1181590Srgrimes const char *filename; 1191590Srgrimes svn_boolean_t is_merged_revision; 1201590Srgrimes struct rev *rev; /* the rev struct for the current revision */ 1211590Srgrimes}; 1221590Srgrimes 12395096Stjr 12495096Stjr 1251590Srgrimes 1261590Srgrimes/* Return a blame chunk associated with REV for a change starting 1271590Srgrimes at token START, and allocated in CHAIN->mainpool. */ 1281590Srgrimesstatic struct blame * 1291590Srgrimesblame_create(struct blame_chain *chain, 13024360Simp const struct rev *rev, 1311590Srgrimes apr_off_t start) 1321590Srgrimes{ 1331590Srgrimes struct blame *blame; 1341590Srgrimes if (chain->avail) 1351590Srgrimes { 1361590Srgrimes blame = chain->avail; 1371590Srgrimes chain->avail = blame->next; 1381590Srgrimes } 1391590Srgrimes else 1401590Srgrimes blame = apr_palloc(chain->pool, sizeof(*blame)); 1411590Srgrimes blame->rev = rev; 1421590Srgrimes blame->start = start; 1431590Srgrimes blame->next = NULL; 1441590Srgrimes return blame; 1451590Srgrimes} 1461590Srgrimes 1471590Srgrimes/* Destroy a blame chunk. */ 1481590Srgrimesstatic void 1491590Srgrimesblame_destroy(struct blame_chain *chain, 1501590Srgrimes struct blame *blame) 1511590Srgrimes{ 1521590Srgrimes blame->next = chain->avail; 1531590Srgrimes chain->avail = blame; 1541590Srgrimes} 1551590Srgrimes 1561590Srgrimes/* Return the blame chunk that contains token OFF, starting the search at 1571590Srgrimes BLAME. */ 1581590Srgrimesstatic struct blame * 1591590Srgrimesblame_find(struct blame *blame, apr_off_t off) 1601590Srgrimes{ 1611590Srgrimes struct blame *prev = NULL; 1621590Srgrimes while (blame) 1631590Srgrimes { 1641590Srgrimes if (blame->start > off) break; 1651590Srgrimes prev = blame; 1661590Srgrimes blame = blame->next; 1671590Srgrimes } 1681590Srgrimes return prev; 1691590Srgrimes} 1701590Srgrimes 1711590Srgrimes/* Shift the start-point of BLAME and all subsequence blame-chunks 1721590Srgrimes by ADJUST tokens */ 1731590Srgrimesstatic void 1741590Srgrimesblame_adjust(struct blame *blame, apr_off_t adjust) 1751590Srgrimes{ 1761590Srgrimes while (blame) 1771590Srgrimes { 1781590Srgrimes blame->start += adjust; 1791590Srgrimes blame = blame->next; 1801590Srgrimes } 1811590Srgrimes} 1821590Srgrimes 1831590Srgrimes/* Delete the blame associated with the region from token START to 1841590Srgrimes START + LENGTH */ 1851590Srgrimesstatic svn_error_t * 1861590Srgrimesblame_delete_range(struct blame_chain *chain, 1871590Srgrimes apr_off_t start, 1881590Srgrimes apr_off_t length) 1891590Srgrimes{ 1901590Srgrimes struct blame *first = blame_find(chain->blame, start); 1911590Srgrimes struct blame *last = blame_find(chain->blame, start + length); 1921590Srgrimes struct blame *tail = last->next; 1931590Srgrimes 1941590Srgrimes if (first != last) 1951590Srgrimes { 1961590Srgrimes struct blame *walk = first->next; 1971590Srgrimes while (walk != last) 1981590Srgrimes { 1991590Srgrimes struct blame *next = walk->next; 2001590Srgrimes blame_destroy(chain, walk); 2011590Srgrimes walk = next; 2021590Srgrimes } 2031590Srgrimes first->next = last; 2041590Srgrimes last->start = start; 2051590Srgrimes if (first->start == start) 2061590Srgrimes { 2071590Srgrimes *first = *last; 2081590Srgrimes blame_destroy(chain, last); 2091590Srgrimes last = first; 2101590Srgrimes } 2111590Srgrimes } 2121590Srgrimes 2131590Srgrimes if (tail && tail->start == last->start + length) 2141590Srgrimes { 2151590Srgrimes *last = *tail; 2161590Srgrimes blame_destroy(chain, tail); 2171590Srgrimes tail = last->next; 2181590Srgrimes } 2191590Srgrimes 2201590Srgrimes blame_adjust(tail, -length); 2211590Srgrimes 2221590Srgrimes return SVN_NO_ERROR; 2231590Srgrimes} 2241590Srgrimes 2251590Srgrimes/* Insert a chunk of blame associated with REV starting 2261590Srgrimes at token START and continuing for LENGTH tokens */ 2271590Srgrimesstatic svn_error_t * 2281590Srgrimesblame_insert_range(struct blame_chain *chain, 2291590Srgrimes const struct rev *rev, 2301590Srgrimes apr_off_t start, 2311590Srgrimes apr_off_t length) 2321590Srgrimes{ 2331590Srgrimes struct blame *head = chain->blame; 2341590Srgrimes struct blame *point = blame_find(head, start); 2351590Srgrimes struct blame *insert; 2361590Srgrimes 2371590Srgrimes if (point->start == start) 2381590Srgrimes { 2391590Srgrimes insert = blame_create(chain, point->rev, point->start + length); 2401590Srgrimes point->rev = rev; 2411590Srgrimes insert->next = point->next; 2421590Srgrimes point->next = insert; 2431590Srgrimes } 2441590Srgrimes else 2451590Srgrimes { 2461590Srgrimes struct blame *middle; 2471590Srgrimes middle = blame_create(chain, rev, start); 2481590Srgrimes insert = blame_create(chain, point->rev, start + length); 2491590Srgrimes middle->next = insert; 2501590Srgrimes insert->next = point->next; 2511590Srgrimes point->next = middle; 2521590Srgrimes } 2531590Srgrimes blame_adjust(insert->next, length); 2541590Srgrimes 2551590Srgrimes return SVN_NO_ERROR; 2561590Srgrimes} 2571590Srgrimes 2581590Srgrimes/* Callback for diff between subsequent revisions */ 2591590Srgrimesstatic svn_error_t * 2601590Srgrimesoutput_diff_modified(void *baton, 2611590Srgrimes apr_off_t original_start, 2621590Srgrimes apr_off_t original_length, 2631590Srgrimes apr_off_t modified_start, 2641590Srgrimes apr_off_t modified_length, 2651590Srgrimes apr_off_t latest_start, 2661590Srgrimes apr_off_t latest_length) 2671590Srgrimes{ 2681590Srgrimes struct diff_baton *db = baton; 2691590Srgrimes 2701590Srgrimes if (original_length) 271102944Sdwmalone SVN_ERR(blame_delete_range(db->chain, modified_start, original_length)); 2721590Srgrimes 2731590Srgrimes if (modified_length) 2741590Srgrimes SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start, 2751590Srgrimes modified_length)); 2761590Srgrimes 2771590Srgrimes return SVN_NO_ERROR; 2781590Srgrimes} 2791590Srgrimes 2801590Srgrimesstatic const svn_diff_output_fns_t output_fns = { 2811590Srgrimes NULL, 2821590Srgrimes output_diff_modified 28319069Sphk}; 2841590Srgrimes 2851590Srgrimes/* Add the blame for the diffs between LAST_FILE and CUR_FILE to CHAIN, 2861590Srgrimes for revision REV. LAST_FILE may be NULL in which 2871590Srgrimes case blame is added for every line of CUR_FILE. */ 2881590Srgrimesstatic svn_error_t * 2891590Srgrimesadd_file_blame(const char *last_file, 2901590Srgrimes const char *cur_file, 2911590Srgrimes struct blame_chain *chain, 2921590Srgrimes struct rev *rev, 2931590Srgrimes const svn_diff_file_options_t *diff_options, 2941590Srgrimes svn_cancel_func_t cancel_func, 2951590Srgrimes void *cancel_baton, 29623694Speter apr_pool_t *pool) 29723694Speter{ 29823694Speter if (!last_file) 29923694Speter { 3001590Srgrimes SVN_ERR_ASSERT(chain->blame == NULL); 3018874Srgrimes chain->blame = blame_create(chain, rev, 0); 3021590Srgrimes } 3031590Srgrimes else 3041590Srgrimes { 3051590Srgrimes svn_diff_t *diff; 3061590Srgrimes struct diff_baton diff_baton; 3071590Srgrimes 3081590Srgrimes diff_baton.chain = chain; 3091590Srgrimes diff_baton.rev = rev; 31019069Sphk 31119069Sphk /* We have a previous file. Get the diff and adjust blame info. */ 3121590Srgrimes SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file, 3131590Srgrimes diff_options, pool)); 3141590Srgrimes SVN_ERR(svn_diff_output2(diff, &diff_baton, &output_fns, 3151590Srgrimes cancel_func, cancel_baton)); 3161590Srgrimes } 3171590Srgrimes 3181590Srgrimes return SVN_NO_ERROR; 3191590Srgrimes} 3201590Srgrimes 3211590Srgrimes/* Record the blame information for the revision in BATON->file_rev_baton. 32223694Speter */ 3231590Srgrimesstatic svn_error_t * 3241590Srgrimesupdate_blame(void *baton) 3251590Srgrimes{ 3261590Srgrimes struct delta_baton *dbaton = baton; 3271590Srgrimes struct file_rev_baton *frb = dbaton->file_rev_baton; 3281590Srgrimes struct blame_chain *chain; 3291590Srgrimes 3301590Srgrimes /* Close the source file used for the delta. 3311590Srgrimes It is important to do this early, since otherwise, they will be deleted 3321590Srgrimes before all handles are closed, which leads to failures on some platforms 3331590Srgrimes when new tempfiles are to be created. */ 3341590Srgrimes if (dbaton->source_stream) 3351590Srgrimes SVN_ERR(svn_stream_close(dbaton->source_stream)); 3361590Srgrimes 3371590Srgrimes /* If we are including merged revisions, we need to add each rev to the 3381590Srgrimes merged chain. */ 3391590Srgrimes if (frb->include_merged_revisions) 3401590Srgrimes chain = frb->merged_chain; 3411590Srgrimes else 3421590Srgrimes chain = frb->chain; 3431590Srgrimes 3441590Srgrimes /* Process this file. */ 3451590Srgrimes SVN_ERR(add_file_blame(frb->last_filename, 3461590Srgrimes dbaton->filename, chain, dbaton->rev, 3471590Srgrimes frb->diff_options, 3481590Srgrimes frb->ctx->cancel_func, frb->ctx->cancel_baton, 3491590Srgrimes frb->currpool)); 3501590Srgrimes 3511590Srgrimes /* If we are including merged revisions, and the current revision is not a 3521590Srgrimes merged one, we need to add its blame info to the chain for the original 3531590Srgrimes line of history. */ 3541590Srgrimes if (frb->include_merged_revisions && ! dbaton->is_merged_revision) 3551590Srgrimes { 3561590Srgrimes apr_pool_t *tmppool; 3571590Srgrimes 3581590Srgrimes SVN_ERR(add_file_blame(frb->last_original_filename, 3591590Srgrimes dbaton->filename, frb->chain, dbaton->rev, 360102944Sdwmalone frb->diff_options, 3611590Srgrimes frb->ctx->cancel_func, frb->ctx->cancel_baton, 36221811Sjoerg frb->currpool)); 36323694Speter 36421811Sjoerg /* This filename could be around for a while, potentially, so 3651590Srgrimes use the longer lifetime pool, and switch it with the previous one*/ 36695096Stjr svn_pool_clear(frb->prevfilepool); 3671590Srgrimes tmppool = frb->filepool; 3681590Srgrimes frb->filepool = frb->prevfilepool; 3691590Srgrimes frb->prevfilepool = tmppool; 370102944Sdwmalone 3711590Srgrimes frb->last_original_filename = apr_pstrdup(frb->filepool, 372102944Sdwmalone dbaton->filename); 3731590Srgrimes } 3741590Srgrimes 3751590Srgrimes /* Prepare for next revision. */ 3761590Srgrimes 3771590Srgrimes /* Remember the file name so we can diff it with the next revision. */ 3781590Srgrimes frb->last_filename = dbaton->filename; 3791590Srgrimes 3801590Srgrimes /* Switch pools. */ 3811590Srgrimes { 3821590Srgrimes apr_pool_t *tmp_pool = frb->lastpool; 3831590Srgrimes frb->lastpool = frb->currpool; 3841590Srgrimes frb->currpool = tmp_pool; 3851590Srgrimes } 3861590Srgrimes 3871590Srgrimes return SVN_NO_ERROR; 3881590Srgrimes} 3891590Srgrimes 390102944Sdwmalone/* The delta window handler for the text delta between the previously seen 3911590Srgrimes * revision and the revision currently being handled. 392102944Sdwmalone * 3931590Srgrimes * Record the blame information for this revision in BATON->file_rev_baton. 3941590Srgrimes * 3951590Srgrimes * Implements svn_txdelta_window_handler_t. 3961590Srgrimes */ 3971590Srgrimesstatic svn_error_t * 3981590Srgrimeswindow_handler(svn_txdelta_window_t *window, void *baton) 3991590Srgrimes{ 4001590Srgrimes struct delta_baton *dbaton = baton; 40195650Smarkm 4021590Srgrimes /* Call the wrapped handler first. */ 40393193Sjmallett if (dbaton->wrapped_handler) 40493193Sjmallett SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton)); 4051590Srgrimes 4061590Srgrimes /* We patiently wait for the NULL window marking the end. */ 4071590Srgrimes if (window) 4081590Srgrimes return SVN_NO_ERROR; 4091590Srgrimes 4101590Srgrimes /* Diff and update blame info. */ 4111590Srgrimes SVN_ERR(update_blame(baton)); 4121590Srgrimes 4131590Srgrimes return SVN_NO_ERROR; 4141590Srgrimes} 4151590Srgrimes 4161590Srgrimes 4171590Srgrimes/* Calculate and record blame information for one revision of the file, 418102944Sdwmalone * by comparing the file content against the previously seen revision. 4191590Srgrimes * 420102944Sdwmalone * This handler is called once for each interesting revision of the file. 4211590Srgrimes * 4221590Srgrimes * Record the blame information for this revision in (file_rev_baton) BATON. 4231590Srgrimes * 4241590Srgrimes * Implements svn_file_rev_handler_t. 42593193Sjmallett */ 42693193Sjmallettstatic svn_error_t * 42793193Sjmallettfile_rev_handler(void *baton, const char *path, svn_revnum_t revnum, 42893193Sjmallett apr_hash_t *rev_props, 42993193Sjmallett svn_boolean_t merged_revision, 43093193Sjmallett svn_txdelta_window_handler_t *content_delta_handler, 4311590Srgrimes void **content_delta_baton, 4321590Srgrimes apr_array_header_t *prop_diffs, 4331590Srgrimes apr_pool_t *pool) 4341590Srgrimes{ 4351590Srgrimes struct file_rev_baton *frb = baton; 4361590Srgrimes svn_stream_t *last_stream; 4371590Srgrimes svn_stream_t *cur_stream; 4381590Srgrimes struct delta_baton *delta_baton; 4391590Srgrimes apr_pool_t *filepool; 4401590Srgrimes 4411590Srgrimes /* Clear the current pool. */ 4421590Srgrimes svn_pool_clear(frb->currpool); 4431590Srgrimes 4441590Srgrimes if (frb->check_mime_type) 4451590Srgrimes { 4461590Srgrimes apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool); 4471590Srgrimes const char *value; 4481590Srgrimes 4491590Srgrimes frb->check_mime_type = FALSE; /* Only check first */ 4501590Srgrimes 4511590Srgrimes value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); 4521590Srgrimes 4531590Srgrimes if (value && svn_mime_type_is_binary(value)) 454102944Sdwmalone { 4551590Srgrimes return svn_error_createf( 4561590Srgrimes SVN_ERR_CLIENT_IS_BINARY_FILE, NULL, 4571590Srgrimes _("Cannot calculate blame information for binary file '%s'"), 45848566Sbillf (svn_path_is_url(frb->target) 45921811Sjoerg ? frb->target 4601590Srgrimes : svn_dirent_local_style(frb->target, pool))); 4611590Srgrimes } 4621590Srgrimes } 4631590Srgrimes 4641590Srgrimes if (frb->ctx->notify_func2) 4651590Srgrimes { 4661590Srgrimes svn_wc_notify_t *notify 46748566Sbillf = svn_wc_create_notify_url( 4681590Srgrimes svn_path_url_add_component2(frb->repos_root_url, 4691590Srgrimes path+1, pool), 4701590Srgrimes svn_wc_notify_blame_revision, pool); 4711590Srgrimes notify->path = path; 4721590Srgrimes notify->kind = svn_node_none; 4731590Srgrimes notify->content_state = notify->prop_state 4741590Srgrimes = svn_wc_notify_state_inapplicable; 4751590Srgrimes notify->lock_state = svn_wc_notify_lock_state_inapplicable; 4761590Srgrimes notify->revision = revnum; 477102944Sdwmalone notify->rev_props = rev_props; 4781590Srgrimes frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool); 47993193Sjmallett } 4801590Srgrimes 4811590Srgrimes if (frb->ctx->cancel_func) 48223694Speter SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton)); 4831590Srgrimes 4841590Srgrimes /* If there were no content changes and no (potential) merges, we couldn't 48593193Sjmallett care less about this revision now. Note that we checked the mime type 48693193Sjmallett above, so things work if the user just changes the mime type in a commit. 48793193Sjmallett Also note that we don't switch the pools in this case. This is important, 48893193Sjmallett since the tempfile will be removed by the pool and we need the tempfile 48993193Sjmallett from the last revision with content changes. */ 49093193Sjmallett if (!content_delta_handler 49193193Sjmallett && (!frb->include_merged_revisions || merged_revision)) 49293193Sjmallett return SVN_NO_ERROR; 49393193Sjmallett 49493193Sjmallett /* Create delta baton. */ 49593193Sjmallett delta_baton = apr_pcalloc(frb->currpool, sizeof(*delta_baton)); 49693193Sjmallett 4971590Srgrimes /* Prepare the text delta window handler. */ 4981590Srgrimes if (frb->last_filename) 4991590Srgrimes SVN_ERR(svn_stream_open_readonly(&delta_baton->source_stream, frb->last_filename, 5001590Srgrimes frb->currpool, pool)); 5011590Srgrimes else 5021590Srgrimes /* Means empty stream below. */ 5031590Srgrimes delta_baton->source_stream = NULL; 50493193Sjmallett last_stream = svn_stream_disown(delta_baton->source_stream, pool); 50593193Sjmallett 5061590Srgrimes if (frb->include_merged_revisions && !merged_revision) 5071590Srgrimes filepool = frb->filepool; 5081590Srgrimes else 5091590Srgrimes filepool = frb->currpool; 5101590Srgrimes 511102944Sdwmalone SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL, 5121590Srgrimes svn_io_file_del_on_pool_cleanup, 513102944Sdwmalone filepool, filepool)); 5141590Srgrimes 5151590Srgrimes /* Wrap the window handler with our own. */ 5161590Srgrimes delta_baton->file_rev_baton = frb; 5171590Srgrimes delta_baton->is_merged_revision = merged_revision; 5181590Srgrimes 5191590Srgrimes /* Create the rev structure. */ 52028423Sjlemon delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev)); 52128423Sjlemon 52228423Sjlemon if (frb->backwards) 5231590Srgrimes { 5241590Srgrimes /* Use from last round... 5258874Srgrimes SVN_INVALID_REVNUM on first, which is exactly 5261590Srgrimes what we want */ 5271590Srgrimes delta_baton->rev->revision = frb->last_revnum; 5281590Srgrimes delta_baton->rev->rev_props = frb->last_props; 5291590Srgrimes 5301590Srgrimes /* Store for next delta */ 5311590Srgrimes if (revnum >= MIN(frb->start_rev, frb->end_rev)) 5321590Srgrimes { 5331590Srgrimes frb->last_revnum = revnum; 53495096Stjr frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool); 53595096Stjr } 53695096Stjr /* Else: Not needed on last rev */ 5371590Srgrimes } 53895096Stjr else if (merged_revision 53995096Stjr || (revnum >= MIN(frb->start_rev, frb->end_rev))) 54095096Stjr { 5411590Srgrimes /* 1+ for the "youngest to oldest" blame */ 5421590Srgrimes SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev)); 5431590Srgrimes 5441590Srgrimes /* Set values from revision props. */ 5451590Srgrimes delta_baton->rev->revision = revnum; 5461590Srgrimes delta_baton->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool); 5471590Srgrimes } 5481590Srgrimes else 5491590Srgrimes { 5501590Srgrimes /* We shouldn't get more than one revision outside the 5511590Srgrimes specified range (unless we alsoe receive merged revisions) */ 5521590Srgrimes SVN_ERR_ASSERT((frb->last_filename == NULL) 5531590Srgrimes || frb->include_merged_revisions); 5541590Srgrimes 5551590Srgrimes /* The file existed before start_rev; generate no blame info for 5561590Srgrimes lines from this revision (or before). 5571590Srgrimes 5581590Srgrimes This revision specifies the state as it was at the start revision */ 5591590Srgrimes 5601590Srgrimes delta_baton->rev->revision = SVN_INVALID_REVNUM; 5611590Srgrimes } 5621590Srgrimes 5631590Srgrimes if (frb->include_merged_revisions) 5641590Srgrimes delta_baton->rev->path = apr_pstrdup(frb->mainpool, path); 5651590Srgrimes 5661590Srgrimes /* Keep last revision for postprocessing after all changes */ 5671590Srgrimes frb->last_rev = delta_baton->rev; 5681590Srgrimes 5691590Srgrimes /* Handle all delta - even if it is empty. 5701590Srgrimes We must do the latter to "merge" blame info from other branches. */ 5711590Srgrimes if (content_delta_handler) 5721590Srgrimes { 5731590Srgrimes /* Proper delta - get window handler for applying delta. 5741590Srgrimes svn_ra_get_file_revs2 will drive the delta editor. */ 5751590Srgrimes svn_txdelta_apply(last_stream, cur_stream, NULL, NULL, 576102944Sdwmalone frb->currpool, 577102944Sdwmalone &delta_baton->wrapped_handler, 5781590Srgrimes &delta_baton->wrapped_baton); 5791590Srgrimes *content_delta_handler = window_handler; 5801590Srgrimes *content_delta_baton = delta_baton; 5811590Srgrimes } 5821590Srgrimes else 5831590Srgrimes { 5841590Srgrimes /* Apply an empty delta, i.e. simply copy the old contents. 5851590Srgrimes We can't simply use the existing file due to the pool rotation logic. 5861590Srgrimes Trigger the blame update magic. */ 5871590Srgrimes SVN_ERR(svn_stream_copy3(last_stream, cur_stream, NULL, NULL, pool)); 5881590Srgrimes SVN_ERR(update_blame(delta_baton)); 5891590Srgrimes } 5901590Srgrimes 5911590Srgrimes return SVN_NO_ERROR; 5921590Srgrimes} 5931590Srgrimes 5941590Srgrimes/* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks, 5951590Srgrimes and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the 596102944Sdwmalone same starting value. Both CHAIN_ORIG and CHAIN_MERGED should not be 5971590Srgrimes NULL. */ 59827422Scharnierstatic void 59927422Scharniernormalize_blames(struct blame_chain *chain, 60027422Scharnier struct blame_chain *chain_merged, 60127422Scharnier apr_pool_t *pool) 6021590Srgrimes{ 6031590Srgrimes 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