1/*
2 * blame.c:  return blame messages
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24#include <apr_pools.h>
25
26#include "client.h"
27
28#include "svn_client.h"
29#include "svn_subst.h"
30#include "svn_string.h"
31#include "svn_error.h"
32#include "svn_diff.h"
33#include "svn_pools.h"
34#include "svn_dirent_uri.h"
35#include "svn_path.h"
36#include "svn_props.h"
37#include "svn_hash.h"
38#include "svn_sorts.h"
39
40#include "private/svn_wc_private.h"
41
42#include "svn_private_config.h"
43
44#include <assert.h>
45
46/* The metadata associated with a particular revision. */
47struct rev
48{
49  svn_revnum_t revision; /* the revision number */
50  apr_hash_t *rev_props; /* the revision properties */
51  /* Used for merge reporting. */
52  const char *path;      /* the absolute repository path */
53};
54
55/* One chunk of blame */
56struct blame
57{
58  const struct rev *rev;    /* the responsible revision */
59  apr_off_t start;          /* the starting diff-token (line) */
60  struct blame *next;       /* the next chunk */
61};
62
63/* A chain of blame chunks */
64struct blame_chain
65{
66  struct blame *blame;      /* linked list of blame chunks */
67  struct blame *avail;      /* linked list of free blame chunks */
68  struct apr_pool_t *pool;  /* Allocate members from this pool. */
69};
70
71/* The baton use for the diff output routine. */
72struct diff_baton {
73  struct blame_chain *chain;
74  const struct rev *rev;
75};
76
77/* The baton used for a file revision. Lives the entire operation */
78struct file_rev_baton {
79  svn_revnum_t start_rev, end_rev;
80  svn_boolean_t backwards;
81  const char *target;
82  svn_client_ctx_t *ctx;
83  const svn_diff_file_options_t *diff_options;
84  /* name of file containing the previous revision of the file */
85  const char *last_filename;
86  struct rev *last_rev;   /* the rev of the last modification */
87  struct blame_chain *chain;      /* the original blame chain. */
88  const char *repos_root_url;    /* To construct a url */
89  apr_pool_t *mainpool;  /* lives during the whole sequence of calls */
90  apr_pool_t *lastpool;  /* pool used during previous call */
91  apr_pool_t *currpool;  /* pool used during this call */
92
93  /* These are used for tracking merged revisions. */
94  svn_boolean_t include_merged_revisions;
95  struct blame_chain *merged_chain;  /* the merged blame chain. */
96  /* name of file containing the previous merged revision of the file */
97  const char *last_original_filename;
98  /* pools for files which may need to persist for more than one rev. */
99  apr_pool_t *filepool;
100  apr_pool_t *prevfilepool;
101
102  svn_boolean_t check_mime_type;
103
104  /* When blaming backwards we have to use the changes
105     on the *next* revision, as the interesting change
106     happens when we move to the previous revision */
107  svn_revnum_t last_revnum;
108  apr_hash_t *last_props;
109};
110
111/* The baton used by the txdelta window handler. Allocated per revision */
112struct delta_baton {
113  /* Our underlying handler/baton that we wrap */
114  svn_txdelta_window_handler_t wrapped_handler;
115  void *wrapped_baton;
116  struct file_rev_baton *file_rev_baton;
117  svn_stream_t *source_stream;  /* the delta source */
118  const char *filename;
119  svn_boolean_t is_merged_revision;
120  struct rev *rev;     /* the rev struct for the current revision */
121};
122
123
124
125
126/* Return a blame chunk associated with REV for a change starting
127   at token START, and allocated in CHAIN->mainpool. */
128static struct blame *
129blame_create(struct blame_chain *chain,
130             const struct rev *rev,
131             apr_off_t start)
132{
133  struct blame *blame;
134  if (chain->avail)
135    {
136      blame = chain->avail;
137      chain->avail = blame->next;
138    }
139  else
140    blame = apr_palloc(chain->pool, sizeof(*blame));
141  blame->rev = rev;
142  blame->start = start;
143  blame->next = NULL;
144  return blame;
145}
146
147/* Destroy a blame chunk. */
148static void
149blame_destroy(struct blame_chain *chain,
150              struct blame *blame)
151{
152  blame->next = chain->avail;
153  chain->avail = blame;
154}
155
156/* Return the blame chunk that contains token OFF, starting the search at
157   BLAME. */
158static struct blame *
159blame_find(struct blame *blame, apr_off_t off)
160{
161  struct blame *prev = NULL;
162  while (blame)
163    {
164      if (blame->start > off) break;
165      prev = blame;
166      blame = blame->next;
167    }
168  return prev;
169}
170
171/* Shift the start-point of BLAME and all subsequence blame-chunks
172   by ADJUST tokens */
173static void
174blame_adjust(struct blame *blame, apr_off_t adjust)
175{
176  while (blame)
177    {
178      blame->start += adjust;
179      blame = blame->next;
180    }
181}
182
183/* Delete the blame associated with the region from token START to
184   START + LENGTH */
185static svn_error_t *
186blame_delete_range(struct blame_chain *chain,
187                   apr_off_t start,
188                   apr_off_t length)
189{
190  struct blame *first = blame_find(chain->blame, start);
191  struct blame *last = blame_find(chain->blame, start + length);
192  struct blame *tail = last->next;
193
194  if (first != last)
195    {
196      struct blame *walk = first->next;
197      while (walk != last)
198        {
199          struct blame *next = walk->next;
200          blame_destroy(chain, walk);
201          walk = next;
202        }
203      first->next = last;
204      last->start = start;
205      if (first->start == start)
206        {
207          *first = *last;
208          blame_destroy(chain, last);
209          last = first;
210        }
211    }
212
213  if (tail && tail->start == last->start + length)
214    {
215      *last = *tail;
216      blame_destroy(chain, tail);
217      tail = last->next;
218    }
219
220  blame_adjust(tail, -length);
221
222  return SVN_NO_ERROR;
223}
224
225/* Insert a chunk of blame associated with REV starting
226   at token START and continuing for LENGTH tokens */
227static svn_error_t *
228blame_insert_range(struct blame_chain *chain,
229                   const struct rev *rev,
230                   apr_off_t start,
231                   apr_off_t length)
232{
233  struct blame *head = chain->blame;
234  struct blame *point = blame_find(head, start);
235  struct blame *insert;
236
237  if (point->start == start)
238    {
239      insert = blame_create(chain, point->rev, point->start + length);
240      point->rev = rev;
241      insert->next = point->next;
242      point->next = insert;
243    }
244  else
245    {
246      struct blame *middle;
247      middle = blame_create(chain, rev, start);
248      insert = blame_create(chain, point->rev, start + length);
249      middle->next = insert;
250      insert->next = point->next;
251      point->next = middle;
252    }
253  blame_adjust(insert->next, length);
254
255  return SVN_NO_ERROR;
256}
257
258/* Callback for diff between subsequent revisions */
259static svn_error_t *
260output_diff_modified(void *baton,
261                     apr_off_t original_start,
262                     apr_off_t original_length,
263                     apr_off_t modified_start,
264                     apr_off_t modified_length,
265                     apr_off_t latest_start,
266                     apr_off_t latest_length)
267{
268  struct diff_baton *db = baton;
269
270  if (original_length)
271    SVN_ERR(blame_delete_range(db->chain, modified_start, original_length));
272
273  if (modified_length)
274    SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start,
275                               modified_length));
276
277  return SVN_NO_ERROR;
278}
279
280static const svn_diff_output_fns_t output_fns = {
281        NULL,
282        output_diff_modified
283};
284
285/* Add the blame for the diffs between LAST_FILE and CUR_FILE to CHAIN,
286   for revision REV.  LAST_FILE may be NULL in which
287   case blame is added for every line of CUR_FILE. */
288static svn_error_t *
289add_file_blame(const char *last_file,
290               const char *cur_file,
291               struct blame_chain *chain,
292               struct rev *rev,
293               const svn_diff_file_options_t *diff_options,
294               svn_cancel_func_t cancel_func,
295               void *cancel_baton,
296               apr_pool_t *pool)
297{
298  if (!last_file)
299    {
300      SVN_ERR_ASSERT(chain->blame == NULL);
301      chain->blame = blame_create(chain, rev, 0);
302    }
303  else
304    {
305      svn_diff_t *diff;
306      struct diff_baton diff_baton;
307
308      diff_baton.chain = chain;
309      diff_baton.rev = rev;
310
311      /* We have a previous file.  Get the diff and adjust blame info. */
312      SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file,
313                                   diff_options, pool));
314      SVN_ERR(svn_diff_output2(diff, &diff_baton, &output_fns,
315                               cancel_func, cancel_baton));
316    }
317
318  return SVN_NO_ERROR;
319}
320
321/* Record the blame information for the revision in BATON->file_rev_baton.
322 */
323static svn_error_t *
324update_blame(void *baton)
325{
326  struct delta_baton *dbaton = baton;
327  struct file_rev_baton *frb = dbaton->file_rev_baton;
328  struct blame_chain *chain;
329
330  /* Close the source file used for the delta.
331     It is important to do this early, since otherwise, they will be deleted
332     before all handles are closed, which leads to failures on some platforms
333     when new tempfiles are to be created. */
334  if (dbaton->source_stream)
335    SVN_ERR(svn_stream_close(dbaton->source_stream));
336
337  /* If we are including merged revisions, we need to add each rev to the
338     merged chain. */
339  if (frb->include_merged_revisions)
340    chain = frb->merged_chain;
341  else
342    chain = frb->chain;
343
344  /* Process this file. */
345  SVN_ERR(add_file_blame(frb->last_filename,
346                         dbaton->filename, chain, dbaton->rev,
347                         frb->diff_options,
348                         frb->ctx->cancel_func, frb->ctx->cancel_baton,
349                         frb->currpool));
350
351  /* If we are including merged revisions, and the current revision is not a
352     merged one, we need to add its blame info to the chain for the original
353     line of history. */
354  if (frb->include_merged_revisions && ! dbaton->is_merged_revision)
355    {
356      apr_pool_t *tmppool;
357
358      SVN_ERR(add_file_blame(frb->last_original_filename,
359                             dbaton->filename, frb->chain, dbaton->rev,
360                             frb->diff_options,
361                             frb->ctx->cancel_func, frb->ctx->cancel_baton,
362                             frb->currpool));
363
364      /* This filename could be around for a while, potentially, so
365         use the longer lifetime pool, and switch it with the previous one*/
366      svn_pool_clear(frb->prevfilepool);
367      tmppool = frb->filepool;
368      frb->filepool = frb->prevfilepool;
369      frb->prevfilepool = tmppool;
370
371      frb->last_original_filename = apr_pstrdup(frb->filepool,
372                                                dbaton->filename);
373    }
374
375  /* Prepare for next revision. */
376
377  /* Remember the file name so we can diff it with the next revision. */
378  frb->last_filename = dbaton->filename;
379
380  /* Switch pools. */
381  {
382    apr_pool_t *tmp_pool = frb->lastpool;
383    frb->lastpool = frb->currpool;
384    frb->currpool = tmp_pool;
385  }
386
387  return SVN_NO_ERROR;
388}
389
390/* The delta window handler for the text delta between the previously seen
391 * revision and the revision currently being handled.
392 *
393 * Record the blame information for this revision in BATON->file_rev_baton.
394 *
395 * Implements svn_txdelta_window_handler_t.
396 */
397static svn_error_t *
398window_handler(svn_txdelta_window_t *window, void *baton)
399{
400  struct delta_baton *dbaton = baton;
401
402  /* Call the wrapped handler first. */
403  if (dbaton->wrapped_handler)
404    SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton));
405
406  /* We patiently wait for the NULL window marking the end. */
407  if (window)
408    return SVN_NO_ERROR;
409
410  /* Diff and update blame info. */
411  SVN_ERR(update_blame(baton));
412
413  return SVN_NO_ERROR;
414}
415
416
417/* Calculate and record blame information for one revision of the file,
418 * by comparing the file content against the previously seen revision.
419 *
420 * This handler is called once for each interesting revision of the file.
421 *
422 * Record the blame information for this revision in (file_rev_baton) BATON.
423 *
424 * Implements svn_file_rev_handler_t.
425 */
426static svn_error_t *
427file_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
428                 apr_hash_t *rev_props,
429                 svn_boolean_t merged_revision,
430                 svn_txdelta_window_handler_t *content_delta_handler,
431                 void **content_delta_baton,
432                 apr_array_header_t *prop_diffs,
433                 apr_pool_t *pool)
434{
435  struct file_rev_baton *frb = baton;
436  svn_stream_t *last_stream;
437  svn_stream_t *cur_stream;
438  struct delta_baton *delta_baton;
439  apr_pool_t *filepool;
440
441  /* Clear the current pool. */
442  svn_pool_clear(frb->currpool);
443
444  if (frb->check_mime_type)
445    {
446      apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool);
447      const char *value;
448
449      frb->check_mime_type = FALSE; /* Only check first */
450
451      value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
452
453      if (value && svn_mime_type_is_binary(value))
454        {
455          return svn_error_createf(
456              SVN_ERR_CLIENT_IS_BINARY_FILE, NULL,
457              _("Cannot calculate blame information for binary file '%s'"),
458               (svn_path_is_url(frb->target)
459                      ? frb->target
460                      : svn_dirent_local_style(frb->target, pool)));
461        }
462    }
463
464  if (frb->ctx->notify_func2)
465    {
466      svn_wc_notify_t *notify
467            = svn_wc_create_notify_url(
468                            svn_path_url_add_component2(frb->repos_root_url,
469                                                        path+1, pool),
470                            svn_wc_notify_blame_revision, pool);
471      notify->path = path;
472      notify->kind = svn_node_none;
473      notify->content_state = notify->prop_state
474        = svn_wc_notify_state_inapplicable;
475      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
476      notify->revision = revnum;
477      notify->rev_props = rev_props;
478      frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool);
479    }
480
481  if (frb->ctx->cancel_func)
482    SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton));
483
484  /* If there were no content changes and no (potential) merges, we couldn't
485     care less about this revision now.  Note that we checked the mime type
486     above, so things work if the user just changes the mime type in a commit.
487     Also note that we don't switch the pools in this case.  This is important,
488     since the tempfile will be removed by the pool and we need the tempfile
489     from the last revision with content changes. */
490  if (!content_delta_handler
491      && (!frb->include_merged_revisions || merged_revision))
492    return SVN_NO_ERROR;
493
494  /* Create delta baton. */
495  delta_baton = apr_pcalloc(frb->currpool, sizeof(*delta_baton));
496
497  /* Prepare the text delta window handler. */
498  if (frb->last_filename)
499    SVN_ERR(svn_stream_open_readonly(&delta_baton->source_stream, frb->last_filename,
500                                     frb->currpool, pool));
501  else
502    /* Means empty stream below. */
503    delta_baton->source_stream = NULL;
504  last_stream = svn_stream_disown(delta_baton->source_stream, pool);
505
506  if (frb->include_merged_revisions && !merged_revision)
507    filepool = frb->filepool;
508  else
509    filepool = frb->currpool;
510
511  SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL,
512                                 svn_io_file_del_on_pool_cleanup,
513                                 filepool, filepool));
514
515  /* Wrap the window handler with our own. */
516  delta_baton->file_rev_baton = frb;
517  delta_baton->is_merged_revision = merged_revision;
518
519  /* Create the rev structure. */
520  delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev));
521
522  if (frb->backwards)
523    {
524      /* Use from last round...
525         SVN_INVALID_REVNUM on first, which is exactly
526         what we want */
527      delta_baton->rev->revision = frb->last_revnum;
528      delta_baton->rev->rev_props = frb->last_props;
529
530      /* Store for next delta */
531      if (revnum >= MIN(frb->start_rev, frb->end_rev))
532        {
533          frb->last_revnum = revnum;
534          frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool);
535        }
536      /* Else: Not needed on last rev */
537    }
538  else if (merged_revision
539           || (revnum >= MIN(frb->start_rev, frb->end_rev)))
540    {
541      /* 1+ for the "youngest to oldest" blame */
542      SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev));
543
544      /* Set values from revision props. */
545      delta_baton->rev->revision = revnum;
546      delta_baton->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool);
547    }
548  else
549    {
550      /* We shouldn't get more than one revision outside the
551         specified range (unless we alsoe receive merged revisions) */
552      SVN_ERR_ASSERT((frb->last_filename == NULL)
553                     || frb->include_merged_revisions);
554
555      /* The file existed before start_rev; generate no blame info for
556         lines from this revision (or before).
557
558         This revision specifies the state as it was at the start revision */
559
560      delta_baton->rev->revision = SVN_INVALID_REVNUM;
561    }
562
563  if (frb->include_merged_revisions)
564    delta_baton->rev->path = apr_pstrdup(frb->mainpool, path);
565
566  /* Keep last revision for postprocessing after all changes */
567  frb->last_rev = delta_baton->rev;
568
569  /* Handle all delta - even if it is empty.
570     We must do the latter to "merge" blame info from other branches. */
571  if (content_delta_handler)
572    {
573      /* Proper delta - get window handler for applying delta.
574         svn_ra_get_file_revs2 will drive the delta editor. */
575      svn_txdelta_apply(last_stream, cur_stream, NULL, NULL,
576                        frb->currpool,
577                        &delta_baton->wrapped_handler,
578                        &delta_baton->wrapped_baton);
579      *content_delta_handler = window_handler;
580      *content_delta_baton = delta_baton;
581    }
582  else
583    {
584      /* Apply an empty delta, i.e. simply copy the old contents.
585         We can't simply use the existing file due to the pool rotation logic.
586         Trigger the blame update magic. */
587      SVN_ERR(svn_stream_copy3(last_stream, cur_stream, NULL, NULL, pool));
588      SVN_ERR(update_blame(delta_baton));
589    }
590
591  return SVN_NO_ERROR;
592}
593
594/* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks,
595   and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the
596   same starting value.  Both CHAIN_ORIG and CHAIN_MERGED should not be
597   NULL.  */
598static void
599normalize_blames(struct blame_chain *chain,
600                 struct blame_chain *chain_merged,
601                 apr_pool_t *pool)
602{
603  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