2 * log.c: return log messages
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 #define APR_WANT_STRFUNC
27 #include <apr_strings.h>
28 #include <apr_pools.h>
30 #include "svn_pools.h"
31 #include "svn_client.h"
32 #include "svn_compat.h"
33 #include "svn_error.h"
34 #include "svn_dirent_uri.h"
37 #include "svn_sorts.h"
38 #include "svn_props.h"
42 #include "svn_private_config.h"
43 #include "private/svn_wc_private.h"
47 /*** Getting misc. information ***/
49 /* The baton for use with copyfrom_info_receiver(). */
50 typedef struct copyfrom_info_t
52 svn_boolean_t is_first;
58 /* A location segment callback for obtaining the copy source of
59 a node at a path and storing it in *BATON (a struct copyfrom_info_t *).
60 Implements svn_location_segment_receiver_t. */
62 copyfrom_info_receiver(svn_location_segment_t *segment,
66 copyfrom_info_t *copyfrom_info = baton;
68 /* If we've already identified the copy source, there's nothing more
70 ### FIXME: We *should* be able to send */
71 if (copyfrom_info->path)
74 /* If this is the first segment, it's not of interest to us. Otherwise
75 (so long as this segment doesn't represent a history gap), it holds
76 our path's previous location (from which it was last copied). */
77 if (copyfrom_info->is_first)
79 copyfrom_info->is_first = FALSE;
81 else if (segment->path)
83 /* The end of the second non-gap segment is the location copied from. */
84 copyfrom_info->path = apr_pstrdup(copyfrom_info->pool, segment->path);
85 copyfrom_info->rev = segment->range_end;
87 /* ### FIXME: We *should* be able to return SVN_ERR_CEASE_INVOCATION
88 ### here so we don't get called anymore. */
95 svn_client__get_copy_source(const char **original_repos_relpath,
96 svn_revnum_t *original_revision,
97 const char *path_or_url,
98 const svn_opt_revision_t *revision,
99 svn_client_ctx_t *ctx,
100 apr_pool_t *result_pool,
101 apr_pool_t *scratch_pool)
104 copyfrom_info_t copyfrom_info = { 0 };
105 apr_pool_t *sesspool = svn_pool_create(scratch_pool);
106 svn_ra_session_t *ra_session;
107 svn_client__pathrev_t *at_loc;
109 copyfrom_info.is_first = TRUE;
110 copyfrom_info.path = NULL;
111 copyfrom_info.rev = SVN_INVALID_REVNUM;
112 copyfrom_info.pool = result_pool;
114 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &at_loc,
119 /* Find the copy source. Walk the location segments to find the revision
120 at which this node was created (copied or added). */
122 err = svn_ra_get_location_segments(ra_session, "", at_loc->rev, at_loc->rev,
124 copyfrom_info_receiver, ©from_info,
127 svn_pool_destroy(sesspool);
131 if (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
132 err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
134 /* A locally-added but uncommitted versioned resource won't
135 exist in the repository. */
136 svn_error_clear(err);
139 *original_repos_relpath = NULL;
140 *original_revision = SVN_INVALID_REVNUM;
142 return svn_error_trace(err);
145 *original_repos_relpath = copyfrom_info.path;
146 *original_revision = copyfrom_info.rev;
151 /* compatibility with pre-1.5 servers, which send only author/date/log
152 *revprops in log entries */
153 typedef struct pre_15_receiver_baton_t
155 svn_client_ctx_t *ctx;
156 /* ra session for retrieving revprops from old servers */
157 svn_ra_session_t *ra_session;
158 /* caller's list of requested revprops, receiver, and baton */
159 const char *ra_session_url;
160 apr_pool_t *ra_session_pool;
161 const apr_array_header_t *revprops;
162 svn_log_entry_receiver_t receiver;
164 } pre_15_receiver_baton_t;
167 pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
169 pre_15_receiver_baton_t *rb = baton;
171 if (log_entry->revision == SVN_INVALID_REVNUM)
172 return rb->receiver(rb->baton, log_entry, pool);
174 /* If only some revprops are requested, get them one at a time on the
175 second ra connection. If all are requested, get them all with
176 svn_ra_rev_proplist. This avoids getting unrequested revprops (which
177 may be arbitrarily large), but means one round-trip per requested
178 revprop. epg isn't entirely sure which should be optimized for. */
182 svn_boolean_t want_author, want_date, want_log;
183 want_author = want_date = want_log = FALSE;
184 for (i = 0; i < rb->revprops->nelts; i++)
186 const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *);
189 /* If a standard revprop is requested, we know it is already in
190 log_entry->revprops if available. */
191 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
196 if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
201 if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
207 if (rb->ra_session == NULL)
208 SVN_ERR(svn_client_open_ra_session2(&rb->ra_session,
209 rb->ra_session_url, NULL,
210 rb->ctx, rb->ra_session_pool,
213 SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision,
214 name, &value, pool));
215 if (log_entry->revprops == NULL)
216 log_entry->revprops = apr_hash_make(pool);
217 svn_hash_sets(log_entry->revprops, name, value);
219 if (log_entry->revprops)
221 /* Pre-1.5 servers send the standard revprops unconditionally;
222 clear those the caller doesn't want. */
224 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, NULL);
226 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, NULL);
228 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, NULL);
233 if (rb->ra_session == NULL)
234 SVN_ERR(svn_client_open_ra_session2(&rb->ra_session,
235 rb->ra_session_url, NULL,
236 rb->ctx, rb->ra_session_pool,
239 SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision,
240 &log_entry->revprops, pool));
243 return rb->receiver(rb->baton, log_entry, pool);
247 typedef struct limit_receiver_baton_t
250 svn_log_entry_receiver_t receiver;
252 } limit_receiver_baton_t;
255 limit_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
257 limit_receiver_baton_t *rb = baton;
261 return rb->receiver(rb->baton, log_entry, pool);
264 /* Resolve the URLs or WC path in TARGETS as per the svn_client_log5 API.
266 The limitations on TARGETS specified by svn_client_log5 are enforced here.
267 So TARGETS can only contain a single WC path or a URL and zero or more
268 relative paths -- anything else will raise an error.
270 PEG_REVISION, TARGETS, and CTX are as per svn_client_log5.
272 If TARGETS contains a single WC path then set *RA_TARGET to the absolute
273 path of that single path if PEG_REVISION is dependent on the working copy
274 (e.g. PREV). Otherwise set *RA_TARGET to the corresponding URL for the
275 single WC path. Set *RELATIVE_TARGETS to an array with a single
278 If TARGETS contains only a single URL, then set *RA_TARGET to a copy of
279 that URL and *RELATIVE_TARGETS to an array with a single element "".
281 If TARGETS contains a single URL and one or more relative paths, then
282 set *RA_TARGET to a copy of that URL and *RELATIVE_TARGETS to a copy of
283 each relative path after the URL.
285 If *PEG_REVISION is svn_opt_revision_unspecified, then *PEG_REVISION is
286 set to svn_opt_revision_head for URLs or svn_opt_revision_working for a
289 *RA_TARGET and *RELATIVE_TARGETS are allocated in RESULT_POOL. */
291 resolve_log_targets(apr_array_header_t **relative_targets,
292 const char **ra_target,
293 svn_opt_revision_t *peg_revision,
294 const apr_array_header_t *targets,
295 svn_client_ctx_t *ctx,
296 apr_pool_t *result_pool,
297 apr_pool_t *scratch_pool)
300 svn_boolean_t url_targets;
302 /* Per svn_client_log5, TARGETS contains either a URL followed by zero or
303 more relative paths, or one working copy path. */
304 const char *url_or_path = APR_ARRAY_IDX(targets, 0, const char *);
306 /* svn_client_log5 requires at least one target. */
307 if (targets->nelts == 0)
308 return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
309 _("No valid target found"));
311 /* Initialize the output array. At a minimum, we need room for one
312 (possibly empty) relpath. Otherwise, we have to hold a relpath
313 for every item in TARGETS except the first. */
314 *relative_targets = apr_array_make(result_pool,
315 MAX(1, targets->nelts - 1),
316 sizeof(const char *));
318 if (svn_path_is_url(url_or_path))
320 /* An unspecified PEG_REVISION for a URL path defaults
321 to svn_opt_revision_head. */
322 if (peg_revision->kind == svn_opt_revision_unspecified)
323 peg_revision->kind = svn_opt_revision_head;
325 /* The logic here is this: If we get passed one argument, we assume
326 it is the full URL to a file/dir we want log info for. If we get
327 a URL plus some paths, then we assume that the URL is the base,
328 and that the paths passed are relative to it. */
329 if (targets->nelts > 1)
331 /* We have some paths, let's use them. Start after the URL. */
332 for (i = 1; i < targets->nelts; i++)
336 target = APR_ARRAY_IDX(targets, i, const char *);
338 if (svn_path_is_url(target) || svn_dirent_is_absolute(target))
339 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
340 _("'%s' is not a relative path"),
343 APR_ARRAY_PUSH(*relative_targets, const char *) =
344 apr_pstrdup(result_pool, target);
349 /* If we have a single URL, then the session will be rooted at
350 it, so just send an empty string for the paths we are
352 APR_ARRAY_PUSH(*relative_targets, const char *) = "";
355 /* Remember that our targets are URLs. */
358 else /* WC path target. */
361 const char *target_abspath;
364 if (targets->nelts > 1)
365 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
366 _("When specifying working copy paths, only "
367 "one target may be given"));
369 /* An unspecified PEG_REVISION for a working copy path defaults
370 to svn_opt_revision_working. */
371 if (peg_revision->kind == svn_opt_revision_unspecified)
372 peg_revision->kind = svn_opt_revision_working;
374 /* Get URLs for each target */
375 target = APR_ARRAY_IDX(targets, 0, const char *);
377 SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, scratch_pool));
378 SVN_ERR(svn_wc__node_get_url(&url_or_path, ctx->wc_ctx, target_abspath,
379 scratch_pool, scratch_pool));
380 APR_ARRAY_PUSH(*relative_targets, const char *) = "";
383 /* If this is a revision type that requires access to the working copy,
384 * we use our initial target path to figure out where to root the RA
385 * session, otherwise we use our URL. */
386 if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
389 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
390 _("PREV, BASE, or COMMITTED revision "
391 "keywords are invalid for URL"));
394 SVN_ERR(svn_dirent_get_absolute(
395 ra_target, APR_ARRAY_IDX(targets, 0, const char *), result_pool));
399 *ra_target = apr_pstrdup(result_pool, url_or_path);
405 /* Keep track of oldest and youngest opt revs found.
407 If REV is younger than *YOUNGEST_REV, or *YOUNGEST_REV is
408 svn_opt_revision_unspecified, then set *YOUNGEST_REV equal to REV.
410 If REV is older than *OLDEST_REV, or *OLDEST_REV is
411 svn_opt_revision_unspecified, then set *OLDEST_REV equal to REV. */
413 find_youngest_and_oldest_revs(svn_revnum_t *youngest_rev,
414 svn_revnum_t *oldest_rev,
417 /* Is REV younger than YOUNGEST_REV? */
418 if (! SVN_IS_VALID_REVNUM(*youngest_rev)
419 || rev > *youngest_rev)
422 if (! SVN_IS_VALID_REVNUM(*oldest_rev)
423 || rev < *oldest_rev)
427 typedef struct rev_range_t
429 svn_revnum_t range_start;
430 svn_revnum_t range_end;
433 /* Convert array of svn_opt_revision_t ranges to an array of svn_revnum_t
436 Given a log target URL_OR_ABSPATH@PEG_REV and an array of
437 svn_opt_revision_range_t's OPT_REV_RANGES, resolve the opt revs in
438 OPT_REV_RANGES to svn_revnum_t's and return these in *REVISION_RANGES, an
439 array of rev_range_t *.
441 Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions
442 found in *REVISION_RANGES.
444 If the repository needs to be contacted to resolve svn_opt_revision_date or
445 svn_opt_revision_head revisions, then the session used to do this is
446 RA_SESSION; it must be an open session to any URL in the right repository.
449 convert_opt_rev_array_to_rev_range_array(
450 apr_array_header_t **revision_ranges,
451 svn_revnum_t *youngest_rev,
452 svn_revnum_t *oldest_rev,
453 svn_ra_session_t *ra_session,
454 const char *url_or_abspath,
455 const apr_array_header_t *opt_rev_ranges,
456 const svn_opt_revision_t *peg_rev,
457 svn_client_ctx_t *ctx,
458 apr_pool_t *result_pool,
459 apr_pool_t *scratch_pool)
462 svn_revnum_t head_rev = SVN_INVALID_REVNUM;
464 /* Initialize the input/output parameters. */
465 *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM;
467 /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
468 and oldest revision range that spans all of OPT_REV_RANGES. */
469 *revision_ranges = apr_array_make(result_pool, opt_rev_ranges->nelts,
470 sizeof(rev_range_t *));
472 for (i = 0; i < opt_rev_ranges->nelts; i++)
474 svn_opt_revision_range_t *range;
475 rev_range_t *rev_range;
476 svn_boolean_t start_same_as_end = FALSE;
478 range = APR_ARRAY_IDX(opt_rev_ranges, i, svn_opt_revision_range_t *);
480 /* Right now RANGE can be any valid pair of svn_opt_revision_t's. We
481 will now convert all RANGEs in place to the corresponding
482 svn_opt_revision_number kind. */
483 if ((range->start.kind != svn_opt_revision_unspecified)
484 && (range->end.kind == svn_opt_revision_unspecified))
486 /* If the user specified exactly one revision, then start rev is
487 * set but end is not. We show the log message for just that
488 * revision by making end equal to start.
490 * Note that if the user requested a single dated revision, then
491 * this will cause the same date to be resolved twice. The
492 * extra code complexity to get around this slight inefficiency
493 * doesn't seem worth it, however. */
494 range->end = range->start;
496 else if (range->start.kind == svn_opt_revision_unspecified)
498 /* Default to any specified peg revision. Otherwise, if the
499 * first target is a URL, then we default to HEAD:0. Lastly,
500 * the default is BASE:0 since WC@HEAD may not exist. */
501 if (peg_rev->kind == svn_opt_revision_unspecified)
503 if (svn_path_is_url(url_or_abspath))
504 range->start.kind = svn_opt_revision_head;
506 range->start.kind = svn_opt_revision_base;
509 range->start = *peg_rev;
511 if (range->end.kind == svn_opt_revision_unspecified)
513 range->end.kind = svn_opt_revision_number;
514 range->end.value.number = 0;
518 if ((range->start.kind == svn_opt_revision_unspecified)
519 || (range->end.kind == svn_opt_revision_unspecified))
521 return svn_error_create
522 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
523 _("Missing required revision specification"));
526 /* Does RANGE describe a single svn_opt_revision_t? */
527 if (range->start.kind == range->end.kind)
529 if (range->start.kind == svn_opt_revision_number)
531 if (range->start.value.number == range->end.value.number)
532 start_same_as_end = TRUE;
534 else if (range->start.kind == svn_opt_revision_date)
536 if (range->start.value.date == range->end.value.date)
537 start_same_as_end = TRUE;
541 start_same_as_end = TRUE;
545 rev_range = apr_palloc(result_pool, sizeof(*rev_range));
546 SVN_ERR(svn_client__get_revision_number(
547 &rev_range->range_start, &head_rev,
548 ctx->wc_ctx, url_or_abspath, ra_session,
549 &range->start, scratch_pool));
550 if (start_same_as_end)
551 rev_range->range_end = rev_range->range_start;
553 SVN_ERR(svn_client__get_revision_number(
554 &rev_range->range_end, &head_rev,
555 ctx->wc_ctx, url_or_abspath, ra_session,
556 &range->end, scratch_pool));
558 /* Possibly update the oldest and youngest revisions requested. */
559 find_youngest_and_oldest_revs(youngest_rev,
561 rev_range->range_start);
562 find_youngest_and_oldest_revs(youngest_rev,
564 rev_range->range_end);
565 APR_ARRAY_PUSH(*revision_ranges, rev_range_t *) = rev_range;
572 compare_rev_to_segment(const void *key_p,
573 const void *element_p)
576 * (svn_revnum_t *)key_p;
577 const svn_location_segment_t *segment =
578 *((const svn_location_segment_t * const *) element_p);
580 if (rev < segment->range_start)
582 else if (rev > segment->range_end)
588 /* Run svn_ra_get_log2 for PATHS, one or more paths relative to RA_SESSION's
589 common parent, for each revision in REVISION_RANGES, an array of
592 RA_SESSION is an open session pointing to ACTUAL_LOC.
594 LOG_SEGMENTS is an array of svn_location_segment_t * items representing the
595 history of PATHS from the oldest to youngest revisions found in
598 The TARGETS, LIMIT, DISCOVER_CHANGED_PATHS, STRICT_NODE_HISTORY,
599 INCLUDE_MERGED_REVISIONS, REVPROPS, REAL_RECEIVER, and REAL_RECEIVER_BATON
600 parameters are all as per the svn_client_log5 API. */
602 run_ra_get_log(apr_array_header_t *revision_ranges,
603 apr_array_header_t *paths,
604 apr_array_header_t *log_segments,
605 svn_client__pathrev_t *actual_loc,
606 svn_ra_session_t *ra_session,
607 /* The following are as per svn_client_log5. */
608 const apr_array_header_t *targets,
610 svn_boolean_t discover_changed_paths,
611 svn_boolean_t strict_node_history,
612 svn_boolean_t include_merged_revisions,
613 const apr_array_header_t *revprops,
614 svn_log_entry_receiver_t real_receiver,
615 void *real_receiver_baton,
616 svn_client_ctx_t *ctx,
617 apr_pool_t *scratch_pool)
620 pre_15_receiver_baton_t rb = {0};
621 apr_pool_t *iterpool;
622 svn_boolean_t has_log_revprops;
624 SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops,
625 SVN_RA_CAPABILITY_LOG_REVPROPS,
628 if (!has_log_revprops)
630 /* See above pre-1.5 notes. */
633 /* Create ra session on first use */
634 rb.ra_session_pool = scratch_pool;
635 rb.ra_session_url = actual_loc->url;
638 /* It's a bit complex to correctly handle the special revision words
639 * such as "BASE", "COMMITTED", and "PREV". For example, if the
642 * $ svn log -rCOMMITTED foo.txt bar.c
644 * which committed rev should be used? The younger of the two? The
645 * first one? Should we just error?
647 * None of the above, I think. Rather, the committed rev of each
648 * target in turn should be used. This is what most users would
649 * expect, and is the most useful interpretation. Of course, this
650 * goes for the other dynamic (i.e., local) revision words too.
652 * Note that the code to do this is a bit more complex than a simple
653 * loop, because the user might run
655 * $ svn log -rCOMMITTED:42 foo.txt bar.c
657 * in which case we want to avoid recomputing the static revision on
660 * ### FIXME: However, we can't yet handle multiple wc targets anyway.
662 * We used to iterate over each target in turn, getting the logs for
663 * the named range. This led to revisions being printed in strange
664 * order or being printed more than once. This is issue 1550.
666 * In r851673, jpieper blocked multiple wc targets in svn/log-cmd.c,
667 * meaning this block not only doesn't work right in that case, but isn't
668 * even testable that way (svn has no unit test suite; we can only test
669 * via the svn command). So, that check is now moved into this function
672 * kfogel ponders future enhancements in r844260:
673 * I think that's okay behavior, since the sense of the command is
674 * that one wants a particular range of logs for *this* file, then
675 * another range for *that* file, and so on. But we should
676 * probably put some sort of separator header between the log
677 * groups. Of course, libsvn_client can't just print stuff out --
678 * it has to take a callback from the client to do that. So we
679 * need to define that callback interface, then have the command
680 * line client pass one down here.
682 * epg wonders if the repository could send a unified stream of log
683 * entries if the paths and revisions were passed down.
685 iterpool = svn_pool_create(scratch_pool);
686 for (i = 0; i < revision_ranges->nelts; i++)
688 const char *old_session_url;
689 const char *path = APR_ARRAY_IDX(targets, 0, const char *);
690 const char *local_abspath_or_url;
692 limit_receiver_baton_t lb;
693 svn_log_entry_receiver_t passed_receiver;
694 void *passed_receiver_baton;
695 const apr_array_header_t *passed_receiver_revprops;
696 svn_location_segment_t **matching_segment;
697 svn_revnum_t younger_rev;
699 svn_pool_clear(iterpool);
701 if (!svn_path_is_url(path))
702 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path,
705 local_abspath_or_url = path;
707 range = APR_ARRAY_IDX(revision_ranges, i, rev_range_t *);
709 /* Issue #4355: Account for renames spanning requested
711 younger_rev = MAX(range->range_start, range->range_end);
712 matching_segment = bsearch(&younger_rev, log_segments->elts,
713 log_segments->nelts, log_segments->elt_size,
714 compare_rev_to_segment);
715 /* LOG_SEGMENTS is supposed to represent the history of PATHS from
716 the oldest to youngest revs in REVISION_RANGES. This function's
717 current sole caller svn_client_log5 *should* be providing
718 LOG_SEGMENTS that span the oldest to youngest revs in
719 REVISION_RANGES, even if one or more of the svn_location_segment_t's
720 returned have NULL path members indicating a gap in the history. So
721 MATCHING_SEGMENT should never be NULL, but clearly sometimes it is,
722 see http://svn.haxx.se/dev/archive-2013-06/0522.shtml
723 So to be safe we handle that case. */
724 if (matching_segment == NULL)
727 /* A segment with a NULL path means there is gap in the history.
728 We'll just proceed and let svn_ra_get_log2 fail with a useful
730 if ((*matching_segment)->path != NULL)
732 /* ...but if there is history, then we must account for issue
733 #4355 and make sure our RA session is pointing at the correct
735 const char *segment_url = svn_path_url_add_component2(
736 actual_loc->repos_root_url, (*matching_segment)->path,
738 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
744 if (has_log_revprops)
746 passed_receiver = real_receiver;
747 passed_receiver_baton = real_receiver_baton;
748 passed_receiver_revprops = revprops;
752 rb.revprops = revprops;
753 rb.receiver = real_receiver;
754 rb.baton = real_receiver_baton;
756 passed_receiver = pre_15_receiver;
757 passed_receiver_baton = &rb;
758 passed_receiver_revprops = svn_compat_log_revprops_in(iterpool);
761 if (limit && revision_ranges->nelts > 1)
764 lb.receiver = passed_receiver;
765 lb.baton = passed_receiver_baton;
767 passed_receiver = limit_receiver;
768 passed_receiver_baton = &lb;
771 SVN_ERR(svn_ra_get_log2(ra_session,
776 discover_changed_paths,
778 include_merged_revisions,
779 passed_receiver_revprops,
781 passed_receiver_baton,
784 if (limit && revision_ranges->nelts > 1)
793 svn_pool_destroy(iterpool);
798 /*** Public Interface. ***/
801 svn_client_log5(const apr_array_header_t *targets,
802 const svn_opt_revision_t *peg_revision,
803 const apr_array_header_t *opt_rev_ranges,
805 svn_boolean_t discover_changed_paths,
806 svn_boolean_t strict_node_history,
807 svn_boolean_t include_merged_revisions,
808 const apr_array_header_t *revprops,
809 svn_log_entry_receiver_t real_receiver,
810 void *real_receiver_baton,
811 svn_client_ctx_t *ctx,
814 svn_ra_session_t *ra_session;
815 const char *old_session_url;
816 const char *ra_target;
817 const char *path_or_url;
818 svn_opt_revision_t youngest_opt_rev;
819 svn_revnum_t youngest_rev;
820 svn_revnum_t oldest_rev;
821 svn_opt_revision_t peg_rev;
822 svn_client__pathrev_t *ra_session_loc;
823 svn_client__pathrev_t *actual_loc;
824 apr_array_header_t *log_segments;
825 apr_array_header_t *revision_ranges;
826 apr_array_header_t *relative_targets;
828 if (opt_rev_ranges->nelts == 0)
830 return svn_error_create
831 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
832 _("Missing required revision specification"));
835 /* Make a copy of PEG_REVISION, we may need to change it to a
837 peg_rev = *peg_revision;
839 SVN_ERR(resolve_log_targets(&relative_targets, &ra_target, &peg_rev,
840 targets, ctx, pool, pool));
842 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &ra_session_loc,
843 ra_target, NULL, &peg_rev, &peg_rev,
846 /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
847 and oldest revision range that spans all of OPT_REV_RANGES. */
848 SVN_ERR(convert_opt_rev_array_to_rev_range_array(&revision_ranges,
853 opt_rev_ranges, &peg_rev,
856 /* For some peg revisions we must resolve revision and url via a local path
857 so use the original RA_TARGET. For others, use the potentially corrected
858 (redirected) ra session URL. */
859 if (peg_rev.kind == svn_opt_revision_previous ||
860 peg_rev.kind == svn_opt_revision_base ||
861 peg_rev.kind == svn_opt_revision_committed ||
862 peg_rev.kind == svn_opt_revision_working)
863 path_or_url = ra_target;
865 path_or_url = ra_session_loc->url;
867 /* Make ACTUAL_LOC and RA_SESSION point to the youngest operative rev. */
868 youngest_opt_rev.kind = svn_opt_revision_number;
869 youngest_opt_rev.value.number = youngest_rev;
870 SVN_ERR(svn_client__resolve_rev_and_url(&actual_loc, ra_session,
871 path_or_url, &peg_rev,
872 &youngest_opt_rev, ctx, pool));
873 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
874 actual_loc->url, pool));
876 /* Save us an RA layer round trip if we are on the repository root and
877 know the result in advance, or if we don't need multiple ranges.
878 All the revision data has already been validated.
880 if (strcmp(actual_loc->url, actual_loc->repos_root_url) == 0
881 || opt_rev_ranges->nelts <= 1)
883 svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
884 log_segments = apr_array_make(pool, 1, sizeof(segment));
886 segment->range_start = oldest_rev;
887 segment->range_end = actual_loc->rev;
888 segment->path = svn_uri_skip_ancestor(actual_loc->repos_root_url,
889 actual_loc->url, pool);
890 APR_ARRAY_PUSH(log_segments, svn_location_segment_t *) = segment;
894 /* Get the svn_location_segment_t's representing the requested log
896 SVN_ERR(svn_client__repos_location_segments(&log_segments, ra_session,
898 actual_loc->rev, /* peg */
899 actual_loc->rev, /* start */
900 oldest_rev, /* end */
905 SVN_ERR(run_ra_get_log(revision_ranges, relative_targets, log_segments,
906 actual_loc, ra_session, targets, limit,
907 discover_changed_paths, strict_node_history,
908 include_merged_revisions, revprops, real_receiver,
909 real_receiver_baton, ctx, pool));