]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/blame.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_client / blame.c
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 *
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
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 *
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
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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
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 *
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 }