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 */
45 struct rev
*next
; /* the next revision */
48 /* One chunk of blame */
51 struct rev
*rev
; /* the responsible revision */
52 apr_off_t start
; /* the starting diff-token (line) */
53 struct blame
*next
; /* the next chunk */
56 /* A chain of blame chunks */
59 struct blame
*blame
; /* linked list of blame chunks */
60 struct blame
*avail
; /* linked list of free blame chunks */
61 struct apr_pool_t
*pool
; /* Allocate members from this pool. */
64 /* The baton use for the diff output routine. */
66 struct blame_chain
*chain
;
70 /* The baton used for a file revision. */
71 struct file_rev_baton
{
72 svn_revnum_t start_rev
, end_rev
;
74 svn_client_ctx_t
*ctx
;
75 const svn_diff_file_options_t
*diff_options
;
76 svn_boolean_t ignore_mime_type
;
77 /* name of file containing the previous revision of the file */
78 const char *last_filename
;
79 struct rev
*rev
; /* the rev for which blame is being assigned
81 struct blame_chain
*chain
; /* the original blame chain. */
82 const char *tmp_path
; /* temp file name to feed svn_io_open_unique_file */
83 apr_pool_t
*mainpool
; /* lives during the whole sequence of calls */
84 apr_pool_t
*lastpool
; /* pool used during previous call */
85 apr_pool_t
*currpool
; /* pool used during this call */
87 /* These are used for tracking merged revisions. */
88 svn_boolean_t include_merged_revisions
;
89 svn_boolean_t merged_revision
;
90 struct blame_chain
*merged_chain
; /* the merged blame chain. */
91 /* name of file containing the previous merged revision of the file */
92 const char *last_original_filename
;
93 /* pools for files which may need to persist for more than one rev. */
95 apr_pool_t
*prevfilepool
;
98 /* The baton used by the txdelta window handler. */
100 /* Our underlying handler/baton that we wrap */
101 svn_txdelta_window_handler_t wrapped_handler
;
103 struct file_rev_baton
*file_rev_baton
;
104 apr_file_t
*source_file
; /* the delta source */
105 apr_file_t
*file
; /* the result of the delta */
106 const char *filename
;
112 /* Return a blame chunk associated with REV for a change starting
113 at token START, and allocated in CHAIN->mainpool. */
114 static struct blame
*
115 blame_create(struct blame_chain
*chain
,
122 blame
= chain
->avail
;
123 chain
->avail
= blame
->next
;
126 blame
= apr_palloc(chain
->pool
, sizeof(*blame
));
128 blame
->start
= start
;
133 /* Destroy a blame chunk. */
135 blame_destroy(struct blame_chain
*chain
,
138 blame
->next
= chain
->avail
;
139 chain
->avail
= blame
;
142 /* Return the blame chunk that contains token OFF, starting the search at
144 static struct blame
*
145 blame_find(struct blame
*blame
, apr_off_t off
)
147 struct blame
*prev
= NULL
;
150 if (blame
->start
> off
) break;
157 /* Shift the start-point of BLAME and all subsequence blame-chunks
160 blame_adjust(struct blame
*blame
, apr_off_t adjust
)
164 blame
->start
+= adjust
;
169 /* Delete the blame associated with the region from token START to
172 blame_delete_range(struct blame_chain
*chain
,
176 struct blame
*first
= blame_find(chain
->blame
, start
);
177 struct blame
*last
= blame_find(chain
->blame
, start
+ length
);
178 struct blame
*tail
= last
->next
;
182 struct blame
*walk
= first
->next
;
185 struct blame
*next
= walk
->next
;
186 blame_destroy(chain
, walk
);
191 if (first
->start
== start
)
194 blame_destroy(chain
, last
);
199 if (tail
&& tail
->start
== last
->start
+ length
)
202 blame_destroy(chain
, tail
);
206 blame_adjust(tail
, -length
);
211 /* Insert a chunk of blame associated with REV starting
212 at token START and continuing for LENGTH tokens */
214 blame_insert_range(struct blame_chain
*chain
,
219 struct blame
*head
= chain
->blame
;
220 struct blame
*point
= blame_find(head
, start
);
221 struct blame
*insert
;
223 if (point
->start
== start
)
225 insert
= blame_create(chain
, point
->rev
, point
->start
+ length
);
227 insert
->next
= point
->next
;
228 point
->next
= insert
;
232 struct blame
*middle
;
233 middle
= blame_create(chain
, rev
, start
);
234 insert
= blame_create(chain
, point
->rev
, start
+ length
);
235 middle
->next
= insert
;
236 insert
->next
= point
->next
;
237 point
->next
= middle
;
239 blame_adjust(insert
->next
, length
);
244 /* Callback for diff between subsequent revisions */
246 output_diff_modified(void *baton
,
247 apr_off_t original_start
,
248 apr_off_t original_length
,
249 apr_off_t modified_start
,
250 apr_off_t modified_length
,
251 apr_off_t latest_start
,
252 apr_off_t latest_length
)
254 struct diff_baton
*db
= baton
;
257 SVN_ERR(blame_delete_range(db
->chain
, modified_start
, original_length
));
260 SVN_ERR(blame_insert_range(db
->chain
, db
->rev
, modified_start
,
266 static const svn_diff_output_fns_t output_fns
= {
271 /* Add the blame for the diffs between LAST_FILE and CUR_FILE with the rev
272 specified in FRB. LAST_FILE may be NULL in which
273 case blame is added for every line of CUR_FILE. */
275 add_file_blame(const char *last_file
,
276 const char *cur_file
,
277 struct blame_chain
*chain
,
279 const svn_diff_file_options_t
*diff_options
,
284 assert(chain
->blame
== NULL
);
285 chain
->blame
= blame_create(chain
, rev
, 0);
290 struct diff_baton diff_baton
;
292 diff_baton
.chain
= chain
;
293 diff_baton
.rev
= rev
;
295 /* We have a previous file. Get the diff and adjust blame info. */
296 SVN_ERR(svn_diff_file_diff_2(&diff
, last_file
, cur_file
,
297 diff_options
, pool
));
298 SVN_ERR(svn_diff_output(diff
, &diff_baton
, &output_fns
));
305 window_handler(svn_txdelta_window_t
*window
, void *baton
)
307 struct delta_baton
*dbaton
= baton
;
308 struct file_rev_baton
*frb
= dbaton
->file_rev_baton
;
309 struct blame_chain
*chain
;
311 /* Call the wrapped handler first. */
312 SVN_ERR(dbaton
->wrapped_handler(window
, dbaton
->wrapped_baton
));
314 /* We patiently wait for the NULL window marking the end. */
318 /* Close the files used for the delta.
319 It is important to do this early, since otherwise, they will be deleted
320 before all handles are closed, which leads to failures on some platforms
321 when new tempfiles are to be created. */
322 if (dbaton
->source_file
)
323 SVN_ERR(svn_io_file_close(dbaton
->source_file
, frb
->currpool
));
324 SVN_ERR(svn_io_file_close(dbaton
->file
, frb
->currpool
));
326 /* If we are including merged revisions, we need to add each rev to the
328 if (frb
->include_merged_revisions
)
329 chain
= frb
->merged_chain
;
333 /* Process this file. */
334 SVN_ERR(add_file_blame(frb
->last_filename
,
335 dbaton
->filename
, chain
, frb
->rev
,
336 frb
->diff_options
, frb
->currpool
));
338 /* If we are including merged revisions, and the current revision is not a
339 merged one, we need to add its blame info to the chain for the original
341 if (frb
->include_merged_revisions
&& ! frb
->merged_revision
)
345 SVN_ERR(add_file_blame(frb
->last_original_filename
,
346 dbaton
->filename
, frb
->chain
, frb
->rev
,
347 frb
->diff_options
, frb
->currpool
));
349 /* This filename could be around for a while, potentially, so
350 use the longer lifetime pool, and switch it with the previous one*/
351 svn_pool_clear(frb
->prevfilepool
);
352 tmppool
= frb
->filepool
;
353 frb
->filepool
= frb
->prevfilepool
;
354 frb
->prevfilepool
= tmppool
;
356 frb
->last_original_filename
= apr_pstrdup(frb
->filepool
,
360 /* Prepare for next revision. */
362 /* Remember the file name so we can diff it with the next revision. */
363 frb
->last_filename
= dbaton
->filename
;
367 apr_pool_t
*tmp_pool
= frb
->lastpool
;
368 frb
->lastpool
= frb
->currpool
;
369 frb
->currpool
= tmp_pool
;
375 /* Throw an SVN_ERR_CLIENT_IS_BINARY_FILE error if PROP_DIFFS indicates a
376 binary MIME type. Else, return SVN_NO_ERROR. */
378 check_mimetype(apr_array_header_t
*prop_diffs
, const char *target
,
383 for (i
= 0; i
< prop_diffs
->nelts
; ++i
)
385 const svn_prop_t
*prop
= &APR_ARRAY_IDX(prop_diffs
, i
, svn_prop_t
);
386 if (strcmp(prop
->name
, SVN_PROP_MIME_TYPE
) == 0
388 && svn_mime_type_is_binary(prop
->value
->data
))
389 return svn_error_createf
390 (SVN_ERR_CLIENT_IS_BINARY_FILE
, 0,
391 _("Cannot calculate blame information for binary file '%s'"),
392 svn_path_local_style(target
, pool
));
399 file_rev_handler(void *baton
, const char *path
, svn_revnum_t revnum
,
400 apr_hash_t
*rev_props
,
401 svn_boolean_t merged_revision
,
402 svn_txdelta_window_handler_t
*content_delta_handler
,
403 void **content_delta_baton
,
404 apr_array_header_t
*prop_diffs
,
407 struct file_rev_baton
*frb
= baton
;
408 svn_stream_t
*last_stream
;
409 svn_stream_t
*cur_stream
;
410 struct delta_baton
*delta_baton
;
411 apr_pool_t
*filepool
;
413 /* Clear the current pool. */
414 svn_pool_clear(frb
->currpool
);
416 /* If this file has a non-textual mime-type, bail out. */
417 if (! frb
->ignore_mime_type
)
418 SVN_ERR(check_mimetype(prop_diffs
, frb
->target
, frb
->currpool
));
420 if (frb
->ctx
->notify_func2
)
422 svn_wc_notify_t
*notify
423 = svn_wc_create_notify(path
, svn_wc_notify_blame_revision
, pool
);
424 notify
->kind
= svn_node_none
;
425 notify
->content_state
= notify
->prop_state
426 = svn_wc_notify_state_inapplicable
;
427 notify
->lock_state
= svn_wc_notify_lock_state_inapplicable
;
428 notify
->revision
= revnum
;
429 frb
->ctx
->notify_func2(frb
->ctx
->notify_baton2
, notify
, pool
);
432 if (frb
->ctx
->cancel_func
)
433 SVN_ERR(frb
->ctx
->cancel_func(frb
->ctx
->cancel_baton
));
435 /* If there were no content changes, we couldn't care less about this
436 revision now. Note that we checked the mime type above, so things
437 work if the user just changes the mime type in a commit.
438 Also note that we don't switch the pools in this case. This is important,
439 since the tempfile will be removed by the pool and we need the tempfile
440 from the last revision with content changes. */
441 if (!content_delta_handler
)
444 frb
->merged_revision
= merged_revision
;
446 /* Create delta baton. */
447 delta_baton
= apr_palloc(frb
->currpool
, sizeof(*delta_baton
));
449 /* Prepare the text delta window handler. */
450 if (frb
->last_filename
)
451 SVN_ERR(svn_io_file_open(&delta_baton
->source_file
, frb
->last_filename
,
452 APR_READ
, APR_OS_DEFAULT
, frb
->currpool
));
454 /* Means empty stream below. */
455 delta_baton
->source_file
= NULL
;
456 last_stream
= svn_stream_from_aprfile(delta_baton
->source_file
, pool
);
458 if (frb
->include_merged_revisions
&& !frb
->merged_revision
)
459 filepool
= frb
->filepool
;
461 filepool
= frb
->currpool
;
463 SVN_ERR(svn_io_open_unique_file2(&delta_baton
->file
,
464 &delta_baton
->filename
,
466 ".tmp", svn_io_file_del_on_pool_cleanup
,
468 cur_stream
= svn_stream_from_aprfile(delta_baton
->file
, frb
->currpool
);
470 /* Get window handler for applying delta. */
471 svn_txdelta_apply(last_stream
, cur_stream
, NULL
, NULL
,
473 &delta_baton
->wrapped_handler
,
474 &delta_baton
->wrapped_baton
);
476 /* Wrap the window handler with our own. */
477 delta_baton
->file_rev_baton
= frb
;
478 *content_delta_handler
= window_handler
;
479 *content_delta_baton
= delta_baton
;
481 /* Create the rev structure. */
482 frb
->rev
= apr_palloc(frb
->mainpool
, sizeof(struct rev
));
484 if (revnum
< frb
->start_rev
)
486 /* We shouldn't get more than one revision before start. */
487 assert(frb
->last_filename
== NULL
);
489 /* The file existed before start_rev; generate no blame info for
490 lines from this revision (or before). */
491 frb
->rev
->revision
= SVN_INVALID_REVNUM
;
492 frb
->rev
->author
= NULL
;
493 frb
->rev
->date
= NULL
;
498 assert(revnum
<= frb
->end_rev
);
500 /* Set values from revision props. */
501 frb
->rev
->revision
= revnum
;
503 if ((str
= apr_hash_get(rev_props
, SVN_PROP_REVISION_AUTHOR
,
504 sizeof(SVN_PROP_REVISION_AUTHOR
) - 1)))
505 frb
->rev
->author
= apr_pstrdup(frb
->mainpool
, str
->data
);
507 frb
->rev
->author
= NULL
;
509 if ((str
= apr_hash_get(rev_props
, SVN_PROP_REVISION_DATE
,
510 sizeof(SVN_PROP_REVISION_DATE
) - 1)))
511 frb
->rev
->date
= apr_pstrdup(frb
->mainpool
, str
->data
);
513 frb
->rev
->date
= NULL
;
516 if (frb
->include_merged_revisions
)
517 frb
->rev
->path
= apr_pstrdup(frb
->mainpool
, path
);
522 /* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks,
523 and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the
524 same starting value. Both CHAIN_ORIG and CHAIN_MERGED should not be
527 normalize_blames(struct blame_chain
*chain
,
528 struct blame_chain
*chain_merged
,
531 struct blame
*walk
, *walk_merged
;
533 /* Walk over the CHAIN's blame chunks and CHAIN_MERGED's blame chunks,
534 creating new chunks as needed. */
535 for (walk
= chain
->blame
, walk_merged
= chain_merged
->blame
;
536 walk
->next
&& walk_merged
->next
;
537 walk
= walk
->next
, walk_merged
= walk_merged
->next
)
539 /* The current chunks should always be starting at the same offset. */
540 assert(walk
->start
== walk_merged
->start
);
542 if (walk
->next
->start
< walk_merged
->next
->start
)
544 struct blame
*tmp
= blame_create(chain_merged
, walk_merged
->next
->rev
,
546 tmp
->next
= walk_merged
->next
->next
;
547 walk_merged
->next
= tmp
;
550 if (walk
->next
->start
> walk_merged
->next
->start
)
552 struct blame
*tmp
= blame_create(chain
, walk
->next
->rev
,
553 walk_merged
->next
->start
);
554 tmp
->next
= walk
->next
->next
;
559 /* If both next pointers are null, we have an equally long list. */
560 if (walk
->next
== NULL
&& walk_merged
->next
== NULL
)
563 if (walk_merged
->next
== NULL
)
565 /* Make new walk_merged chunks as needed at the end of the list so that
566 the length matches that of walk. */
567 while (walk
->next
!= NULL
)
569 struct blame
*tmp
= blame_create(chain_merged
, walk_merged
->rev
,
571 walk_merged
->next
= tmp
;
572 walk_merged
= walk_merged
->next
;
577 if (walk
->next
== NULL
)
579 /* Same as above, only create walk chunks as needed. */
580 while (walk_merged
->next
!= NULL
)
582 struct blame
*tmp
= blame_create(chain
, walk
->rev
,
583 walk_merged
->next
->start
);
586 walk_merged
= walk_merged
->next
;
592 svn_client_blame4(const char *target
,
593 const svn_opt_revision_t
*peg_revision
,
594 const svn_opt_revision_t
*start
,
595 const svn_opt_revision_t
*end
,
596 const svn_diff_file_options_t
*diff_options
,
597 svn_boolean_t ignore_mime_type
,
598 svn_boolean_t include_merged_revisions
,
599 svn_client_blame_receiver2_t receiver
,
600 void *receiver_baton
,
601 svn_client_ctx_t
*ctx
,
604 struct file_rev_baton frb
;
605 svn_ra_session_t
*ra_session
;
607 svn_revnum_t start_revnum
, end_revnum
;
608 struct blame
*walk
, *walk_merged
= NULL
;
610 apr_pool_t
*iterpool
;
611 svn_stream_t
*stream
;
613 if (start
->kind
== svn_opt_revision_unspecified
614 || end
->kind
== svn_opt_revision_unspecified
)
615 return svn_error_create
616 (SVN_ERR_CLIENT_BAD_REVISION
, NULL
, NULL
);
617 else if (start
->kind
== svn_opt_revision_working
618 || end
->kind
== svn_opt_revision_working
)
619 return svn_error_create
620 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
621 _("blame of the WORKING revision is not supported"));
623 /* Get an RA plugin for this filesystem object. */
624 SVN_ERR(svn_client__ra_session_from_path(&ra_session
, &end_revnum
,
629 SVN_ERR(svn_client__get_revision_number(&start_revnum
, NULL
, ra_session
,
630 start
, target
, pool
));
632 if (end_revnum
< start_revnum
)
633 return svn_error_create
634 (SVN_ERR_CLIENT_BAD_REVISION
, NULL
,
635 _("Start revision must precede end revision"));
637 frb
.start_rev
= start_revnum
;
638 frb
.end_rev
= end_revnum
;
641 frb
.diff_options
= diff_options
;
642 frb
.ignore_mime_type
= ignore_mime_type
;
643 frb
.include_merged_revisions
= include_merged_revisions
;
644 frb
.last_filename
= NULL
;
645 frb
.last_original_filename
= NULL
;
646 frb
.chain
= apr_palloc(pool
, sizeof(*frb
.chain
));
647 frb
.chain
->blame
= NULL
;
648 frb
.chain
->avail
= NULL
;
649 frb
.chain
->pool
= pool
;
650 if (include_merged_revisions
)
652 frb
.merged_chain
= apr_palloc(pool
, sizeof(*frb
.merged_chain
));
653 frb
.merged_chain
->blame
= NULL
;
654 frb
.merged_chain
->avail
= NULL
;
655 frb
.merged_chain
->pool
= pool
;
658 SVN_ERR(svn_io_temp_dir(&frb
.tmp_path
, pool
));
659 frb
.tmp_path
= svn_path_join(frb
.tmp_path
, "tmp", 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 /* Report the blame to the caller. */
684 /* The callback has to have been called at least once. */
685 assert(frb
.last_filename
!= NULL
);
687 /* Create a pool for the iteration below. */
688 iterpool
= svn_pool_create(pool
);
690 /* Open the last file and get a stream. */
691 SVN_ERR(svn_io_file_open(&file
, frb
.last_filename
, APR_READ
| APR_BUFFERED
,
692 APR_OS_DEFAULT
, pool
));
693 stream
= svn_subst_stream_translated(svn_stream_from_aprfile(file
, pool
),
694 "\n", TRUE
, NULL
, FALSE
, pool
);
696 /* Perform optional merged chain normalization. */
697 if (include_merged_revisions
)
699 /* If we never created any blame for the original chain, create it now,
700 with the most recent changed revision. This could occur if a file
701 was created on a branch and them merged to another branch. This is
702 semanticly a copy, and we want to use the revision on the branch as
703 the most recently changed revision. ### Is this really what we want
704 to do here? Do the sematics of copy change? */
705 if (!frb
.chain
->blame
)
706 frb
.chain
->blame
= blame_create(frb
.chain
, frb
.rev
, 0);
708 normalize_blames(frb
.chain
, frb
.merged_chain
, pool
);
709 walk_merged
= frb
.merged_chain
->blame
;
712 /* Process each blame item. */
713 for (walk
= frb
.chain
->blame
; walk
; walk
= walk
->next
)
716 svn_revnum_t merged_rev
;
717 const char *merged_author
, *merged_date
, *merged_path
;
721 merged_rev
= walk_merged
->rev
->revision
;
722 merged_author
= walk_merged
->rev
->author
;
723 merged_date
= walk_merged
->rev
->date
;
724 merged_path
= walk_merged
->rev
->path
;
728 merged_rev
= SVN_INVALID_REVNUM
;
729 merged_author
= NULL
;
734 for (line_no
= walk
->start
;
735 !walk
->next
|| line_no
< walk
->next
->start
;
741 svn_pool_clear(iterpool
);
742 SVN_ERR(svn_stream_readline(stream
, &sb
, "\n", &eof
, iterpool
));
743 if (ctx
->cancel_func
)
744 SVN_ERR(ctx
->cancel_func(ctx
->cancel_baton
));
746 SVN_ERR(receiver(receiver_baton
, line_no
, walk
->rev
->revision
,
747 walk
->rev
->author
, walk
->rev
->date
,
748 merged_rev
, merged_author
, merged_date
,
749 merged_path
, sb
->data
, iterpool
));
754 walk_merged
= walk_merged
->next
;
757 SVN_ERR(svn_stream_close(stream
));
759 /* We don't need the temp file any more. */
760 SVN_ERR(svn_io_file_close(file
, pool
));
762 svn_pool_destroy(frb
.lastpool
);
763 svn_pool_destroy(frb
.currpool
);
764 if (include_merged_revisions
)
766 svn_pool_destroy(frb
.filepool
);
767 svn_pool_destroy(frb
.prevfilepool
);
769 svn_pool_destroy(iterpool
);
774 /* Baton for use with wrap_blame_receiver */
775 struct blame_receiver_wrapper_baton
{
777 svn_client_blame_receiver_t receiver
;
780 /* This implements svn_client_blame_receiver2_t */
782 blame_wrapper_receiver(void *baton
,
784 svn_revnum_t revision
,
787 svn_revnum_t merged_revision
,
788 const char *merged_author
,
789 const char *merged_date
,
790 const char *merged_path
,
794 struct blame_receiver_wrapper_baton
*brwb
= baton
;
797 return brwb
->receiver(brwb
->baton
,
798 line_no
, revision
, author
, date
, line
, pool
);
804 wrap_blame_receiver(svn_client_blame_receiver2_t
*receiver2
,
805 void **receiver2_baton
,
806 svn_client_blame_receiver_t receiver
,
807 void *receiver_baton
,
810 struct blame_receiver_wrapper_baton
*brwb
= apr_palloc(pool
, sizeof(*brwb
));
812 /* Set the user provided old format callback in the baton. */
813 brwb
->baton
= receiver_baton
;
814 brwb
->receiver
= receiver
;
816 *receiver2_baton
= brwb
;
817 *receiver2
= blame_wrapper_receiver
;
821 svn_client_blame3(const char *target
,
822 const svn_opt_revision_t
*peg_revision
,
823 const svn_opt_revision_t
*start
,
824 const svn_opt_revision_t
*end
,
825 const svn_diff_file_options_t
*diff_options
,
826 svn_boolean_t ignore_mime_type
,
827 svn_client_blame_receiver_t receiver
,
828 void *receiver_baton
,
829 svn_client_ctx_t
*ctx
,
832 svn_client_blame_receiver2_t receiver2
;
833 void *receiver2_baton
;
835 wrap_blame_receiver(&receiver2
, &receiver2_baton
, receiver
, receiver_baton
,
838 return svn_client_blame4(target
, peg_revision
, start
, end
, diff_options
,
839 ignore_mime_type
, FALSE
, receiver2
, receiver2_baton
,
843 /* svn_client_blame3 guarantees 'no EOL chars' as part of the receiver
844 LINE argument. Older versions depend on the fact that if a CR is
845 required, that CR is already part of the LINE data.
847 Because of this difference, we need to trap old receivers and append
848 a CR to LINE before passing it on to the actual receiver on platforms
849 which want CRLF line termination.
853 struct wrapped_receiver_baton_s
855 svn_client_blame_receiver_t orig_receiver
;
860 wrapped_receiver(void *baton
,
862 svn_revnum_t revision
,
868 struct wrapped_receiver_baton_s
*b
= baton
;
869 svn_stringbuf_t
*expanded_line
= svn_stringbuf_create(line
, pool
);
871 svn_stringbuf_appendbytes(expanded_line
, "\r", 1);
873 return b
->orig_receiver(b
->orig_baton
, line_no
, revision
, author
,
874 date
, expanded_line
->data
, pool
);
878 wrap_pre_blame3_receiver(svn_client_blame_receiver_t
*receiver
,
879 void **receiver_baton
,
882 if (strlen(APR_EOL_STR
) > 1)
884 struct wrapped_receiver_baton_s
*b
= apr_palloc(pool
,sizeof(*b
));
886 b
->orig_receiver
= *receiver
;
887 b
->orig_baton
= *receiver_baton
;
890 *receiver
= wrapped_receiver
;
895 svn_client_blame2(const char *target
,
896 const svn_opt_revision_t
*peg_revision
,
897 const svn_opt_revision_t
*start
,
898 const svn_opt_revision_t
*end
,
899 svn_client_blame_receiver_t receiver
,
900 void *receiver_baton
,
901 svn_client_ctx_t
*ctx
,
904 wrap_pre_blame3_receiver(&receiver
, &receiver_baton
, pool
);
905 return svn_client_blame3(target
, peg_revision
, start
, end
,
906 svn_diff_file_options_create(pool
), FALSE
,
907 receiver
, receiver_baton
, ctx
, pool
);
910 svn_client_blame(const char *target
,
911 const svn_opt_revision_t
*start
,
912 const svn_opt_revision_t
*end
,
913 svn_client_blame_receiver_t receiver
,
914 void *receiver_baton
,
915 svn_client_ctx_t
*ctx
,
918 wrap_pre_blame3_receiver(&receiver
, &receiver_baton
, pool
);
919 return svn_client_blame2(target
, end
, start
, end
,
920 receiver
, receiver_baton
, ctx
, pool
);