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"
37 #include "svn_sorts.h"
39 #include "private/svn_wc_private.h"
41 #include "svn_private_config.h"
45 /* The metadata associated with a particular revision. */
48 svn_revnum_t revision; /* the revision number */
49 apr_hash_t *rev_props; /* the revision properties */
50 /* Used for merge reporting. */
51 const char *path; /* the absolute repository path */
54 /* One chunk of blame */
57 const struct rev *rev; /* the responsible revision */
58 apr_off_t start; /* the starting diff-token (line) */
59 struct blame *next; /* the next chunk */
62 /* A chain of blame chunks */
65 struct blame *blame; /* linked list of blame chunks */
66 struct blame *avail; /* linked list of free blame chunks */
67 struct apr_pool_t *pool; /* Allocate members from this pool. */
70 /* The baton use for the diff output routine. */
72 struct blame_chain *chain;
73 const struct rev *rev;
76 /* The baton used for a file revision. */
77 struct file_rev_baton {
78 svn_revnum_t start_rev, end_rev;
80 svn_client_ctx_t *ctx;
81 const svn_diff_file_options_t *diff_options;
82 /* name of file containing the previous revision of the file */
83 const char *last_filename;
84 struct rev *rev; /* the rev for which blame is being assigned
86 struct blame_chain *chain; /* the original blame chain. */
87 const char *repos_root_url; /* To construct a url */
88 apr_pool_t *mainpool; /* lives during the whole sequence of calls */
89 apr_pool_t *lastpool; /* pool used during previous call */
90 apr_pool_t *currpool; /* pool used during this call */
92 /* These are used for tracking merged revisions. */
93 svn_boolean_t include_merged_revisions;
94 svn_boolean_t merged_revision;
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;
103 /* The baton used by the txdelta window handler. */
105 /* Our underlying handler/baton that we wrap */
106 svn_txdelta_window_handler_t wrapped_handler;
108 struct file_rev_baton *file_rev_baton;
109 const char *filename;
115 /* Return a blame chunk associated with REV for a change starting
116 at token START, and allocated in CHAIN->mainpool. */
117 static struct blame *
118 blame_create(struct blame_chain *chain,
119 const struct rev *rev,
125 blame = chain->avail;
126 chain->avail = blame->next;
129 blame = apr_palloc(chain->pool, sizeof(*blame));
131 blame->start = start;
136 /* Destroy a blame chunk. */
138 blame_destroy(struct blame_chain *chain,
141 blame->next = chain->avail;
142 chain->avail = blame;
145 /* Return the blame chunk that contains token OFF, starting the search at
147 static struct blame *
148 blame_find(struct blame *blame, apr_off_t off)
150 struct blame *prev = NULL;
153 if (blame->start > off) break;
160 /* Shift the start-point of BLAME and all subsequence blame-chunks
163 blame_adjust(struct blame *blame, apr_off_t adjust)
167 blame->start += adjust;
172 /* Delete the blame associated with the region from token START to
175 blame_delete_range(struct blame_chain *chain,
179 struct blame *first = blame_find(chain->blame, start);
180 struct blame *last = blame_find(chain->blame, start + length);
181 struct blame *tail = last->next;
185 struct blame *walk = first->next;
188 struct blame *next = walk->next;
189 blame_destroy(chain, walk);
194 if (first->start == start)
197 blame_destroy(chain, last);
202 if (tail && tail->start == last->start + length)
205 blame_destroy(chain, tail);
209 blame_adjust(tail, -length);
214 /* Insert a chunk of blame associated with REV starting
215 at token START and continuing for LENGTH tokens */
217 blame_insert_range(struct blame_chain *chain,
218 const struct rev *rev,
222 struct blame *head = chain->blame;
223 struct blame *point = blame_find(head, start);
224 struct blame *insert;
226 if (point->start == start)
228 insert = blame_create(chain, point->rev, point->start + length);
230 insert->next = point->next;
231 point->next = insert;
235 struct blame *middle;
236 middle = blame_create(chain, rev, start);
237 insert = blame_create(chain, point->rev, start + length);
238 middle->next = insert;
239 insert->next = point->next;
240 point->next = middle;
242 blame_adjust(insert->next, length);
247 /* Callback for diff between subsequent revisions */
249 output_diff_modified(void *baton,
250 apr_off_t original_start,
251 apr_off_t original_length,
252 apr_off_t modified_start,
253 apr_off_t modified_length,
254 apr_off_t latest_start,
255 apr_off_t latest_length)
257 struct diff_baton *db = baton;
260 SVN_ERR(blame_delete_range(db->chain, modified_start, original_length));
263 SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start,
269 static const svn_diff_output_fns_t output_fns = {
274 /* Add the blame for the diffs between LAST_FILE and CUR_FILE to CHAIN,
275 for revision REV. LAST_FILE may be NULL in which
276 case blame is added for every line of CUR_FILE. */
278 add_file_blame(const char *last_file,
279 const char *cur_file,
280 struct blame_chain *chain,
282 const svn_diff_file_options_t *diff_options,
287 SVN_ERR_ASSERT(chain->blame == NULL);
288 chain->blame = blame_create(chain, rev, 0);
293 struct diff_baton diff_baton;
295 diff_baton.chain = chain;
296 diff_baton.rev = rev;
298 /* We have a previous file. Get the diff and adjust blame info. */
299 SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file,
300 diff_options, pool));
301 SVN_ERR(svn_diff_output(diff, &diff_baton, &output_fns));
307 /* The delta window handler for the text delta between the previously seen
308 * revision and the revision currently being handled.
310 * Record the blame information for this revision in BATON->file_rev_baton.
312 * Implements svn_txdelta_window_handler_t.
315 window_handler(svn_txdelta_window_t *window, void *baton)
317 struct delta_baton *dbaton = baton;
318 struct file_rev_baton *frb = dbaton->file_rev_baton;
319 struct blame_chain *chain;
321 /* Call the wrapped handler first. */
322 SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton));
324 /* We patiently wait for the NULL window marking the end. */
328 /* If we are including merged revisions, we need to add each rev to the
330 if (frb->include_merged_revisions)
331 chain = frb->merged_chain;
335 /* Process this file. */
336 SVN_ERR(add_file_blame(frb->last_filename,
337 dbaton->filename, chain, frb->rev,
338 frb->diff_options, frb->currpool));
340 /* If we are including merged revisions, and the current revision is not a
341 merged one, we need to add its blame info to the chain for the original
343 if (frb->include_merged_revisions && ! frb->merged_revision)
347 SVN_ERR(add_file_blame(frb->last_original_filename,
348 dbaton->filename, frb->chain, frb->rev,
349 frb->diff_options, frb->currpool));
351 /* This filename could be around for a while, potentially, so
352 use the longer lifetime pool, and switch it with the previous one*/
353 svn_pool_clear(frb->prevfilepool);
354 tmppool = frb->filepool;
355 frb->filepool = frb->prevfilepool;
356 frb->prevfilepool = tmppool;
358 frb->last_original_filename = apr_pstrdup(frb->filepool,
362 /* Prepare for next revision. */
364 /* Remember the file name so we can diff it with the next revision. */
365 frb->last_filename = dbaton->filename;
369 apr_pool_t *tmp_pool = frb->lastpool;
370 frb->lastpool = frb->currpool;
371 frb->currpool = tmp_pool;
378 /* Calculate and record blame information for one revision of the file,
379 * by comparing the file content against the previously seen revision.
381 * This handler is called once for each interesting revision of the file.
383 * Record the blame information for this revision in (file_rev_baton) BATON.
385 * Implements svn_file_rev_handler_t.
388 file_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
389 apr_hash_t *rev_props,
390 svn_boolean_t merged_revision,
391 svn_txdelta_window_handler_t *content_delta_handler,
392 void **content_delta_baton,
393 apr_array_header_t *prop_diffs,
396 struct file_rev_baton *frb = baton;
397 svn_stream_t *last_stream;
398 svn_stream_t *cur_stream;
399 struct delta_baton *delta_baton;
400 apr_pool_t *filepool;
402 /* Clear the current pool. */
403 svn_pool_clear(frb->currpool);
405 if (frb->ctx->notify_func2)
407 svn_wc_notify_t *notify
408 = svn_wc_create_notify_url(
409 svn_path_url_add_component2(frb->repos_root_url,
411 svn_wc_notify_blame_revision, pool);
413 notify->kind = svn_node_none;
414 notify->content_state = notify->prop_state
415 = svn_wc_notify_state_inapplicable;
416 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
417 notify->revision = revnum;
418 notify->rev_props = rev_props;
419 frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool);
422 if (frb->ctx->cancel_func)
423 SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton));
425 /* If there were no content changes, we couldn't care less about this
426 revision now. Note that we checked the mime type above, so things
427 work if the user just changes the mime type in a commit.
428 Also note that we don't switch the pools in this case. This is important,
429 since the tempfile will be removed by the pool and we need the tempfile
430 from the last revision with content changes. */
431 if (!content_delta_handler)
434 frb->merged_revision = merged_revision;
436 /* Create delta baton. */
437 delta_baton = apr_palloc(frb->currpool, sizeof(*delta_baton));
439 /* Prepare the text delta window handler. */
440 if (frb->last_filename)
441 SVN_ERR(svn_stream_open_readonly(&last_stream, frb->last_filename,
442 frb->currpool, pool));
444 last_stream = svn_stream_empty(frb->currpool);
446 if (frb->include_merged_revisions && !frb->merged_revision)
447 filepool = frb->filepool;
449 filepool = frb->currpool;
451 SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL,
452 svn_io_file_del_on_pool_cleanup,
455 /* Get window handler for applying delta. */
456 svn_txdelta_apply(last_stream, cur_stream, NULL, NULL,
458 &delta_baton->wrapped_handler,
459 &delta_baton->wrapped_baton);
461 /* Wrap the window handler with our own. */
462 delta_baton->file_rev_baton = frb;
463 *content_delta_handler = window_handler;
464 *content_delta_baton = delta_baton;
466 /* Create the rev structure. */
467 frb->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev));
469 if (revnum < frb->start_rev)
471 /* We shouldn't get more than one revision before the starting
472 revision (unless of including merged revisions). */
473 SVN_ERR_ASSERT((frb->last_filename == NULL)
474 || frb->include_merged_revisions);
476 /* The file existed before start_rev; generate no blame info for
477 lines from this revision (or before). */
478 frb->rev->revision = SVN_INVALID_REVNUM;
482 SVN_ERR_ASSERT(revnum <= frb->end_rev);
484 /* Set values from revision props. */
485 frb->rev->revision = revnum;
486 frb->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool);
489 if (frb->include_merged_revisions)
490 frb->rev->path = apr_pstrdup(frb->mainpool, path);
495 /* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks,
496 and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the
497 same starting value. Both CHAIN_ORIG and CHAIN_MERGED should not be
500 normalize_blames(struct blame_chain *chain,
501 struct blame_chain *chain_merged,
504 struct blame *walk, *walk_merged;
506 /* Walk over the CHAIN's blame chunks and CHAIN_MERGED's blame chunks,
507 creating new chunks as needed. */
508 for (walk = chain->blame, walk_merged = chain_merged->blame;
509 walk->next && walk_merged->next;
510 walk = walk->next, walk_merged = walk_merged->next)
512 /* The current chunks should always be starting at the same offset. */
513 assert(walk->start == walk_merged->start);
515 if (walk->next->start < walk_merged->next->start)
517 /* insert a new chunk in CHAIN_MERGED. */
518 struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
520 tmp->next = walk_merged->next;
521 walk_merged->next = tmp;
524 if (walk->next->start > walk_merged->next->start)
526 /* insert a new chunk in CHAIN. */
527 struct blame *tmp = blame_create(chain, walk->rev,
528 walk_merged->next->start);
529 tmp->next = walk->next;
534 /* If both NEXT pointers are null, the lists are equally long, otherwise
535 we need to extend one of them. If CHAIN is longer, append new chunks
536 to CHAIN_MERGED until its length matches that of CHAIN. */
537 while (walk->next != NULL)
539 struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
541 walk_merged->next = tmp;
543 walk_merged = walk_merged->next;
547 /* Same as above, only extend CHAIN to match CHAIN_MERGED. */
548 while (walk_merged->next != NULL)
550 struct blame *tmp = blame_create(chain, walk->rev,
551 walk_merged->next->start);
555 walk_merged = walk_merged->next;
560 svn_client_blame5(const char *target,
561 const svn_opt_revision_t *peg_revision,
562 const svn_opt_revision_t *start,
563 const svn_opt_revision_t *end,
564 const svn_diff_file_options_t *diff_options,
565 svn_boolean_t ignore_mime_type,
566 svn_boolean_t include_merged_revisions,
567 svn_client_blame_receiver3_t receiver,
568 void *receiver_baton,
569 svn_client_ctx_t *ctx,
572 struct file_rev_baton frb;
573 svn_ra_session_t *ra_session;
574 svn_revnum_t start_revnum, end_revnum;
575 svn_client__pathrev_t *end_loc;
576 struct blame *walk, *walk_merged = NULL;
577 apr_pool_t *iterpool;
578 svn_stream_t *last_stream;
579 svn_stream_t *stream;
580 const char *target_abspath_or_url;
582 if (start->kind == svn_opt_revision_unspecified
583 || end->kind == svn_opt_revision_unspecified)
584 return svn_error_create
585 (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
587 if (svn_path_is_url(target))
588 target_abspath_or_url = target;
590 SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool));
592 /* Get an RA plugin for this filesystem object. */
593 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &end_loc,
594 target, NULL, peg_revision, end,
596 end_revnum = end_loc->rev;
598 SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx,
599 target_abspath_or_url, ra_session,
602 if (end_revnum < start_revnum)
603 return svn_error_create
604 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
605 _("Start revision must precede end revision"));
607 /* We check the mime-type of the yougest revision before getting all
608 the older revisions. */
609 if (!ignore_mime_type)
612 apr_hash_index_t *hi;
614 SVN_ERR(svn_client_propget5(&props, NULL, SVN_PROP_MIME_TYPE,
615 target_abspath_or_url, peg_revision,
616 end, NULL, svn_depth_empty, NULL, ctx,
619 /* props could be keyed on URLs or paths depending on the
620 peg_revision and end values so avoid using the key. */
621 hi = apr_hash_first(pool, props);
626 /* Should only be one value */
627 SVN_ERR_ASSERT(apr_hash_count(props) == 1);
629 value = svn__apr_hash_index_val(hi);
630 if (value && svn_mime_type_is_binary(value->data))
631 return svn_error_createf
632 (SVN_ERR_CLIENT_IS_BINARY_FILE, 0,
633 _("Cannot calculate blame information for binary file '%s'"),
634 (svn_path_is_url(target)
635 ? target : svn_dirent_local_style(target, pool)));
639 frb.start_rev = start_revnum;
640 frb.end_rev = end_revnum;
643 frb.diff_options = diff_options;
644 frb.include_merged_revisions = include_merged_revisions;
645 frb.last_filename = NULL;
646 frb.last_original_filename = NULL;
647 frb.chain = apr_palloc(pool, sizeof(*frb.chain));
648 frb.chain->blame = NULL;
649 frb.chain->avail = NULL;
650 frb.chain->pool = pool;
651 if (include_merged_revisions)
653 frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain));
654 frb.merged_chain->blame = NULL;
655 frb.merged_chain->avail = NULL;
656 frb.merged_chain->pool = pool;
659 SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool));
662 /* The callback will flip the following two pools, because it needs
663 information from the previous call. Obviously, it can't rely on
664 the lifetime of the pool provided by get_file_revs. */
665 frb.lastpool = svn_pool_create(pool);
666 frb.currpool = svn_pool_create(pool);
667 if (include_merged_revisions)
669 frb.filepool = svn_pool_create(pool);
670 frb.prevfilepool = svn_pool_create(pool);
673 /* Collect all blame information.
674 We need to ensure that we get one revision before the start_rev,
675 if available so that we can know what was actually changed in the start
677 SVN_ERR(svn_ra_get_file_revs2(ra_session, "",
678 start_revnum - (start_revnum > 0 ? 1 : 0),
679 end_revnum, include_merged_revisions,
680 file_rev_handler, &frb, pool));
682 if (end->kind == svn_opt_revision_working)
684 /* If the local file is modified we have to call the handler on the
685 working copy file with keywords unexpanded */
686 svn_wc_status3_t *status;
688 SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool,
691 if (status->text_status != svn_wc_status_normal
692 || (status->prop_status != svn_wc_status_normal
693 && status->prop_status != svn_wc_status_none))
695 svn_stream_t *wcfile;
696 svn_stream_t *tempfile;
697 svn_opt_revision_t rev;
698 svn_boolean_t normalize_eols = FALSE;
699 const char *temppath;
701 if (status->prop_status != svn_wc_status_none)
703 const svn_string_t *eol_style;
704 SVN_ERR(svn_wc_prop_get2(&eol_style, ctx->wc_ctx,
705 target_abspath_or_url,
711 svn_subst_eol_style_t style;
713 svn_subst_eol_style_from_value(&style, &eol, eol_style->data);
715 normalize_eols = (style == svn_subst_eol_style_native);
719 rev.kind = svn_opt_revision_working;
720 SVN_ERR(svn_client__get_normalized_stream(&wcfile, ctx->wc_ctx,
721 target_abspath_or_url, &rev,
722 FALSE, normalize_eols,
727 SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL,
728 svn_io_file_del_on_pool_cleanup,
731 SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func,
732 ctx->cancel_baton, pool));
734 SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL,
735 frb.diff_options, pool));
737 frb.last_filename = temppath;
741 /* Report the blame to the caller. */
743 /* The callback has to have been called at least once. */
744 SVN_ERR_ASSERT(frb.last_filename != NULL);
746 /* Create a pool for the iteration below. */
747 iterpool = svn_pool_create(pool);
749 /* Open the last file and get a stream. */
750 SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename,
752 stream = svn_subst_stream_translated(last_stream,
753 "\n", TRUE, NULL, FALSE, pool);
755 /* Perform optional merged chain normalization. */
756 if (include_merged_revisions)
758 /* If we never created any blame for the original chain, create it now,
759 with the most recent changed revision. This could occur if a file
760 was created on a branch and them merged to another branch. This is
761 semanticly a copy, and we want to use the revision on the branch as
762 the most recently changed revision. ### Is this really what we want
763 to do here? Do the sematics of copy change? */
764 if (!frb.chain->blame)
765 frb.chain->blame = blame_create(frb.chain, frb.rev, 0);
767 normalize_blames(frb.chain, frb.merged_chain, pool);
768 walk_merged = frb.merged_chain->blame;
771 /* Process each blame item. */
772 for (walk = frb.chain->blame; walk; walk = walk->next)
775 svn_revnum_t merged_rev;
776 const char *merged_path;
777 apr_hash_t *merged_rev_props;
781 merged_rev = walk_merged->rev->revision;
782 merged_rev_props = walk_merged->rev->rev_props;
783 merged_path = walk_merged->rev->path;
787 merged_rev = SVN_INVALID_REVNUM;
788 merged_rev_props = NULL;
792 for (line_no = walk->start;
793 !walk->next || line_no < walk->next->start;
799 svn_pool_clear(iterpool);
800 SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool));
801 if (ctx->cancel_func)
802 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
806 SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
807 line_no, walk->rev->revision,
808 walk->rev->rev_props, merged_rev,
809 merged_rev_props, merged_path,
810 sb->data, FALSE, iterpool));
812 SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
813 line_no, SVN_INVALID_REVNUM,
814 NULL, SVN_INVALID_REVNUM,
816 sb->data, TRUE, iterpool));
822 walk_merged = walk_merged->next;
825 SVN_ERR(svn_stream_close(stream));
827 svn_pool_destroy(frb.lastpool);
828 svn_pool_destroy(frb.currpool);
829 if (include_merged_revisions)
831 svn_pool_destroy(frb.filepool);
832 svn_pool_destroy(frb.prevfilepool);
834 svn_pool_destroy(iterpool);