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