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