Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_client / blame.c
blob0167ee8f41cbf7b054c3d72fe16db513c1cebd18
1 /*
2 * blame.c: return blame messages
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #include <apr_pools.h>
21 #include "client.h"
23 #include "svn_client.h"
24 #include "svn_subst.h"
25 #include "svn_string.h"
26 #include "svn_error.h"
27 #include "svn_diff.h"
28 #include "svn_pools.h"
29 #include "svn_path.h"
30 #include "svn_props.h"
31 #include "svn_sorts.h"
33 #include "svn_private_config.h"
35 #include <assert.h>
37 /* The metadata associated with a particular revision. */
38 struct rev
40 svn_revnum_t revision; /* the revision number */
41 const char *author; /* the author of the revision */
42 const char *date; /* the date of the revision */
43 /* Used for merge reporting. */
44 const char *path; /* the absolute repository path */
47 /* One chunk of blame */
48 struct blame
50 struct rev *rev; /* the responsible revision */
51 apr_off_t start; /* the starting diff-token (line) */
52 struct blame *next; /* the next chunk */
55 /* A chain of blame chunks */
56 struct blame_chain
58 struct blame *blame; /* linked list of blame chunks */
59 struct blame *avail; /* linked list of free blame chunks */
60 struct apr_pool_t *pool; /* Allocate members from this pool. */
63 /* The baton use for the diff output routine. */
64 struct diff_baton {
65 struct blame_chain *chain;
66 struct rev *rev;
69 /* The baton used for a file revision. */
70 struct file_rev_baton {
71 svn_revnum_t start_rev, end_rev;
72 const char *target;
73 svn_client_ctx_t *ctx;
74 const svn_diff_file_options_t *diff_options;
75 svn_boolean_t ignore_mime_type;
76 /* name of file containing the previous revision of the file */
77 const char *last_filename;
78 struct rev *rev; /* the rev for which blame is being assigned
79 during a diff */
80 struct blame_chain *chain; /* the original blame chain. */
81 const char *tmp_path; /* temp file name to feed svn_io_open_unique_file */
82 apr_pool_t *mainpool; /* lives during the whole sequence of calls */
83 apr_pool_t *lastpool; /* pool used during previous call */
84 apr_pool_t *currpool; /* pool used during this call */
86 /* These are used for tracking merged revisions. */
87 svn_boolean_t include_merged_revisions;
88 svn_boolean_t merged_revision;
89 struct blame_chain *merged_chain; /* the merged blame chain. */
90 /* name of file containing the previous merged revision of the file */
91 const char *last_original_filename;
92 /* pools for files which may need to persist for more than one rev. */
93 apr_pool_t *filepool;
94 apr_pool_t *prevfilepool;
97 /* The baton used by the txdelta window handler. */
98 struct delta_baton {
99 /* Our underlying handler/baton that we wrap */
100 svn_txdelta_window_handler_t wrapped_handler;
101 void *wrapped_baton;
102 struct file_rev_baton *file_rev_baton;
103 apr_file_t *source_file; /* the delta source */
104 apr_file_t *file; /* the result of the delta */
105 const char *filename;
111 /* Return a blame chunk associated with REV for a change starting
112 at token START, and allocated in CHAIN->mainpool. */
113 static struct blame *
114 blame_create(struct blame_chain *chain,
115 struct rev *rev,
116 apr_off_t start)
118 struct blame *blame;
119 if (chain->avail)
121 blame = chain->avail;
122 chain->avail = blame->next;
124 else
125 blame = apr_palloc(chain->pool, sizeof(*blame));
126 blame->rev = rev;
127 blame->start = start;
128 blame->next = NULL;
129 return blame;
132 /* Destroy a blame chunk. */
133 static void
134 blame_destroy(struct blame_chain *chain,
135 struct blame *blame)
137 blame->next = chain->avail;
138 chain->avail = blame;
141 /* Return the blame chunk that contains token OFF, starting the search at
142 BLAME. */
143 static struct blame *
144 blame_find(struct blame *blame, apr_off_t off)
146 struct blame *prev = NULL;
147 while (blame)
149 if (blame->start > off) break;
150 prev = blame;
151 blame = blame->next;
153 return prev;
156 /* Shift the start-point of BLAME and all subsequence blame-chunks
157 by ADJUST tokens */
158 static void
159 blame_adjust(struct blame *blame, apr_off_t adjust)
161 while (blame)
163 blame->start += adjust;
164 blame = blame->next;
168 /* Delete the blame associated with the region from token START to
169 START + LENGTH */
170 static svn_error_t *
171 blame_delete_range(struct blame_chain *chain,
172 apr_off_t start,
173 apr_off_t length)
175 struct blame *first = blame_find(chain->blame, start);
176 struct blame *last = blame_find(chain->blame, start + length);
177 struct blame *tail = last->next;
179 if (first != last)
181 struct blame *walk = first->next;
182 while (walk != last)
184 struct blame *next = walk->next;
185 blame_destroy(chain, walk);
186 walk = next;
188 first->next = last;
189 last->start = start;
190 if (first->start == start)
192 *first = *last;
193 blame_destroy(chain, last);
194 last = first;
198 if (tail && tail->start == last->start + length)
200 *last = *tail;
201 blame_destroy(chain, tail);
202 tail = last->next;
205 blame_adjust(tail, -length);
207 return SVN_NO_ERROR;
210 /* Insert a chunk of blame associated with REV starting
211 at token START and continuing for LENGTH tokens */
212 static svn_error_t *
213 blame_insert_range(struct blame_chain *chain,
214 struct rev *rev,
215 apr_off_t start,
216 apr_off_t length)
218 struct blame *head = chain->blame;
219 struct blame *point = blame_find(head, start);
220 struct blame *insert;
222 if (point->start == start)
224 insert = blame_create(chain, point->rev, point->start + length);
225 point->rev = rev;
226 insert->next = point->next;
227 point->next = insert;
229 else
231 struct blame *middle;
232 middle = blame_create(chain, rev, start);
233 insert = blame_create(chain, point->rev, start + length);
234 middle->next = insert;
235 insert->next = point->next;
236 point->next = middle;
238 blame_adjust(insert->next, length);
240 return SVN_NO_ERROR;
243 /* Callback for diff between subsequent revisions */
244 static svn_error_t *
245 output_diff_modified(void *baton,
246 apr_off_t original_start,
247 apr_off_t original_length,
248 apr_off_t modified_start,
249 apr_off_t modified_length,
250 apr_off_t latest_start,
251 apr_off_t latest_length)
253 struct diff_baton *db = baton;
255 if (original_length)
256 SVN_ERR(blame_delete_range(db->chain, modified_start, original_length));
258 if (modified_length)
259 SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start,
260 modified_length));
262 return SVN_NO_ERROR;
265 static const svn_diff_output_fns_t output_fns = {
266 NULL,
267 output_diff_modified
270 /* Add the blame for the diffs between LAST_FILE and CUR_FILE with the rev
271 specified in FRB. LAST_FILE may be NULL in which
272 case blame is added for every line of CUR_FILE. */
273 static svn_error_t *
274 add_file_blame(const char *last_file,
275 const char *cur_file,
276 struct blame_chain *chain,
277 struct rev *rev,
278 const svn_diff_file_options_t *diff_options,
279 apr_pool_t *pool)
281 if (!last_file)
283 assert(chain->blame == NULL);
284 chain->blame = blame_create(chain, rev, 0);
286 else
288 svn_diff_t *diff;
289 struct diff_baton diff_baton;
291 diff_baton.chain = chain;
292 diff_baton.rev = rev;
294 /* We have a previous file. Get the diff and adjust blame info. */
295 SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file,
296 diff_options, pool));
297 SVN_ERR(svn_diff_output(diff, &diff_baton, &output_fns));
300 return SVN_NO_ERROR;
303 static svn_error_t *
304 window_handler(svn_txdelta_window_t *window, void *baton)
306 struct delta_baton *dbaton = baton;
307 struct file_rev_baton *frb = dbaton->file_rev_baton;
308 struct blame_chain *chain;
310 /* Call the wrapped handler first. */
311 SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton));
313 /* We patiently wait for the NULL window marking the end. */
314 if (window)
315 return SVN_NO_ERROR;
317 /* Close the files used for the delta.
318 It is important to do this early, since otherwise, they will be deleted
319 before all handles are closed, which leads to failures on some platforms
320 when new tempfiles are to be created. */
321 if (dbaton->source_file)
322 SVN_ERR(svn_io_file_close(dbaton->source_file, frb->currpool));
323 SVN_ERR(svn_io_file_close(dbaton->file, frb->currpool));
325 /* If we are including merged revisions, we need to add each rev to the
326 merged chain. */
327 if (frb->include_merged_revisions)
328 chain = frb->merged_chain;
329 else
330 chain = frb->chain;
332 /* Process this file. */
333 SVN_ERR(add_file_blame(frb->last_filename,
334 dbaton->filename, chain, frb->rev,
335 frb->diff_options, frb->currpool));
337 /* If we are including merged revisions, and the current revision is not a
338 merged one, we need to add its blame info to the chain for the original
339 line of history. */
340 if (frb->include_merged_revisions && ! frb->merged_revision)
342 apr_pool_t *tmppool;
344 SVN_ERR(add_file_blame(frb->last_original_filename,
345 dbaton->filename, frb->chain, frb->rev,
346 frb->diff_options, frb->currpool));
348 /* This filename could be around for a while, potentially, so
349 use the longer lifetime pool, and switch it with the previous one*/
350 svn_pool_clear(frb->prevfilepool);
351 tmppool = frb->filepool;
352 frb->filepool = frb->prevfilepool;
353 frb->prevfilepool = tmppool;
355 frb->last_original_filename = apr_pstrdup(frb->filepool,
356 dbaton->filename);
359 /* Prepare for next revision. */
361 /* Remember the file name so we can diff it with the next revision. */
362 frb->last_filename = dbaton->filename;
364 /* Switch pools. */
366 apr_pool_t *tmp_pool = frb->lastpool;
367 frb->lastpool = frb->currpool;
368 frb->currpool = tmp_pool;
371 return SVN_NO_ERROR;
374 /* Throw an SVN_ERR_CLIENT_IS_BINARY_FILE error if PROP_DIFFS indicates a
375 binary MIME type. Else, return SVN_NO_ERROR. */
376 static svn_error_t *
377 check_mimetype(apr_array_header_t *prop_diffs, const char *target,
378 apr_pool_t *pool)
380 int i;
382 for (i = 0; i < prop_diffs->nelts; ++i)
384 const svn_prop_t *prop = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
385 if (strcmp(prop->name, SVN_PROP_MIME_TYPE) == 0
386 && prop->value
387 && svn_mime_type_is_binary(prop->value->data))
388 return svn_error_createf
389 (SVN_ERR_CLIENT_IS_BINARY_FILE, 0,
390 _("Cannot calculate blame information for binary file '%s'"),
391 svn_path_local_style(target, pool));
393 return SVN_NO_ERROR;
397 static svn_error_t *
398 file_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
399 apr_hash_t *rev_props,
400 svn_boolean_t merged_revision,
401 svn_txdelta_window_handler_t *content_delta_handler,
402 void **content_delta_baton,
403 apr_array_header_t *prop_diffs,
404 apr_pool_t *pool)
406 struct file_rev_baton *frb = baton;
407 svn_stream_t *last_stream;
408 svn_stream_t *cur_stream;
409 struct delta_baton *delta_baton;
410 apr_pool_t *filepool;
412 /* Clear the current pool. */
413 svn_pool_clear(frb->currpool);
415 /* If this file has a non-textual mime-type, bail out. */
416 if (! frb->ignore_mime_type)
417 SVN_ERR(check_mimetype(prop_diffs, frb->target, frb->currpool));
419 if (frb->ctx->notify_func2)
421 svn_wc_notify_t *notify
422 = svn_wc_create_notify(path, svn_wc_notify_blame_revision, pool);
423 notify->kind = svn_node_none;
424 notify->content_state = notify->prop_state
425 = svn_wc_notify_state_inapplicable;
426 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
427 notify->revision = revnum;
428 frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool);
431 if (frb->ctx->cancel_func)
432 SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton));
434 /* If there were no content changes, we couldn't care less about this
435 revision now. Note that we checked the mime type above, so things
436 work if the user just changes the mime type in a commit.
437 Also note that we don't switch the pools in this case. This is important,
438 since the tempfile will be removed by the pool and we need the tempfile
439 from the last revision with content changes. */
440 if (!content_delta_handler)
441 return SVN_NO_ERROR;
443 frb->merged_revision = merged_revision;
445 /* Create delta baton. */
446 delta_baton = apr_palloc(frb->currpool, sizeof(*delta_baton));
448 /* Prepare the text delta window handler. */
449 if (frb->last_filename)
450 SVN_ERR(svn_io_file_open(&delta_baton->source_file, frb->last_filename,
451 APR_READ, APR_OS_DEFAULT, frb->currpool));
452 else
453 /* Means empty stream below. */
454 delta_baton->source_file = NULL;
455 last_stream = svn_stream_from_aprfile(delta_baton->source_file, pool);
457 if (frb->include_merged_revisions && !frb->merged_revision)
458 filepool = frb->filepool;
459 else
460 filepool = frb->currpool;
462 SVN_ERR(svn_io_open_unique_file2(&delta_baton->file,
463 &delta_baton->filename,
464 frb->tmp_path,
465 ".tmp", svn_io_file_del_on_pool_cleanup,
466 filepool));
467 cur_stream = svn_stream_from_aprfile(delta_baton->file, frb->currpool);
469 /* Get window handler for applying delta. */
470 svn_txdelta_apply(last_stream, cur_stream, NULL, NULL,
471 frb->currpool,
472 &delta_baton->wrapped_handler,
473 &delta_baton->wrapped_baton);
475 /* Wrap the window handler with our own. */
476 delta_baton->file_rev_baton = frb;
477 *content_delta_handler = window_handler;
478 *content_delta_baton = delta_baton;
480 /* Create the rev structure. */
481 frb->rev = apr_palloc(frb->mainpool, sizeof(struct rev));
483 if (revnum < frb->start_rev)
485 /* We shouldn't get more than one revision before start. */
486 assert(frb->last_filename == NULL);
488 /* The file existed before start_rev; generate no blame info for
489 lines from this revision (or before). */
490 frb->rev->revision = SVN_INVALID_REVNUM;
491 frb->rev->author = NULL;
492 frb->rev->date = NULL;
494 else
496 svn_string_t *str;
497 assert(revnum <= frb->end_rev);
499 /* Set values from revision props. */
500 frb->rev->revision = revnum;
502 if ((str = apr_hash_get(rev_props, SVN_PROP_REVISION_AUTHOR,
503 sizeof(SVN_PROP_REVISION_AUTHOR) - 1)))
504 frb->rev->author = apr_pstrdup(frb->mainpool, str->data);
505 else
506 frb->rev->author = NULL;
508 if ((str = apr_hash_get(rev_props, SVN_PROP_REVISION_DATE,
509 sizeof(SVN_PROP_REVISION_DATE) - 1)))
510 frb->rev->date = apr_pstrdup(frb->mainpool, str->data);
511 else
512 frb->rev->date = NULL;
515 if (frb->include_merged_revisions)
516 frb->rev->path = apr_pstrdup(frb->mainpool, path);
518 return SVN_NO_ERROR;
521 /* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks,
522 and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the
523 same starting value. Both CHAIN_ORIG and CHAIN_MERGED should not be
524 NULL. */
525 static void
526 normalize_blames(struct blame_chain *chain,
527 struct blame_chain *chain_merged,
528 apr_pool_t *pool)
530 struct blame *walk, *walk_merged;
532 /* Walk over the CHAIN's blame chunks and CHAIN_MERGED's blame chunks,
533 creating new chunks as needed. */
534 for (walk = chain->blame, walk_merged = chain_merged->blame;
535 walk->next && walk_merged->next;
536 walk = walk->next, walk_merged = walk_merged->next)
538 /* The current chunks should always be starting at the same offset. */
539 assert(walk->start == walk_merged->start);
541 if (walk->next->start < walk_merged->next->start)
543 struct blame *tmp = blame_create(chain_merged, walk_merged->next->rev,
544 walk->next->start);
545 tmp->next = walk_merged->next->next;
546 walk_merged->next = tmp;
549 if (walk->next->start > walk_merged->next->start)
551 struct blame *tmp = blame_create(chain, walk->next->rev,
552 walk_merged->next->start);
553 tmp->next = walk->next->next;
554 walk->next = tmp;
558 /* If both next pointers are null, we have an equally long list. */
559 if (walk->next == NULL && walk_merged->next == NULL)
560 return;
562 if (walk_merged->next == NULL)
564 /* Make new walk_merged chunks as needed at the end of the list so that
565 the length matches that of walk. */
566 while (walk->next != NULL)
568 struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
569 walk->next->start);
570 walk_merged->next = tmp;
571 walk_merged = walk_merged->next;
572 walk = walk->next;
576 if (walk->next == NULL)
578 /* Same as above, only create walk chunks as needed. */
579 while (walk_merged->next != NULL)
581 struct blame *tmp = blame_create(chain, walk->rev,
582 walk_merged->next->start);
583 walk->next = tmp;
584 walk = walk->next;
585 walk_merged = walk_merged->next;
590 svn_error_t *
591 svn_client_blame4(const char *target,
592 const svn_opt_revision_t *peg_revision,
593 const svn_opt_revision_t *start,
594 const svn_opt_revision_t *end,
595 const svn_diff_file_options_t *diff_options,
596 svn_boolean_t ignore_mime_type,
597 svn_boolean_t include_merged_revisions,
598 svn_client_blame_receiver2_t receiver,
599 void *receiver_baton,
600 svn_client_ctx_t *ctx,
601 apr_pool_t *pool)
603 struct file_rev_baton frb;
604 svn_ra_session_t *ra_session;
605 const char *url;
606 svn_revnum_t start_revnum, end_revnum;
607 struct blame *walk, *walk_merged = NULL;
608 apr_file_t *file;
609 apr_pool_t *iterpool;
610 svn_stream_t *stream;
612 if (start->kind == svn_opt_revision_unspecified
613 || end->kind == svn_opt_revision_unspecified)
614 return svn_error_create
615 (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
616 else if (start->kind == svn_opt_revision_working
617 || end->kind == svn_opt_revision_working)
618 return svn_error_create
619 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
620 _("blame of the WORKING revision is not supported"));
622 /* Get an RA plugin for this filesystem object. */
623 SVN_ERR(svn_client__ra_session_from_path(&ra_session, &end_revnum,
624 &url, target, NULL,
625 peg_revision, end,
626 ctx, pool));
628 SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ra_session,
629 start, target, pool));
631 if (end_revnum < start_revnum)
632 return svn_error_create
633 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
634 _("Start revision must precede end revision"));
636 frb.start_rev = start_revnum;
637 frb.end_rev = end_revnum;
638 frb.target = target;
639 frb.ctx = ctx;
640 frb.diff_options = diff_options;
641 frb.ignore_mime_type = ignore_mime_type;
642 frb.include_merged_revisions = include_merged_revisions;
643 frb.last_filename = NULL;
644 frb.last_original_filename = NULL;
645 frb.chain = apr_palloc(pool, sizeof(*frb.chain));
646 frb.chain->blame = NULL;
647 frb.chain->avail = NULL;
648 frb.chain->pool = pool;
649 if (include_merged_revisions)
651 frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain));
652 frb.merged_chain->blame = NULL;
653 frb.merged_chain->avail = NULL;
654 frb.merged_chain->pool = pool;
657 SVN_ERR(svn_io_temp_dir(&frb.tmp_path, pool));
658 frb.tmp_path = svn_path_join(frb.tmp_path, "tmp", pool),
660 frb.mainpool = pool;
661 /* The callback will flip the following two pools, because it needs
662 information from the previous call. Obviously, it can't rely on
663 the lifetime of the pool provided by get_file_revs. */
664 frb.lastpool = svn_pool_create(pool);
665 frb.currpool = svn_pool_create(pool);
666 if (include_merged_revisions)
668 frb.filepool = svn_pool_create(pool);
669 frb.prevfilepool = svn_pool_create(pool);
672 /* Collect all blame information.
673 We need to ensure that we get one revision before the start_rev,
674 if available so that we can know what was actually changed in the start
675 revision. */
676 SVN_ERR(svn_ra_get_file_revs2(ra_session, "",
677 start_revnum - (start_revnum > 0 ? 1 : 0),
678 end_revnum, include_merged_revisions,
679 file_rev_handler, &frb, pool));
681 /* Report the blame to the caller. */
683 /* The callback has to have been called at least once. */
684 assert(frb.last_filename != NULL);
686 /* Create a pool for the iteration below. */
687 iterpool = svn_pool_create(pool);
689 /* Open the last file and get a stream. */
690 SVN_ERR(svn_io_file_open(&file, frb.last_filename, APR_READ | APR_BUFFERED,
691 APR_OS_DEFAULT, pool));
692 stream = svn_subst_stream_translated(svn_stream_from_aprfile(file, pool),
693 "\n", TRUE, NULL, FALSE, pool);
695 /* Perform optional merged chain normalization. */
696 if (include_merged_revisions)
698 /* If we never created any blame for the original chain, create it now,
699 with the most recent changed revision. This could occur if a file
700 was created on a branch and them merged to another branch. This is
701 semanticly a copy, and we want to use the revision on the branch as
702 the most recently changed revision. ### Is this really what we want
703 to do here? Do the sematics of copy change? */
704 if (!frb.chain->blame)
705 frb.chain->blame = blame_create(frb.chain, frb.rev, 0);
707 normalize_blames(frb.chain, frb.merged_chain, pool);
708 walk_merged = frb.merged_chain->blame;
711 /* Process each blame item. */
712 for (walk = frb.chain->blame; walk; walk = walk->next)
714 apr_off_t line_no;
715 svn_revnum_t merged_rev;
716 const char *merged_author, *merged_date, *merged_path;
718 if (walk_merged)
720 merged_rev = walk_merged->rev->revision;
721 merged_author = walk_merged->rev->author;
722 merged_date = walk_merged->rev->date;
723 merged_path = walk_merged->rev->path;
725 else
727 merged_rev = SVN_INVALID_REVNUM;
728 merged_author = NULL;
729 merged_date = NULL;
730 merged_path = NULL;
733 for (line_no = walk->start;
734 !walk->next || line_no < walk->next->start;
735 ++line_no)
737 svn_boolean_t eof;
738 svn_stringbuf_t *sb;
740 svn_pool_clear(iterpool);
741 SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool));
742 if (ctx->cancel_func)
743 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
744 if (!eof || sb->len)
745 SVN_ERR(receiver(receiver_baton, line_no, walk->rev->revision,
746 walk->rev->author, walk->rev->date,
747 merged_rev, merged_author, merged_date,
748 merged_path, sb->data, iterpool));
749 if (eof) break;
752 if (walk_merged)
753 walk_merged = walk_merged->next;
756 SVN_ERR(svn_stream_close(stream));
758 /* We don't need the temp file any more. */
759 SVN_ERR(svn_io_file_close(file, pool));
761 svn_pool_destroy(frb.lastpool);
762 svn_pool_destroy(frb.currpool);
763 if (include_merged_revisions)
765 svn_pool_destroy(frb.filepool);
766 svn_pool_destroy(frb.prevfilepool);
768 svn_pool_destroy(iterpool);
770 return SVN_NO_ERROR;
773 /* Baton for use with wrap_blame_receiver */
774 struct blame_receiver_wrapper_baton {
775 void *baton;
776 svn_client_blame_receiver_t receiver;
779 /* This implements svn_client_blame_receiver2_t */
780 static svn_error_t *
781 blame_wrapper_receiver(void *baton,
782 apr_int64_t line_no,
783 svn_revnum_t revision,
784 const char *author,
785 const char *date,
786 svn_revnum_t merged_revision,
787 const char *merged_author,
788 const char *merged_date,
789 const char *merged_path,
790 const char *line,
791 apr_pool_t *pool)
793 struct blame_receiver_wrapper_baton *brwb = baton;
795 if (brwb->receiver)
796 return brwb->receiver(brwb->baton,
797 line_no, revision, author, date, line, pool);
799 return SVN_NO_ERROR;
802 static void
803 wrap_blame_receiver(svn_client_blame_receiver2_t *receiver2,
804 void **receiver2_baton,
805 svn_client_blame_receiver_t receiver,
806 void *receiver_baton,
807 apr_pool_t *pool)
809 struct blame_receiver_wrapper_baton *brwb = apr_palloc(pool, sizeof(*brwb));
811 /* Set the user provided old format callback in the baton. */
812 brwb->baton = receiver_baton;
813 brwb->receiver = receiver;
815 *receiver2_baton = brwb;
816 *receiver2 = blame_wrapper_receiver;
819 svn_error_t *
820 svn_client_blame3(const char *target,
821 const svn_opt_revision_t *peg_revision,
822 const svn_opt_revision_t *start,
823 const svn_opt_revision_t *end,
824 const svn_diff_file_options_t *diff_options,
825 svn_boolean_t ignore_mime_type,
826 svn_client_blame_receiver_t receiver,
827 void *receiver_baton,
828 svn_client_ctx_t *ctx,
829 apr_pool_t *pool)
831 svn_client_blame_receiver2_t receiver2;
832 void *receiver2_baton;
834 wrap_blame_receiver(&receiver2, &receiver2_baton, receiver, receiver_baton,
835 pool);
837 return svn_client_blame4(target, peg_revision, start, end, diff_options,
838 ignore_mime_type, FALSE, receiver2, receiver2_baton,
839 ctx, pool);
842 /* svn_client_blame3 guarantees 'no EOL chars' as part of the receiver
843 LINE argument. Older versions depend on the fact that if a CR is
844 required, that CR is already part of the LINE data.
846 Because of this difference, we need to trap old receivers and append
847 a CR to LINE before passing it on to the actual receiver on platforms
848 which want CRLF line termination.
852 struct wrapped_receiver_baton_s
854 svn_client_blame_receiver_t orig_receiver;
855 void *orig_baton;
858 static svn_error_t *
859 wrapped_receiver(void *baton,
860 apr_int64_t line_no,
861 svn_revnum_t revision,
862 const char *author,
863 const char *date,
864 const char *line,
865 apr_pool_t *pool)
867 struct wrapped_receiver_baton_s *b = baton;
868 svn_stringbuf_t *expanded_line = svn_stringbuf_create(line, pool);
870 svn_stringbuf_appendbytes(expanded_line, "\r", 1);
872 return b->orig_receiver(b->orig_baton, line_no, revision, author,
873 date, expanded_line->data, pool);
876 static void
877 wrap_pre_blame3_receiver(svn_client_blame_receiver_t *receiver,
878 void **receiver_baton,
879 apr_pool_t *pool)
881 if (strlen(APR_EOL_STR) > 1)
883 struct wrapped_receiver_baton_s *b = apr_palloc(pool,sizeof(*b));
885 b->orig_receiver = *receiver;
886 b->orig_baton = *receiver_baton;
888 *receiver_baton = b;
889 *receiver = wrapped_receiver;
893 svn_error_t *
894 svn_client_blame2(const char *target,
895 const svn_opt_revision_t *peg_revision,
896 const svn_opt_revision_t *start,
897 const svn_opt_revision_t *end,
898 svn_client_blame_receiver_t receiver,
899 void *receiver_baton,
900 svn_client_ctx_t *ctx,
901 apr_pool_t *pool)
903 wrap_pre_blame3_receiver(&receiver, &receiver_baton, pool);
904 return svn_client_blame3(target, peg_revision, start, end,
905 svn_diff_file_options_create(pool), FALSE,
906 receiver, receiver_baton, ctx, pool);
908 svn_error_t *
909 svn_client_blame(const char *target,
910 const svn_opt_revision_t *start,
911 const svn_opt_revision_t *end,
912 svn_client_blame_receiver_t receiver,
913 void *receiver_baton,
914 svn_client_ctx_t *ctx,
915 apr_pool_t *pool)
917 wrap_pre_blame3_receiver(&receiver, &receiver_baton, pool);
918 return svn_client_blame2(target, end, start, end,
919 receiver, receiver_baton, ctx, pool);