In the command-line client, forbid
[svn.git] / subversion / libsvn_repos / log.c
blob9130260d9a351697ad04ebe5fa30e4fcc8b46067
1 /* log.c --- retrieving log messages
3 * ====================================================================
4 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
19 #include <stdlib.h>
20 #define APR_WANT_STRFUNC
21 #include <apr_want.h>
23 #include "svn_compat.h"
24 #include "svn_private_config.h"
25 #include "svn_pools.h"
26 #include "svn_error.h"
27 #include "svn_path.h"
28 #include "svn_fs.h"
29 #include "svn_repos.h"
30 #include "svn_string.h"
31 #include "svn_sorts.h"
32 #include "svn_props.h"
33 #include "svn_mergeinfo.h"
34 #include "repos.h"
37 static svn_error_t *
38 do_merged_log(svn_fs_t *fs,
39 const char *path,
40 svn_revnum_t rev,
41 svn_boolean_t discover_changed_paths,
42 apr_array_header_t *revprops,
43 svn_boolean_t descending_order,
44 svn_log_entry_receiver_t receiver,
45 void *receiver_baton,
46 svn_repos_authz_func_t authz_read_func,
47 void *authz_read_baton,
48 apr_pool_t *pool);
51 svn_error_t *
52 svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
53 svn_repos_t *repos,
54 svn_revnum_t revision,
55 svn_repos_authz_func_t authz_read_func,
56 void *authz_read_baton,
57 apr_pool_t *pool)
59 svn_fs_t *fs = svn_repos_fs(repos);
60 svn_fs_root_t *rev_root;
61 apr_hash_t *changes;
62 apr_hash_index_t *hi;
63 svn_boolean_t found_readable = FALSE;
64 svn_boolean_t found_unreadable = FALSE;
65 apr_pool_t *subpool;
67 /* By default, we'll grant full read access to REVISION. */
68 *access_level = svn_repos_revision_access_full;
70 /* No auth-checking function? We're done. */
71 if (! authz_read_func)
72 return SVN_NO_ERROR;
74 /* Fetch the changes associated with REVISION. */
75 SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
76 SVN_ERR(svn_fs_paths_changed(&changes, rev_root, pool));
78 /* No changed paths? We're done. */
79 if (apr_hash_count(changes) == 0)
80 return SVN_NO_ERROR;
82 /* Otherwise, we have to check the readability of each changed
83 path, or at least enough to answer the question asked. */
84 subpool = svn_pool_create(pool);
85 for (hi = apr_hash_first(NULL, changes); hi; hi = apr_hash_next(hi))
87 const void *key;
88 void *val;
89 svn_fs_path_change_t *change;
90 svn_boolean_t readable;
92 svn_pool_clear(subpool);
93 apr_hash_this(hi, &key, NULL, &val);
94 change = val;
96 SVN_ERR(authz_read_func(&readable, rev_root, key,
97 authz_read_baton, subpool));
98 if (! readable)
99 found_unreadable = TRUE;
100 else
101 found_readable = TRUE;
103 /* If we have at least one of each (readable/unreadable), we
104 have our answer. */
105 if (found_readable && found_unreadable)
106 goto decision;
108 switch (change->change_kind)
110 case svn_fs_path_change_add:
111 case svn_fs_path_change_replace:
113 const char *copyfrom_path;
114 svn_revnum_t copyfrom_rev;
116 SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
117 rev_root, key, subpool));
118 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
120 svn_fs_root_t *copyfrom_root;
121 SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
122 copyfrom_rev, subpool));
123 SVN_ERR(authz_read_func(&readable,
124 copyfrom_root, copyfrom_path,
125 authz_read_baton, subpool));
126 if (! readable)
127 found_unreadable = TRUE;
129 /* If we have at least one of each (readable/unreadable), we
130 have our answer. */
131 if (found_readable && found_unreadable)
132 goto decision;
135 break;
137 case svn_fs_path_change_delete:
138 case svn_fs_path_change_modify:
139 default:
140 break;
144 decision:
145 svn_pool_destroy(subpool);
147 /* Either every changed path was unreadable... */
148 if (! found_readable)
149 *access_level = svn_repos_revision_access_none;
151 /* ... or some changed path was unreadable... */
152 else if (found_unreadable)
153 *access_level = svn_repos_revision_access_partial;
155 /* ... or every changed path was readable (the default). */
156 return SVN_NO_ERROR;
160 /* Store as keys in CHANGED the paths of all node in ROOT that show a
161 * significant change. "Significant" means that the text or
162 * properties of the node were changed, or that the node was added or
163 * deleted.
165 * The CHANGED hash set and its keys and values are allocated in POOL;
166 * keys are const char * paths and values are svn_log_changed_path_t.
168 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
169 * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
170 * copyfrom_path) is readable:
172 * - If some paths are readable and some are not, then silently
173 * omit the unreadable paths from the CHANGED hash, and return
174 * SVN_ERR_AUTHZ_PARTIALLY_READABLE.
176 * - If absolutely every changed-path (and copyfrom_path) is
177 * unreadable, then return an empty CHANGED hash and
178 * SVN_ERR_AUTHZ_UNREADABLE. (This is to distinguish a revision
179 * which truly has no changed paths from a revision in which all
180 * paths are unreadable.)
182 static svn_error_t *
183 detect_changed(apr_hash_t **changed,
184 svn_fs_root_t *root,
185 svn_fs_t *fs,
186 svn_repos_authz_func_t authz_read_func,
187 void *authz_read_baton,
188 apr_pool_t *pool)
190 apr_hash_t *changes;
191 apr_hash_index_t *hi;
192 apr_pool_t *subpool = svn_pool_create(pool);
193 svn_boolean_t found_readable = FALSE;
194 svn_boolean_t found_unreadable = FALSE;
196 *changed = apr_hash_make(pool);
197 SVN_ERR(svn_fs_paths_changed(&changes, root, pool));
199 if (apr_hash_count(changes) == 0)
200 /* No paths changed in this revision? Uh, sure, I guess the
201 revision is readable, then. */
202 return SVN_NO_ERROR;
204 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
206 /* NOTE: Much of this loop is going to look quite similar to
207 svn_repos_check_revision_access(), but we have to do more things
208 here, so we'll live with the duplication. */
209 const void *key;
210 void *val;
211 svn_fs_path_change_t *change;
212 const char *path;
213 char action;
214 svn_log_changed_path_t *item;
216 svn_pool_clear(subpool);
218 /* KEY will be the path, VAL the change. */
219 apr_hash_this(hi, &key, NULL, &val);
220 path = (const char *) key;
221 change = val;
223 /* Skip path if unreadable. */
224 if (authz_read_func)
226 svn_boolean_t readable;
227 SVN_ERR(authz_read_func(&readable,
228 root, path,
229 authz_read_baton, subpool));
230 if (! readable)
232 found_unreadable = TRUE;
233 continue;
237 /* At least one changed-path was readable. */
238 found_readable = TRUE;
240 switch (change->change_kind)
242 case svn_fs_path_change_reset:
243 continue;
245 case svn_fs_path_change_add:
246 action = 'A';
247 break;
249 case svn_fs_path_change_replace:
250 action = 'R';
251 break;
253 case svn_fs_path_change_delete:
254 action = 'D';
255 break;
257 case svn_fs_path_change_modify:
258 default:
259 action = 'M';
260 break;
263 item = apr_pcalloc(pool, sizeof(*item));
264 item->action = action;
265 item->copyfrom_rev = SVN_INVALID_REVNUM;
266 if ((action == 'A') || (action == 'R'))
268 const char *copyfrom_path;
269 svn_revnum_t copyfrom_rev;
271 SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
272 root, path, subpool));
274 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
276 svn_boolean_t readable = TRUE;
278 if (authz_read_func)
280 svn_fs_root_t *copyfrom_root;
282 SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
283 copyfrom_rev, subpool));
284 SVN_ERR(authz_read_func(&readable,
285 copyfrom_root, copyfrom_path,
286 authz_read_baton, subpool));
287 if (! readable)
288 found_unreadable = TRUE;
291 if (readable)
293 item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
294 item->copyfrom_rev = copyfrom_rev;
298 apr_hash_set(*changed, apr_pstrdup(pool, path),
299 APR_HASH_KEY_STRING, item);
302 svn_pool_destroy(subpool);
304 if (! found_readable)
305 /* Every changed-path was unreadable. */
306 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
307 NULL, NULL);
309 if (found_unreadable)
310 /* At least one changed-path was unreadable. */
311 return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
312 NULL, NULL);
314 /* Every changed-path was readable. */
315 return SVN_NO_ERROR;
318 /* This is used by svn_repos_get_logs to keep track of multiple
319 * path history information while working through history.
321 * The two pools are swapped after each iteration through history because
322 * to get the next history requires the previous one.
324 struct path_info
326 svn_stringbuf_t *path;
327 svn_revnum_t history_rev;
328 svn_boolean_t done;
329 svn_boolean_t first_time;
331 /* If possible, we like to keep open the history object for each path,
332 since it avoids needed to open and close it many times as we walk
333 backwards in time. To do so we need two pools, so that we can clear
334 one each time through. If we're not holding the history open for
335 this path then these three pointers will be NULL. */
336 svn_fs_history_t *hist;
337 apr_pool_t *newpool;
338 apr_pool_t *oldpool;
341 /* Advance to the next history for the path.
343 * If INFO->HIST is not NULL we do this using that existing history object,
344 * otherwise we open a new one.
346 * If no more history is available or the history revision is less
347 * than (earlier) than START, or the history is not available due
348 * to authorization, then INFO->DONE is set to TRUE.
350 * A STRICT value of FALSE will indicate to follow history across copied
351 * paths.
353 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
354 * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
355 * we do indeed find more history for the path.
357 static svn_error_t *
358 get_history(struct path_info *info,
359 svn_fs_t *fs,
360 svn_boolean_t strict,
361 svn_repos_authz_func_t authz_read_func,
362 void *authz_read_baton,
363 svn_revnum_t start,
364 apr_pool_t *pool)
366 svn_fs_root_t *history_root = NULL;
367 svn_fs_history_t *hist;
368 apr_pool_t *subpool;
369 const char *path;
371 if (info->hist)
373 subpool = info->newpool;
375 SVN_ERR(svn_fs_history_prev(&info->hist, info->hist,
376 strict ? FALSE : TRUE, subpool));
378 hist = info->hist;
380 else
382 subpool = svn_pool_create(pool);
384 /* Open the history located at the last rev we were at. */
385 SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
386 subpool));
388 SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
389 subpool));
391 SVN_ERR(svn_fs_history_prev(&hist, hist, strict ? FALSE : TRUE,
392 subpool));
394 if (info->first_time)
395 info->first_time = FALSE;
396 else
397 SVN_ERR(svn_fs_history_prev(&hist, hist, strict ? FALSE : TRUE,
398 subpool));
401 if (! hist)
403 svn_pool_destroy(subpool);
404 if (info->oldpool)
405 svn_pool_destroy(info->oldpool);
406 info->done = TRUE;
407 return SVN_NO_ERROR;
410 /* Fetch the location information for this history step. */
411 SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
412 hist, subpool));
414 svn_stringbuf_set(info->path, path);
416 /* If this history item predates our START revision then
417 don't fetch any more for this path. */
418 if (info->history_rev < start)
420 svn_pool_destroy(subpool);
421 if (info->oldpool)
422 svn_pool_destroy(info->oldpool);
423 info->done = TRUE;
424 return SVN_NO_ERROR;
427 /* Is the history item readable? If not, done with path. */
428 if (authz_read_func)
430 svn_boolean_t readable;
431 SVN_ERR(svn_fs_revision_root(&history_root, fs,
432 info->history_rev,
433 subpool));
434 SVN_ERR(authz_read_func(&readable, history_root,
435 info->path->data,
436 authz_read_baton,
437 subpool));
438 if (! readable)
439 info->done = TRUE;
442 if (! info->hist)
444 svn_pool_destroy(subpool);
446 else
448 apr_pool_t *temppool = info->oldpool;
449 info->oldpool = info->newpool;
450 svn_pool_clear(temppool);
451 info->newpool = temppool;
454 return SVN_NO_ERROR;
457 /* Set INFO->HIST to the next history for the path *if* there is history
458 * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
460 * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
461 * otherwise it is not touched.
463 * If we do need to get the next history revision for the path, call
464 * get_history to do it -- see it for details.
466 static svn_error_t *
467 check_history(svn_boolean_t *changed,
468 struct path_info *info,
469 svn_fs_t *fs,
470 svn_revnum_t current,
471 svn_boolean_t strict,
472 svn_repos_authz_func_t authz_read_func,
473 void *authz_read_baton,
474 svn_revnum_t start,
475 apr_pool_t *pool)
477 /* If we're already done with histories for this path,
478 don't try to fetch any more. */
479 if (info->done)
480 return SVN_NO_ERROR;
482 /* If the last rev we got for this path is less than CURRENT,
483 then just return and don't fetch history for this path.
484 The caller will get to this rev eventually or else reach
485 the limit. */
486 if (info->history_rev < current)
487 return SVN_NO_ERROR;
489 /* If the last rev we got for this path is equal to CURRENT
490 then set *CHANGED to true and get the next history
491 rev where this path was changed. */
492 *changed = TRUE;
493 SVN_ERR(get_history(info, fs, strict, authz_read_func,
494 authz_read_baton, start, pool));
495 return SVN_NO_ERROR;
498 /* Return the next interesting revision in our list of HISTORIES. */
499 static svn_revnum_t
500 next_history_rev(apr_array_header_t *histories)
502 svn_revnum_t next_rev = SVN_INVALID_REVNUM;
503 int i;
505 for (i = 0; i < histories->nelts; ++i)
507 struct path_info *info = APR_ARRAY_IDX(histories, i,
508 struct path_info *);
509 if (info->done)
510 continue;
511 if (info->history_rev > next_rev)
512 next_rev = info->history_rev;
515 return next_rev;
518 struct filter_baton
520 svn_fs_t *fs;
521 svn_revnum_t rev;
522 svn_fs_root_t *root;
524 /* Boolean which shortcircuits the filter process. See note in
525 branching_copy_filter() for details. */
526 svn_boolean_t finding_current_revision;
529 /* Use an algorithm similar to the one on in
530 libsvn_client/copy.c:get_implied_mergeinfo() to determine the expected
531 mergeinfo for a branching copy from SRC_PATH to DST_PATH in REV.
532 Return the resulting mergeinfo in *IMPLIED_MERGEINFO. */
533 static svn_error_t *
534 calculate_branching_copy_mergeinfo(apr_hash_t **implied_mergeinfo,
535 svn_fs_root_t *src_root,
536 const char *src_path,
537 const char *dst_path,
538 svn_revnum_t rev,
539 apr_pool_t *pool)
541 svn_fs_root_t *copy_root;
542 const char *copy_path;
543 svn_revnum_t oldest_rev;
544 svn_merge_range_t *range;
545 apr_array_header_t *rangelist;
547 *implied_mergeinfo = apr_hash_make(pool);
549 SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, src_root, src_path,
550 pool));
551 if (copy_root == NULL)
552 return SVN_NO_ERROR;
554 oldest_rev = svn_fs_revision_root_revision(copy_root);
556 range = apr_palloc(pool, sizeof(*range));
557 range->start = oldest_rev;
558 range->end = rev - 1;
559 range->inheritable = TRUE;
560 rangelist = apr_array_make(pool, 1, sizeof(range));
561 APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = range;
562 apr_hash_set(*implied_mergeinfo, dst_path, APR_HASH_KEY_STRING, rangelist);
564 return SVN_NO_ERROR;
567 svn_error_t *
568 svn_repos__get_path_mergeinfo(apr_hash_t **mergeinfo,
569 svn_fs_t *fs,
570 const char *path,
571 svn_revnum_t revnum,
572 apr_pool_t *pool)
574 apr_hash_t *tmp_mergeinfo;
575 const char *mergeinfo_str;
576 svn_fs_root_t *root;
577 apr_pool_t *subpool = svn_pool_create(pool);
578 apr_array_header_t *paths = apr_array_make(subpool, 1,
579 sizeof(const char *));
581 APR_ARRAY_PUSH(paths, const char *) = path;
583 SVN_ERR(svn_fs_revision_root(&root, fs, revnum, subpool));
584 SVN_ERR(svn_fs_get_mergeinfo(&tmp_mergeinfo, root, paths,
585 svn_mergeinfo_inherited, subpool));
587 mergeinfo_str = apr_hash_get(tmp_mergeinfo, path, APR_HASH_KEY_STRING);
588 if (mergeinfo_str != NULL)
589 SVN_ERR(svn_mergeinfo_parse(mergeinfo, mergeinfo_str, pool));
590 else
591 *mergeinfo = apr_hash_make(pool);
593 svn_pool_destroy(subpool);
595 return SVN_NO_ERROR;
598 svn_error_t *
599 svn_repos__is_branching_copy(svn_boolean_t *is_branching,
600 svn_fs_root_t *root,
601 const char *path,
602 apr_hash_t *path_mergeinfo,
603 apr_pool_t *pool)
605 const char *copy_path;
606 svn_fs_root_t *copy_root;
607 svn_revnum_t copy_rev;
608 apr_hash_t *mergeinfo, *implied_mergeinfo;
609 apr_hash_t *deleted, *added;
610 svn_revnum_t rev = svn_fs_revision_root_revision(root);
611 apr_pool_t *subpool = svn_pool_create(pool);
613 /* Assume it's not a branching revision */
614 *is_branching = FALSE;
616 /* If we weren't supplied with any path_mergeinfo, we need to go fetch it. */
617 if (path_mergeinfo != NULL)
618 mergeinfo = path_mergeinfo;
619 else
620 SVN_ERR(svn_repos__get_path_mergeinfo(&mergeinfo, svn_fs_root_fs(root),
621 path, rev, subpool));
623 /* Check and see if there was a copy in this revision. If not, set omit to
624 FALSE and return. */
625 SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root, path,
626 subpool));
627 if (copy_root == NULL)
629 svn_pool_destroy(subpool);
630 return SVN_NO_ERROR;
633 copy_rev = svn_fs_revision_root_revision(copy_root);
634 if (copy_rev != rev)
636 svn_pool_destroy(subpool);
637 return SVN_NO_ERROR;
640 /* At this point, we know that PATH was created as a copy in REV. Using an
641 algorithm similar to libsvn_client/copy.c:get_implied_mergeinfo(), check
642 to see if the mergeinfo generated on a branching copy, and the mergeinfo
643 that we are presented with matches. If so, omit the path. */
644 SVN_ERR(calculate_branching_copy_mergeinfo(&implied_mergeinfo, copy_root,
645 copy_path, path, rev, subpool));
647 SVN_ERR(svn_mergeinfo_diff(&deleted, &added, implied_mergeinfo,
648 mergeinfo, FALSE,
649 subpool));
650 if (apr_hash_count(deleted) == 0 && apr_hash_count(added) == 0)
652 svn_pool_destroy(subpool);
653 return SVN_NO_ERROR;
656 /* If we've reached this point, we've found a branching revision. */
657 *is_branching = TRUE;
659 svn_pool_destroy(subpool);
660 return SVN_NO_ERROR;
663 /* Filter function to be used with svn_fs_get_mergeinfo_for_tree().
664 This should return FALSE if PATH is a copy which is considered a
665 "branch"; that is, a copied path which has mergeinfo identical to
666 what would be expected for a copy from source to destination
667 without any modification.
669 This function implements the svn_fs_mergeinfo_filter_func_t API. */
670 static svn_error_t *
671 branching_copy_filter(void *baton,
672 svn_boolean_t *omit,
673 const char *path,
674 apr_hash_t *path_mergeinfo,
675 apr_pool_t *pool)
677 struct filter_baton *fb = (struct filter_baton *) baton;
678 svn_node_kind_t kind;
679 svn_boolean_t is_branching;
681 /* Assume *omit is FALSE, unless determined otherwise. */
682 *omit = FALSE;
684 /* If the rev isn't the rev of interest for which we are currently looking
685 for new mergeinfo, don't omit this path's mergeinfo.
687 Consider the following scenario: We are finding the mergeinfo difference
688 between r10, which is a branching copy, and r9 which is the previous
689 revision. If the current revision is r10, *and*, we are looking for
690 mergeinfo in r10, we may want to omit, so this test should fail.
692 However, if the current revision is r11, and we're looking for mergeinfo
693 in r10, we want to keep it in, even if r10 is a branching revision. If
694 we didn't those extra revisions would show up as a diff with r11, and they
695 would get pulled in as children of r11.
697 Whew! That's a long explantion for a single line of code. :) */
698 if (!fb->finding_current_revision)
699 return SVN_NO_ERROR;
701 /* Find out if the path even exists in fb->root. */
702 SVN_ERR(svn_fs_check_path(&kind, fb->root, path, pool));
703 if (kind == svn_node_none)
704 return SVN_NO_ERROR;
706 /* Finally, do the generic check for branching revisions. */
707 SVN_ERR(svn_repos__is_branching_copy(&is_branching, fb->root, path,
708 path_mergeinfo, pool));
709 if (is_branching)
710 *omit = TRUE;
712 return SVN_NO_ERROR;
715 /* Return the combined rangelists for everyone's mergeinfo for the
716 PATHS tree at REV in *RANGELIST. Perform all allocations in POOL. */
717 static svn_error_t *
718 get_combined_mergeinfo(apr_hash_t **mergeinfo,
719 svn_fs_t *fs,
720 svn_revnum_t rev,
721 svn_revnum_t current_rev,
722 const apr_array_header_t *paths,
723 apr_pool_t *pool)
725 svn_fs_root_t *root;
726 apr_hash_index_t *hi;
727 apr_hash_t *tree_mergeinfo;
728 apr_pool_t *subpool = svn_pool_create(pool);
729 struct filter_baton fb;
731 /* Revision 0 doesn't have any mergeinfo. */
732 if (rev == 0)
734 *mergeinfo = apr_hash_make(pool);
735 return SVN_NO_ERROR;
738 /* Get the mergeinfo for each tree roots in PATHS. */
739 SVN_ERR(svn_fs_revision_root(&root, fs, rev, subpool));
741 fb.fs = fs;
742 fb.rev = rev;
743 fb.root = root;
744 fb.finding_current_revision = (rev == current_rev);
745 SVN_ERR(svn_fs_get_mergeinfo_for_tree(&tree_mergeinfo, root, paths,
746 branching_copy_filter, &fb, pool));
748 *mergeinfo = apr_hash_make(pool);
750 /* Merge all the mergeinfos into one mergeinfo */
751 for (hi = apr_hash_first(subpool, tree_mergeinfo); hi; hi = apr_hash_next(hi))
753 apr_hash_t *path_mergeinfo;
755 apr_hash_this(hi, NULL, NULL, (void *)&path_mergeinfo);
756 SVN_ERR(svn_mergeinfo_merge(*mergeinfo, path_mergeinfo, pool));
759 svn_pool_destroy(subpool);
761 return SVN_NO_ERROR;
764 /* Combine and return in *RANGELIST the various rangelists for each bit of
765 MERGEINFO. */
766 static svn_error_t *
767 combine_mergeinfo_rangelists(apr_array_header_t **rangelist,
768 apr_hash_t *mergeinfo,
769 apr_pool_t *pool)
771 apr_hash_index_t *hi;
773 *rangelist = apr_array_make(pool, 0, sizeof(svn_merge_range_t *));
775 /* Iterate over each path's rangelist, and merge them into RANGELIST. */
776 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
778 apr_array_header_t *path_rangelist;
780 apr_hash_this(hi, NULL, NULL, (void *)&path_rangelist);
781 SVN_ERR(svn_rangelist_merge(rangelist, path_rangelist,
782 pool));
785 return SVN_NO_ERROR;
788 /* Determine all the revisions which were merged into PATHS in REV. Return
789 them as a new MERGEINFO. */
790 static svn_error_t *
791 get_merged_rev_mergeinfo(apr_hash_t **mergeinfo,
792 svn_fs_t *fs,
793 const apr_array_header_t *paths,
794 svn_revnum_t rev,
795 apr_pool_t *pool)
797 apr_hash_t *curr_mergeinfo, *prev_mergeinfo;
798 apr_hash_t *deleted, *changed;
799 apr_pool_t *subpool;
801 /* Revision 0 is always empty. */
802 if (rev == 0)
804 *mergeinfo = apr_hash_make(pool);
805 return SVN_NO_ERROR;
808 subpool = svn_pool_create(pool);
810 SVN_ERR(get_combined_mergeinfo(&curr_mergeinfo, fs, rev, rev, paths,
811 subpool));
812 SVN_ERR(get_combined_mergeinfo(&prev_mergeinfo, fs, rev - 1, rev, paths,
813 subpool));
815 SVN_ERR(svn_mergeinfo_diff(&deleted, &changed, prev_mergeinfo,
816 curr_mergeinfo, FALSE,
817 subpool));
818 SVN_ERR(svn_mergeinfo_merge(changed, deleted, subpool));
820 *mergeinfo = svn_mergeinfo_dup(changed, pool);
821 svn_pool_destroy(subpool);
823 return SVN_NO_ERROR;
826 /* Fill LOG_ENTRY with history information in FS at REV. */
827 static svn_error_t *
828 fill_log_entry(svn_log_entry_t *log_entry,
829 svn_revnum_t rev,
830 svn_fs_t *fs,
831 svn_boolean_t discover_changed_paths,
832 apr_array_header_t *revprops,
833 svn_repos_authz_func_t authz_read_func,
834 void *authz_read_baton,
835 apr_pool_t *pool)
837 apr_hash_t *r_props, *changed_paths = NULL;
838 svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
840 /* Discover changed paths if the user requested them
841 or if we need to check that they are readable. */
842 if ((rev > 0)
843 && (authz_read_func || discover_changed_paths))
845 svn_fs_root_t *newroot;
846 svn_error_t *patherr;
848 SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
849 patherr = detect_changed(&changed_paths,
850 newroot, fs,
851 authz_read_func, authz_read_baton,
852 pool);
854 if (patherr
855 && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
857 /* All changed-paths are unreadable, so clear all fields. */
858 svn_error_clear(patherr);
859 changed_paths = NULL;
860 get_revprops = FALSE;
862 else if (patherr
863 && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
865 /* At least one changed-path was unreadable, so censor all
866 but author and date. (The unreadable paths are already
867 missing from the hash.) */
868 svn_error_clear(patherr);
869 censor_revprops = TRUE;
871 else if (patherr)
872 return patherr;
874 /* It may be the case that an authz func was passed in, but
875 the user still doesn't want to see any changed-paths. */
876 if (! discover_changed_paths)
877 changed_paths = NULL;
880 if (get_revprops)
882 /* User is allowed to see at least some revprops. */
883 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
884 if (revprops == NULL)
886 /* Requested all revprops... */
887 if (censor_revprops)
889 /* ... but we can only return author/date. */
890 log_entry->revprops = apr_hash_make(pool);
891 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
892 APR_HASH_KEY_STRING,
893 apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
894 APR_HASH_KEY_STRING));
895 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
896 APR_HASH_KEY_STRING,
897 apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
898 APR_HASH_KEY_STRING));
900 else
901 /* ... so return all we got. */
902 log_entry->revprops = r_props;
904 else
906 /* Requested only some revprops... */
907 int i;
908 for (i = 0; i < revprops->nelts; i++)
910 char *name = APR_ARRAY_IDX(revprops, i, char *);
911 svn_string_t *value = apr_hash_get(r_props, name,
912 APR_HASH_KEY_STRING);
913 if (censor_revprops
914 && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
915 || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
916 /* ... but we can only return author/date. */
917 continue;
918 if (log_entry->revprops == NULL)
919 log_entry->revprops = apr_hash_make(pool);
920 apr_hash_set(log_entry->revprops, name,
921 APR_HASH_KEY_STRING, value);
926 log_entry->changed_paths = changed_paths;
927 log_entry->revision = rev;
929 return SVN_NO_ERROR;
932 /* Look through path in MERGEINFO, and find the one in which revision is part
933 of it's rangelist. */
934 static svn_error_t *
935 find_merge_source(const char **merge_source,
936 svn_revnum_t revision,
937 apr_hash_t *mergeinfo,
938 apr_pool_t *pool)
940 apr_hash_index_t *hi;
942 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
944 apr_array_header_t *rangelist;
945 const char *key;
946 int i;
948 apr_hash_this(hi, (void*) &key, NULL, (void*) &rangelist);
950 for (i = 0; i < rangelist->nelts; i++)
952 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
953 svn_merge_range_t *);
955 if (revision > range->start && revision <= range->end)
957 *merge_source = key;
958 return SVN_NO_ERROR;
963 return SVN_NO_ERROR;
966 /* Send log tree, beging with REV to RECEIVER with its RECEIVER_BATON.
968 * FS is used with REV to fetch the interesting history information,
969 * such as changed paths, revprops, etc.
971 * The detect_changed function is used if either AUTHZ_READ_FUNC is
972 * not NULL, or if DISCOVER_CHANGED_PATHS is TRUE. See it for details.
974 * If DESCENDING_ORDER is true, send child messages in descending order.
976 * If REVPROPS is NULL, retrieve all revprops; else, retrieve only the
977 * revprops named in the array (i.e. retrieve none if the array is empty).
979 * If INCLUDE_MERGED_REVISIONS is TRUE, send history information for any
980 * revisions which were merged in as a result of REV immediately following
981 * REV. Terminate that list with a message with call to RECEIVER with a
982 * log entry for SVN_INVALID_REVNUM.
984 static svn_error_t *
985 send_logs(const apr_array_header_t *paths,
986 svn_revnum_t rev,
987 svn_fs_t *fs,
988 svn_boolean_t discover_changed_paths,
989 svn_boolean_t include_merged_revisions,
990 apr_array_header_t *revprops,
991 svn_boolean_t descending_order,
992 svn_log_entry_receiver_t receiver,
993 void *receiver_baton,
994 svn_repos_authz_func_t authz_read_func,
995 void *authz_read_baton,
996 apr_pool_t *pool)
998 svn_log_entry_t *log_entry;
999 apr_array_header_t *rangelist;
1000 apr_hash_t *mergeinfo;
1002 log_entry = svn_log_entry_create(pool);
1003 SVN_ERR(fill_log_entry(log_entry, rev, fs, discover_changed_paths,
1004 revprops, authz_read_func, authz_read_baton,
1005 pool));
1007 /* Check to see if we need to include any extra merged revisions. */
1008 if (include_merged_revisions)
1010 SVN_ERR(get_merged_rev_mergeinfo(&mergeinfo, fs, paths, rev, pool));
1011 SVN_ERR(combine_mergeinfo_rangelists(&rangelist, mergeinfo, pool));
1013 if (svn_rangelist_count_revs(rangelist) != 0)
1014 log_entry->has_children = TRUE;
1017 /* Send the entry to the receiver. */
1018 SVN_ERR((*receiver)(receiver_baton, log_entry, pool));
1020 if (log_entry->has_children)
1022 /* Send the subtree, starting at the most recent revision in the
1023 rangelist difference. The idea is to send the tree rooted at
1024 the current message, and remove any revisions which are included by
1025 children of that tree from the remaining revisions. In this way, we
1026 can untransitify merged revisions, and make sure that revisions get
1027 nested at the appropriate level. */
1028 apr_array_header_t *revisions;
1029 apr_pool_t *iterpool = svn_pool_create(pool);
1030 svn_log_entry_t *empty_log_entry;
1031 int i;
1033 /* Get the individual revisions, and sort in descending order. */
1034 SVN_ERR(svn_rangelist_to_revs(&revisions, rangelist, pool));
1035 qsort(revisions->elts, revisions->nelts, revisions->elt_size,
1036 svn_sort_compare_revisions);
1038 /* For each revision, send the subtree. */
1039 for (i = 0; i < revisions->nelts; i++)
1041 svn_revnum_t revision = APR_ARRAY_IDX(revisions, i, svn_revnum_t);
1042 const char *merge_source;
1043 svn_node_kind_t kind;
1044 svn_fs_root_t *root;
1046 svn_pool_clear(iterpool);
1048 /* Figure out where the source of this revision was, given our
1049 mergeinfo. */
1050 SVN_ERR(find_merge_source(&merge_source, revision, mergeinfo,
1051 iterpool));
1053 SVN_ERR(svn_fs_revision_root(&root, fs, revision, iterpool));
1054 SVN_ERR(svn_fs_check_path(&kind, root, merge_source, iterpool));
1055 if (kind == svn_node_none)
1056 continue;
1058 SVN_ERR(do_merged_log(fs, merge_source, revision,
1059 discover_changed_paths, revprops,
1060 descending_order, receiver, receiver_baton,
1061 authz_read_func, authz_read_baton, pool));
1064 empty_log_entry = svn_log_entry_create(iterpool);
1065 empty_log_entry->revision = SVN_INVALID_REVNUM;
1066 SVN_ERR((*receiver)(receiver_baton, empty_log_entry, iterpool));
1068 svn_pool_destroy(iterpool);
1071 return SVN_NO_ERROR;
1074 /* This controls how many history objects we keep open. For any targets
1075 over this number we have to open and close their histories as needed,
1076 which is CPU intensive, but keeps us from using an unbounded amount of
1077 memory. */
1078 #define MAX_OPEN_HISTORIES 32
1080 /* Get the histories for PATHS, and store them in *HISTORIES. */
1081 static svn_error_t *
1082 get_path_histories(apr_array_header_t **histories,
1083 svn_fs_t *fs,
1084 const apr_array_header_t *paths,
1085 svn_revnum_t hist_start,
1086 svn_revnum_t hist_end,
1087 svn_boolean_t strict_node_history,
1088 svn_repos_authz_func_t authz_read_func,
1089 void *authz_read_baton,
1090 apr_pool_t *pool)
1092 svn_fs_root_t *root;
1093 apr_pool_t *iterpool;
1094 int i;
1096 /* Create a history object for each path so we can walk through
1097 them all at the same time until we have all changes or LIMIT
1098 is reached.
1100 There is some pool fun going on due to the fact that we have
1101 to hold on to the old pool with the history before we can
1102 get the next history.
1104 *histories = apr_array_make(pool, paths->nelts,
1105 sizeof(struct path_info *));
1107 SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
1109 iterpool = svn_pool_create(pool);
1110 for (i = 0; i < paths->nelts; i++)
1112 const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
1113 struct path_info *info = apr_palloc(pool,
1114 sizeof(struct path_info));
1116 if (authz_read_func)
1118 svn_boolean_t readable;
1120 svn_pool_clear(iterpool);
1122 SVN_ERR(authz_read_func(&readable, root, this_path,
1123 authz_read_baton, iterpool));
1124 if (! readable)
1125 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
1128 info->path = svn_stringbuf_create(this_path, pool);
1129 info->done = FALSE;
1130 info->history_rev = hist_end;
1131 info->first_time = TRUE;
1133 if (i < MAX_OPEN_HISTORIES)
1135 SVN_ERR(svn_fs_node_history(&info->hist, root, this_path, pool));
1136 info->newpool = svn_pool_create(pool);
1137 info->oldpool = svn_pool_create(pool);
1139 else
1141 info->hist = NULL;
1142 info->oldpool = NULL;
1143 info->newpool = NULL;
1146 SVN_ERR(get_history(info, fs,
1147 strict_node_history,
1148 authz_read_func, authz_read_baton,
1149 hist_start, pool));
1150 APR_ARRAY_PUSH(*histories, struct path_info *) = info;
1152 svn_pool_destroy(iterpool);
1154 return SVN_NO_ERROR;
1157 static svn_error_t *
1158 do_merged_log(svn_fs_t *fs,
1159 const char *path,
1160 svn_revnum_t rev,
1161 svn_boolean_t discover_changed_paths,
1162 apr_array_header_t *revprops,
1163 svn_boolean_t descending_order,
1164 svn_log_entry_receiver_t receiver,
1165 void *receiver_baton,
1166 svn_repos_authz_func_t authz_read_func,
1167 void *authz_read_baton,
1168 apr_pool_t *pool)
1170 apr_array_header_t *histories;
1171 apr_pool_t *subpool = svn_pool_create(pool);
1172 apr_array_header_t *paths = apr_array_make(subpool, 1, sizeof(const char *));
1173 svn_boolean_t changed = FALSE;
1174 int i;
1176 /* We only really care about revisions in which those paths were changed.
1177 So we ask the filesystem for all the revisions in which any of the
1178 paths was changed. */
1179 APR_ARRAY_PUSH(paths, const char *) = path;
1180 SVN_ERR(get_path_histories(&histories, fs, paths, rev, rev, TRUE,
1181 authz_read_func, authz_read_baton, subpool));
1183 /* Check for this path in the list of histories. */
1184 for (i = 0; i < histories->nelts; i++)
1186 struct path_info *info = APR_ARRAY_IDX(histories, i,
1187 struct path_info *);
1189 /* Check history for this path in current rev. */
1190 SVN_ERR(check_history(&changed, info, fs, rev, TRUE,
1191 authz_read_func, authz_read_baton, rev, subpool));
1194 /* If any of the paths changed in this rev then set the output. */
1195 if (changed)
1197 SVN_ERR(send_logs(paths, rev, fs, discover_changed_paths, TRUE,
1198 revprops, descending_order,
1199 receiver, receiver_baton,
1200 authz_read_func, authz_read_baton, pool));
1203 svn_pool_destroy(subpool);
1205 return SVN_NO_ERROR;
1208 static svn_error_t *
1209 do_logs(svn_fs_t *fs,
1210 const apr_array_header_t *paths,
1211 svn_revnum_t hist_start,
1212 svn_revnum_t hist_end,
1213 int limit,
1214 svn_boolean_t discover_changed_paths,
1215 svn_boolean_t strict_node_history,
1216 svn_boolean_t include_merged_revisions,
1217 apr_array_header_t *revprops,
1218 svn_boolean_t descending_order,
1219 svn_log_entry_receiver_t receiver,
1220 void *receiver_baton,
1221 svn_repos_authz_func_t authz_read_func,
1222 void *authz_read_baton,
1223 apr_pool_t *pool)
1225 apr_pool_t *iterpool;
1226 apr_array_header_t *revs = NULL;
1227 svn_revnum_t current;
1228 apr_array_header_t *histories;
1229 svn_boolean_t any_histories_left = TRUE;
1230 int send_count = 0;
1231 int i;
1233 /* We only really care about revisions in which those paths were changed.
1234 So we ask the filesystem for all the revisions in which any of the
1235 paths was changed. */
1236 SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
1237 strict_node_history, authz_read_func,
1238 authz_read_baton, pool));
1240 /* Loop through all the revisions in the range and add any
1241 where a path was changed to the array, or if they wanted
1242 history in reverse order just send it to them right away.
1244 iterpool = svn_pool_create(pool);
1245 for (current = hist_end;
1246 current >= hist_start && any_histories_left;
1247 current = next_history_rev(histories))
1249 svn_boolean_t changed = FALSE;
1250 any_histories_left = FALSE;
1251 svn_pool_clear(iterpool);
1253 for (i = 0; i < histories->nelts; i++)
1255 struct path_info *info = APR_ARRAY_IDX(histories, i,
1256 struct path_info *);
1258 /* Check history for this path in current rev. */
1259 SVN_ERR(check_history(&changed, info, fs, current,
1260 strict_node_history,
1261 authz_read_func, authz_read_baton,
1262 hist_start, pool));
1263 if (! info->done)
1264 any_histories_left = TRUE;
1267 /* If any of the paths changed in this rev then add or send it. */
1268 if (changed)
1270 /* If they wanted it in reverse order we can send it completely
1271 streamily right now. */
1272 if (descending_order)
1274 SVN_ERR(send_logs(paths, current, fs, discover_changed_paths,
1275 include_merged_revisions, revprops,
1276 descending_order, receiver, receiver_baton,
1277 authz_read_func, authz_read_baton, iterpool));
1279 if (limit && ++send_count >= limit)
1280 break;
1282 else
1284 /* They wanted it in forward order, so we have to buffer up
1285 a list of revs and process it later. */
1286 if (! revs)
1287 revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
1288 APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
1293 if (revs)
1295 /* Work loop for processing the revisions we found since they wanted
1296 history in forward order. */
1297 for (i = 0; i < revs->nelts; ++i)
1299 svn_pool_clear(iterpool);
1300 SVN_ERR(send_logs(paths, APR_ARRAY_IDX(revs, revs->nelts - i - 1,
1301 svn_revnum_t),
1302 fs, discover_changed_paths,
1303 include_merged_revisions,
1304 revprops, descending_order,
1305 receiver, receiver_baton,
1306 authz_read_func, authz_read_baton, iterpool));
1308 if (limit && i + 1 >= limit)
1309 break;
1313 svn_pool_destroy(iterpool);
1315 return SVN_NO_ERROR;
1318 svn_error_t *
1319 svn_repos_get_logs4(svn_repos_t *repos,
1320 const apr_array_header_t *paths,
1321 svn_revnum_t start,
1322 svn_revnum_t end,
1323 int limit,
1324 svn_boolean_t discover_changed_paths,
1325 svn_boolean_t strict_node_history,
1326 svn_boolean_t include_merged_revisions,
1327 apr_array_header_t *revprops,
1328 svn_repos_authz_func_t authz_read_func,
1329 void *authz_read_baton,
1330 svn_log_entry_receiver_t receiver,
1331 void *receiver_baton,
1332 apr_pool_t *pool)
1334 svn_revnum_t head = SVN_INVALID_REVNUM;
1335 svn_fs_t *fs = repos->fs;
1336 svn_boolean_t descending_order;
1337 svn_revnum_t hist_start = start;
1338 svn_revnum_t hist_end = end;
1340 /* Setup log range. */
1341 SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
1343 if (! SVN_IS_VALID_REVNUM(start))
1344 start = head;
1346 if (! SVN_IS_VALID_REVNUM(end))
1347 end = head;
1349 /* Check that revisions are sane before ever invoking receiver. */
1350 if (start > head)
1351 return svn_error_createf
1352 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
1353 _("No such revision %ld"), start);
1354 if (end > head)
1355 return svn_error_createf
1356 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
1357 _("No such revision %ld"), end);
1359 descending_order = start >= end;
1360 if (descending_order)
1362 hist_start = end;
1363 hist_end = start;
1367 /* If paths were specified, then we only really care about revisions
1368 in which those paths were changed. So we ask the filesystem for
1369 all the revisions in which any of the paths was changed.
1371 SPECIAL CASE: If we were given only path, and that path is empty,
1372 then the results are the same as if we were passed no paths at
1373 all. Why? Because the answer to the question "In which
1374 revisions was the root of the filesystem changed?" is always
1375 "Every single one of them." And since this section of code is
1376 only about answering that question, and we already know the
1377 answer ... well, you get the picture.
1379 if (! paths)
1380 paths = apr_array_make(pool, 0, sizeof(const char *));
1382 if ((! paths->nelts)
1383 || (paths->nelts == 1 &&
1384 svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))))
1386 int send_count = 0;
1387 int i;
1388 apr_pool_t *iterpool = svn_pool_create(pool);
1390 /* They want history for the root path, so every rev has a change. */
1391 send_count = hist_end - hist_start + 1;
1392 if (limit && send_count > limit)
1393 send_count = limit;
1394 for (i = 0; i < send_count; ++i)
1396 svn_revnum_t rev = hist_start + i;
1398 svn_pool_clear(iterpool);
1400 if (descending_order)
1401 rev = hist_end - i;
1402 SVN_ERR(send_logs(paths, rev, fs,
1403 discover_changed_paths,
1404 include_merged_revisions,
1405 revprops, descending_order,
1406 receiver, receiver_baton,
1407 authz_read_func, authz_read_baton,
1408 iterpool));
1410 svn_pool_destroy(iterpool);
1412 return SVN_NO_ERROR;
1415 SVN_ERR(do_logs(repos->fs, paths, hist_start, hist_end, limit,
1416 discover_changed_paths, strict_node_history,
1417 include_merged_revisions, revprops,
1418 descending_order, receiver, receiver_baton,
1419 authz_read_func, authz_read_baton, pool));
1421 return SVN_NO_ERROR;
1425 svn_error_t *
1426 svn_repos_get_logs3(svn_repos_t *repos,
1427 const apr_array_header_t *paths,
1428 svn_revnum_t start,
1429 svn_revnum_t end,
1430 int limit,
1431 svn_boolean_t discover_changed_paths,
1432 svn_boolean_t strict_node_history,
1433 svn_repos_authz_func_t authz_read_func,
1434 void *authz_read_baton,
1435 svn_log_message_receiver_t receiver,
1436 void *receiver_baton,
1437 apr_pool_t *pool)
1439 svn_log_entry_receiver_t receiver2;
1440 void *receiver2_baton;
1442 svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
1443 receiver, receiver_baton,
1444 pool);
1446 return svn_repos_get_logs4(repos, paths, start, end, limit,
1447 discover_changed_paths, strict_node_history,
1448 FALSE, svn_compat_log_revprops_in(pool),
1449 authz_read_func, authz_read_baton,
1450 receiver2, receiver2_baton,
1451 pool);
1454 svn_error_t *
1455 svn_repos_get_logs2(svn_repos_t *repos,
1456 const apr_array_header_t *paths,
1457 svn_revnum_t start,
1458 svn_revnum_t end,
1459 svn_boolean_t discover_changed_paths,
1460 svn_boolean_t strict_node_history,
1461 svn_repos_authz_func_t authz_read_func,
1462 void *authz_read_baton,
1463 svn_log_message_receiver_t receiver,
1464 void *receiver_baton,
1465 apr_pool_t *pool)
1467 return svn_repos_get_logs3(repos, paths, start, end, 0,
1468 discover_changed_paths, strict_node_history,
1469 authz_read_func, authz_read_baton, receiver,
1470 receiver_baton, pool);
1474 svn_error_t *
1475 svn_repos_get_logs(svn_repos_t *repos,
1476 const apr_array_header_t *paths,
1477 svn_revnum_t start,
1478 svn_revnum_t end,
1479 svn_boolean_t discover_changed_paths,
1480 svn_boolean_t strict_node_history,
1481 svn_log_message_receiver_t receiver,
1482 void *receiver_baton,
1483 apr_pool_t *pool)
1485 return svn_repos_get_logs3(repos, paths, start, end, 0,
1486 discover_changed_paths, strict_node_history,
1487 NULL, NULL, /* no authz stuff */
1488 receiver, receiver_baton, pool);