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. */
47 struct 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 */
56 struct 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 */
64 struct 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. */
72 struct 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 */
78 struct 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 */
112 struct 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. */
128 static struct blame *
blame_create(struct blame_chain * chain,const struct rev * rev,apr_off_t start)129 blame_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. */
148 static void
blame_destroy(struct blame_chain * chain,struct blame * blame)149 blame_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. */
158 static struct blame *
blame_find(struct blame * blame,apr_off_t off)159 blame_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 */
173 static void
blame_adjust(struct blame * blame,apr_off_t adjust)174 blame_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 */
185 static svn_error_t *
blame_delete_range(struct blame_chain * chain,apr_off_t start,apr_off_t length)186 blame_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 */
227 static svn_error_t *
blame_insert_range(struct blame_chain * chain,const struct rev * rev,apr_off_t start,apr_off_t length)228 blame_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 */
259 static svn_error_t *
output_diff_modified(void * baton,apr_off_t original_start,apr_off_t original_length,apr_off_t modified_start,apr_off_t modified_length,apr_off_t latest_start,apr_off_t latest_length)260 output_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 
280 static 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. */
288 static svn_error_t *
add_file_blame(const char * last_file,const char * cur_file,struct blame_chain * chain,struct rev * rev,const svn_diff_file_options_t * diff_options,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)289 add_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  */
323 static svn_error_t *
update_blame(void * baton)324 update_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  */
397 static svn_error_t *
window_handler(svn_txdelta_window_t * window,void * baton)398 window_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  */
426 static svn_error_t *
file_rev_handler(void * baton,const char * path,svn_revnum_t revnum,apr_hash_t * rev_props,svn_boolean_t merged_revision,svn_txdelta_window_handler_t * content_delta_handler,void ** content_delta_baton,apr_array_header_t * prop_diffs,apr_pool_t * pool)427 file_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.  */
598 static void
normalize_blames(struct blame_chain * chain,struct blame_chain * chain_merged,apr_pool_t * pool)599 normalize_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 
658 svn_error_t *
svn_client_blame5(const char * target,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * start,const svn_opt_revision_t * end,const svn_diff_file_options_t * diff_options,svn_boolean_t ignore_mime_type,svn_boolean_t include_merged_revisions,svn_client_blame_receiver3_t receiver,void * receiver_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)659 svn_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