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>
23 #include "svn_client.h"
24 #include "svn_subst.h"
25 #include "svn_string.h"
26 #include "svn_error.h"
28 #include "svn_pools.h"
30 #include "svn_props.h"
31 #include "svn_sorts.h"
33 #include "svn_private_config.h"
37 /* The metadata associated with a particular revision. */
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 */
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 */
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. */
65 struct blame_chain
*chain
;
69 /* The baton used for a file revision. */
70 struct file_rev_baton
{
71 svn_revnum_t start_rev
, end_rev
;
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
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. */
94 apr_pool_t
*prevfilepool
;
97 /* The baton used by the txdelta window handler. */
99 /* Our underlying handler/baton that we wrap */
100 svn_txdelta_window_handler_t wrapped_handler
;
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
,
121 blame
= chain
->avail
;
122 chain
->avail
= blame
->next
;
125 blame
= apr_palloc(chain
->pool
, sizeof(*blame
));
127 blame
->start
= start
;
132 /* Destroy a blame chunk. */
134 blame_destroy(struct blame_chain
*chain
,
137 blame
->next
= chain
->avail
;
138 chain
->avail
= blame
;
141 /* Return the blame chunk that contains token OFF, starting the search at
143 static struct blame
*
144 blame_find(struct blame
*blame
, apr_off_t off
)
146 struct blame
*prev
= NULL
;
149 if (blame
->start
> off
) break;
156 /* Shift the start-point of BLAME and all subsequence blame-chunks
159 blame_adjust(struct blame
*blame
, apr_off_t adjust
)
163 blame
->start
+= adjust
;
168 /* Delete the blame associated with the region from token START to
171 blame_delete_range(struct blame_chain
*chain
,
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
;
181 struct blame
*walk
= first
->next
;
184 struct blame
*next
= walk
->next
;
185 blame_destroy(chain
, walk
);
190 if (first
->start
== start
)
193 blame_destroy(chain
, last
);
198 if (tail
&& tail
->start
== last
->start
+ length
)
201 blame_destroy(chain
, tail
);
205 blame_adjust(tail
, -length
);
210 /* Insert a chunk of blame associated with REV starting
211 at token START and continuing for LENGTH tokens */
213 blame_insert_range(struct blame_chain
*chain
,
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
);
226 insert
->next
= point
->next
;
227 point
->next
= insert
;
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
);
243 /* Callback for diff between subsequent revisions */
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
;
256 SVN_ERR(blame_delete_range(db
->chain
, modified_start
, original_length
));
259 SVN_ERR(blame_insert_range(db
->chain
, db
->rev
, modified_start
,
265 static const svn_diff_output_fns_t output_fns
= {
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. */
274 add_file_blame(const char *last_file
,
275 const char *cur_file
,
276 struct blame_chain
*chain
,
278 const svn_diff_file_options_t
*diff_options
,
283 assert(chain
->blame
== NULL
);
284 chain
->blame
= blame_create(chain
, rev
, 0);
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
));
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. */
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
327 if (frb
->include_merged_revisions
)
328 chain
= frb
->merged_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
340 if (frb
->include_merged_revisions
&& ! frb
->merged_revision
)
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
,
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
;
366 apr_pool_t
*tmp_pool
= frb
->lastpool
;
367 frb
->lastpool
= frb
->currpool
;
368 frb
->currpool
= tmp_pool
;
374 /* Throw an SVN_ERR_CLIENT_IS_BINARY_FILE error if PROP_DIFFS indicates a
375 binary MIME type. Else, return SVN_NO_ERROR. */
377 check_mimetype(apr_array_header_t
*prop_diffs
, const char *target
,
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
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
));
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
,
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
)
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
));
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
;
460 filepool
= frb
->currpool
;
462 SVN_ERR(svn_io_open_unique_file2(&delta_baton
->file
,
463 &delta_baton
->filename
,
465 ".tmp", svn_io_file_del_on_pool_cleanup
,
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
,
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
;
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
);
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
);
512 frb
->rev
->date
= NULL
;
515 if (frb
->include_merged_revisions
)
516 frb
->rev
->path
= apr_pstrdup(frb
->mainpool
, path
);
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
526 normalize_blames(struct blame_chain
*chain
,
527 struct blame_chain
*chain_merged
,
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
,
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
;
558 /* If both next pointers are null, we have an equally long list. */
559 if (walk
->next
== NULL
&& walk_merged
->next
== NULL
)
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
,
570 walk_merged
->next
= tmp
;
571 walk_merged
= walk_merged
->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
);
585 walk_merged
= walk_merged
->next
;
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
,
603 struct file_rev_baton frb
;
604 svn_ra_session_t
*ra_session
;
606 svn_revnum_t start_revnum
, end_revnum
;
607 struct blame
*walk
, *walk_merged
= NULL
;
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
,
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
;
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
),
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
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
)
715 svn_revnum_t merged_rev
;
716 const char *merged_author
, *merged_date
, *merged_path
;
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
;
727 merged_rev
= SVN_INVALID_REVNUM
;
728 merged_author
= NULL
;
733 for (line_no
= walk
->start
;
734 !walk
->next
|| line_no
< walk
->next
->start
;
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
));
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
));
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
);
773 /* Baton for use with wrap_blame_receiver */
774 struct blame_receiver_wrapper_baton
{
776 svn_client_blame_receiver_t receiver
;
779 /* This implements svn_client_blame_receiver2_t */
781 blame_wrapper_receiver(void *baton
,
783 svn_revnum_t revision
,
786 svn_revnum_t merged_revision
,
787 const char *merged_author
,
788 const char *merged_date
,
789 const char *merged_path
,
793 struct blame_receiver_wrapper_baton
*brwb
= baton
;
796 return brwb
->receiver(brwb
->baton
,
797 line_no
, revision
, author
, date
, line
, pool
);
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
,
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
;
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
,
831 svn_client_blame_receiver2_t receiver2
;
832 void *receiver2_baton
;
834 wrap_blame_receiver(&receiver2
, &receiver2_baton
, receiver
, receiver_baton
,
837 return svn_client_blame4(target
, peg_revision
, start
, end
, diff_options
,
838 ignore_mime_type
, FALSE
, receiver2
, receiver2_baton
,
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
;
859 wrapped_receiver(void *baton
,
861 svn_revnum_t revision
,
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
);
877 wrap_pre_blame3_receiver(svn_client_blame_receiver_t
*receiver
,
878 void **receiver_baton
,
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
;
889 *receiver
= wrapped_receiver
;
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
,
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
);
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
,
917 wrap_pre_blame3_receiver(&receiver
, &receiver_baton
, pool
);
918 return svn_client_blame2(target
, end
, start
, end
,
919 receiver
, receiver_baton
, ctx
, pool
);