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