2 * blame.c: return blame messages
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 #include <apr_pools.h>
28 #include "svn_client.h"
29 #include "svn_subst.h"
30 #include "svn_string.h"
31 #include "svn_error.h"
33 #include "svn_pools.h"
34 #include "svn_dirent_uri.h"
36 #include "svn_props.h"
38 #include "svn_sorts.h"
40 #include "private/svn_wc_private.h"
42 #include "svn_private_config.h"
46 /* The metadata associated with a particular revision. */
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 */
55 /* One chunk of blame */
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 */
63 /* A chain of blame chunks */
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. */
71 /* The baton use for the diff output routine. */
73 struct blame_chain *chain;
74 const struct rev *rev;
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;
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 */
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. */
100 apr_pool_t *prevfilepool;
102 svn_boolean_t check_mime_type;
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;
111 /* The baton used by the txdelta window handler. Allocated per revision */
113 /* Our underlying handler/baton that we wrap */
114 svn_txdelta_window_handler_t wrapped_handler;
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 */
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,
136 blame = chain->avail;
137 chain->avail = blame->next;
140 blame = apr_palloc(chain->pool, sizeof(*blame));
142 blame->start = start;
147 /* Destroy a blame chunk. */
149 blame_destroy(struct blame_chain *chain,
152 blame->next = chain->avail;
153 chain->avail = blame;
156 /* Return the blame chunk that contains token OFF, starting the search at
158 static struct blame *
159 blame_find(struct blame *blame, apr_off_t off)
161 struct blame *prev = NULL;
164 if (blame->start > off) break;
171 /* Shift the start-point of BLAME and all subsequence blame-chunks
174 blame_adjust(struct blame *blame, apr_off_t adjust)
178 blame->start += adjust;
183 /* Delete the blame associated with the region from token START to
186 blame_delete_range(struct blame_chain *chain,
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;
196 struct blame *walk = first->next;
199 struct blame *next = walk->next;
200 blame_destroy(chain, walk);
205 if (first->start == start)
208 blame_destroy(chain, last);
213 if (tail && tail->start == last->start + length)
216 blame_destroy(chain, tail);
220 blame_adjust(tail, -length);
225 /* Insert a chunk of blame associated with REV starting
226 at token START and continuing for LENGTH tokens */
228 blame_insert_range(struct blame_chain *chain,
229 const struct rev *rev,
233 struct blame *head = chain->blame;
234 struct blame *point = blame_find(head, start);
235 struct blame *insert;
237 if (point->start == start)
239 insert = blame_create(chain, point->rev, point->start + length);
241 insert->next = point->next;
242 point->next = insert;
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;
253 blame_adjust(insert->next, length);
258 /* Callback for diff between subsequent revisions */
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)
268 struct diff_baton *db = baton;
271 SVN_ERR(blame_delete_range(db->chain, modified_start, original_length));
274 SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start,
280 static const svn_diff_output_fns_t output_fns = {
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. */
289 add_file_blame(const char *last_file,
290 const char *cur_file,
291 struct blame_chain *chain,
293 const svn_diff_file_options_t *diff_options,
294 svn_cancel_func_t cancel_func,
300 SVN_ERR_ASSERT(chain->blame == NULL);
301 chain->blame = blame_create(chain, rev, 0);
306 struct diff_baton diff_baton;
308 diff_baton.chain = chain;
309 diff_baton.rev = rev;
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));
321 /* Record the blame information for the revision in BATON->file_rev_baton.
324 update_blame(void *baton)
326 struct delta_baton *dbaton = baton;
327 struct file_rev_baton *frb = dbaton->file_rev_baton;
328 struct blame_chain *chain;
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));
337 /* If we are including merged revisions, we need to add each rev to the
339 if (frb->include_merged_revisions)
340 chain = frb->merged_chain;
344 /* Process this file. */
345 SVN_ERR(add_file_blame(frb->last_filename,
346 dbaton->filename, chain, dbaton->rev,
348 frb->ctx->cancel_func, frb->ctx->cancel_baton,
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
354 if (frb->include_merged_revisions && ! dbaton->is_merged_revision)
358 SVN_ERR(add_file_blame(frb->last_original_filename,
359 dbaton->filename, frb->chain, dbaton->rev,
361 frb->ctx->cancel_func, frb->ctx->cancel_baton,
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;
371 frb->last_original_filename = apr_pstrdup(frb->filepool,
375 /* Prepare for next revision. */
377 /* Remember the file name so we can diff it with the next revision. */
378 frb->last_filename = dbaton->filename;
382 apr_pool_t *tmp_pool = frb->lastpool;
383 frb->lastpool = frb->currpool;
384 frb->currpool = tmp_pool;
390 /* The delta window handler for the text delta between the previously seen
391 * revision and the revision currently being handled.
393 * Record the blame information for this revision in BATON->file_rev_baton.
395 * Implements svn_txdelta_window_handler_t.
398 window_handler(svn_txdelta_window_t *window, void *baton)
400 struct delta_baton *dbaton = baton;
402 /* Call the wrapped handler first. */
403 if (dbaton->wrapped_handler)
404 SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton));
406 /* We patiently wait for the NULL window marking the end. */
410 /* Diff and update blame info. */
411 SVN_ERR(update_blame(baton));
417 /* Calculate and record blame information for one revision of the file,
418 * by comparing the file content against the previously seen revision.
420 * This handler is called once for each interesting revision of the file.
422 * Record the blame information for this revision in (file_rev_baton) BATON.
424 * Implements svn_file_rev_handler_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,
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;
441 /* Clear the current pool. */
442 svn_pool_clear(frb->currpool);
444 if (frb->check_mime_type)
446 apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool);
449 frb->check_mime_type = FALSE; /* Only check first */
451 value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
453 if (value && svn_mime_type_is_binary(value))
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)
460 : svn_dirent_local_style(frb->target, pool)));
464 if (frb->ctx->notify_func2)
466 svn_wc_notify_t *notify
467 = svn_wc_create_notify_url(
468 svn_path_url_add_component2(frb->repos_root_url,
470 svn_wc_notify_blame_revision, pool);
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);
481 if (frb->ctx->cancel_func)
482 SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton));
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))
494 /* Create delta baton. */
495 delta_baton = apr_pcalloc(frb->currpool, sizeof(*delta_baton));
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));
502 /* Means empty stream below. */
503 delta_baton->source_stream = NULL;
504 last_stream = svn_stream_disown(delta_baton->source_stream, pool);
506 if (frb->include_merged_revisions && !merged_revision)
507 filepool = frb->filepool;
509 filepool = frb->currpool;
511 SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL,
512 svn_io_file_del_on_pool_cleanup,
513 filepool, filepool));
515 /* Wrap the window handler with our own. */
516 delta_baton->file_rev_baton = frb;
517 delta_baton->is_merged_revision = merged_revision;
519 /* Create the rev structure. */
520 delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev));
524 /* Use from last round...
525 SVN_INVALID_REVNUM on first, which is exactly
527 delta_baton->rev->revision = frb->last_revnum;
528 delta_baton->rev->rev_props = frb->last_props;
530 /* Store for next delta */
531 if (revnum >= MIN(frb->start_rev, frb->end_rev))
533 frb->last_revnum = revnum;
534 frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool);
536 /* Else: Not needed on last rev */
538 else if (merged_revision
539 || (revnum >= MIN(frb->start_rev, frb->end_rev)))
541 /* 1+ for the "youngest to oldest" blame */
542 SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev));
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);
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);
555 /* The file existed before start_rev; generate no blame info for
556 lines from this revision (or before).
558 This revision specifies the state as it was at the start revision */
560 delta_baton->rev->revision = SVN_INVALID_REVNUM;
563 if (frb->include_merged_revisions)
564 delta_baton->rev->path = apr_pstrdup(frb->mainpool, path);
566 /* Keep last revision for postprocessing after all changes */
567 frb->last_rev = delta_baton->rev;
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)
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,
577 &delta_baton->wrapped_handler,
578 &delta_baton->wrapped_baton);
579 *content_delta_handler = window_handler;
580 *content_delta_baton = delta_baton;
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));
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
599 normalize_blames(struct blame_chain *chain,
600 struct blame_chain *chain_merged,
603 struct blame *walk, *walk_merged;
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)
611 /* The current chunks should always be starting at the same offset. */
612 assert(walk->start == walk_merged->start);
614 if (walk->next->start < walk_merged->next->start)
616 /* insert a new chunk in CHAIN_MERGED. */
617 struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
619 tmp->next = walk_merged->next;
620 walk_merged->next = tmp;
623 if (walk->next->start > walk_merged->next->start)
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;
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)
638 struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
640 walk_merged->next = tmp;
642 walk_merged = walk_merged->next;
646 /* Same as above, only extend CHAIN to match CHAIN_MERGED. */
647 while (walk_merged->next != NULL)
649 struct blame *tmp = blame_create(chain, walk->rev,
650 walk_merged->next->start);
654 walk_merged = walk_merged->next;
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,
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;
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);
685 if (svn_path_is_url(target))
686 target_abspath_or_url = target;
688 SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool));
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,
696 SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx,
697 target_abspath_or_url, ra_session,
700 SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx,
701 target_abspath_or_url, ra_session,
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);
710 SVN_ERR(svn_client__resolve_rev_and_url(&loc, ra_session,
711 target, peg_revision,
715 /* Make the session point to the real URL. */
716 SVN_ERR(svn_ra_reparent(ra_session, loc->url, pool));
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)
725 const char *mime_type = NULL;
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))
732 SVN_ERR(svn_ra_get_file(ra_session, "", end_revnum, NULL, NULL,
735 mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
739 const svn_string_t *value;
741 if (end->kind == svn_opt_revision_working)
742 SVN_ERR(svn_wc_prop_get2(&value, ctx->wc_ctx,
743 target_abspath_or_url,
748 SVN_ERR(svn_wc_get_pristine_props(&props, ctx->wc_ctx,
749 target_abspath_or_url,
752 value = props ? svn_hash_gets(props, SVN_PROP_MIME_TYPE)
756 mime_type = value ? value->data : NULL;
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)));
770 frb.start_rev = start_revnum;
771 frb.end_rev = end_revnum;
774 frb.diff_options = diff_options;
775 frb.include_merged_revisions = include_merged_revisions;
776 frb.last_filename = 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)
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;
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);
795 SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, 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)
805 frb.filepool = svn_pool_create(pool);
806 frb.prevfilepool = svn_pool_create(pool);
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
813 SVN_ERR(svn_ra_get_file_revs2(ra_session, "",
814 frb.backwards ? start_revnum
815 : MAX(0, start_revnum-1),
817 include_merged_revisions,
818 file_rev_handler, &frb, pool));
820 if (end->kind == svn_opt_revision_working)
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;
826 SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool,
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))
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;
839 if (status->prop_status != svn_wc_status_none)
841 const svn_string_t *eol_style;
842 SVN_ERR(svn_wc_prop_get2(&eol_style, ctx->wc_ctx,
843 target_abspath_or_url,
849 svn_subst_eol_style_t style;
851 svn_subst_eol_style_from_value(&style, &eol, eol_style->data);
853 normalize_eols = (style == svn_subst_eol_style_native);
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,
865 SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL,
866 svn_io_file_del_on_pool_cleanup,
869 SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func,
870 ctx->cancel_baton, pool));
872 SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL,
874 ctx->cancel_func, ctx->cancel_baton, pool));
876 frb.last_filename = temppath;
880 /* Report the blame to the caller. */
882 /* The callback has to have been called at least once. */
883 SVN_ERR_ASSERT(frb.last_filename != NULL);
885 /* Create a pool for the iteration below. */
886 iterpool = svn_pool_create(pool);
888 /* Open the last file and get a stream. */
889 SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename,
891 stream = svn_subst_stream_translated(last_stream,
892 "\n", TRUE, NULL, FALSE, pool);
894 /* Perform optional merged chain normalization. */
895 if (include_merged_revisions)
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);
906 normalize_blames(frb.chain, frb.merged_chain, pool);
907 walk_merged = frb.merged_chain->blame;
910 /* Process each blame item. */
911 for (walk = frb.chain->blame; walk; walk = walk->next)
914 svn_revnum_t merged_rev;
915 const char *merged_path;
916 apr_hash_t *merged_rev_props;
920 merged_rev = walk_merged->rev->revision;
921 merged_rev_props = walk_merged->rev->rev_props;
922 merged_path = walk_merged->rev->path;
926 merged_rev = SVN_INVALID_REVNUM;
927 merged_rev_props = NULL;
931 for (line_no = walk->start;
932 !walk->next || line_no < walk->next->start;
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));
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));
951 SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
952 line_no, SVN_INVALID_REVNUM,
953 NULL, SVN_INVALID_REVNUM,
955 sb->data, TRUE, iterpool));
961 walk_merged = walk_merged->next;
964 SVN_ERR(svn_stream_close(stream));
966 svn_pool_destroy(frb.lastpool);
967 svn_pool_destroy(frb.currpool);
968 if (include_merged_revisions)
970 svn_pool_destroy(frb.filepool);
971 svn_pool_destroy(frb.prevfilepool);
973 svn_pool_destroy(iterpool);