Reorganize the output to "svnserve --help".
[svn.git] / subversion / libsvn_repos / log.c
blob01096d5e54da6625134cf83755fb53b194244df4
1 /* log.c --- retrieving log messages
3 * ====================================================================
4 * Copyright (c) 2000-2008 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"
38 svn_error_t *
39 svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
40 svn_repos_t *repos,
41 svn_revnum_t revision,
42 svn_repos_authz_func_t authz_read_func,
43 void *authz_read_baton,
44 apr_pool_t *pool)
46 svn_fs_t *fs = svn_repos_fs(repos);
47 svn_fs_root_t *rev_root;
48 apr_hash_t *changes;
49 apr_hash_index_t *hi;
50 svn_boolean_t found_readable = FALSE;
51 svn_boolean_t found_unreadable = FALSE;
52 apr_pool_t *subpool;
54 /* By default, we'll grant full read access to REVISION. */
55 *access_level = svn_repos_revision_access_full;
57 /* No auth-checking function? We're done. */
58 if (! authz_read_func)
59 return SVN_NO_ERROR;
61 /* Fetch the changes associated with REVISION. */
62 SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
63 SVN_ERR(svn_fs_paths_changed(&changes, rev_root, pool));
65 /* No changed paths? We're done. */
66 if (apr_hash_count(changes) == 0)
67 return SVN_NO_ERROR;
69 /* Otherwise, we have to check the readability of each changed
70 path, or at least enough to answer the question asked. */
71 subpool = svn_pool_create(pool);
72 for (hi = apr_hash_first(NULL, changes); hi; hi = apr_hash_next(hi))
74 const void *key;
75 void *val;
76 svn_fs_path_change_t *change;
77 svn_boolean_t readable;
79 svn_pool_clear(subpool);
80 apr_hash_this(hi, &key, NULL, &val);
81 change = val;
83 SVN_ERR(authz_read_func(&readable, rev_root, key,
84 authz_read_baton, subpool));
85 if (! readable)
86 found_unreadable = TRUE;
87 else
88 found_readable = TRUE;
90 /* If we have at least one of each (readable/unreadable), we
91 have our answer. */
92 if (found_readable && found_unreadable)
93 goto decision;
95 switch (change->change_kind)
97 case svn_fs_path_change_add:
98 case svn_fs_path_change_replace:
100 const char *copyfrom_path;
101 svn_revnum_t copyfrom_rev;
103 SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
104 rev_root, key, subpool));
105 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
107 svn_fs_root_t *copyfrom_root;
108 SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
109 copyfrom_rev, subpool));
110 SVN_ERR(authz_read_func(&readable,
111 copyfrom_root, copyfrom_path,
112 authz_read_baton, subpool));
113 if (! readable)
114 found_unreadable = TRUE;
116 /* If we have at least one of each (readable/unreadable), we
117 have our answer. */
118 if (found_readable && found_unreadable)
119 goto decision;
122 break;
124 case svn_fs_path_change_delete:
125 case svn_fs_path_change_modify:
126 default:
127 break;
131 decision:
132 svn_pool_destroy(subpool);
134 /* Either every changed path was unreadable... */
135 if (! found_readable)
136 *access_level = svn_repos_revision_access_none;
138 /* ... or some changed path was unreadable... */
139 else if (found_unreadable)
140 *access_level = svn_repos_revision_access_partial;
142 /* ... or every changed path was readable (the default). */
143 return SVN_NO_ERROR;
147 /* Store as keys in CHANGED the paths of all node in ROOT that show a
148 * significant change. "Significant" means that the text or
149 * properties of the node were changed, or that the node was added or
150 * deleted.
152 * The CHANGED hash set and its keys and values are allocated in POOL;
153 * keys are const char * paths and values are svn_log_changed_path_t.
155 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
156 * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
157 * copyfrom_path) is readable:
159 * - If some paths are readable and some are not, then silently
160 * omit the unreadable paths from the CHANGED hash, and return
161 * SVN_ERR_AUTHZ_PARTIALLY_READABLE.
163 * - If absolutely every changed-path (and copyfrom_path) is
164 * unreadable, then return an empty CHANGED hash and
165 * SVN_ERR_AUTHZ_UNREADABLE. (This is to distinguish a revision
166 * which truly has no changed paths from a revision in which all
167 * paths are unreadable.)
169 static svn_error_t *
170 detect_changed(apr_hash_t **changed,
171 svn_fs_root_t *root,
172 svn_fs_t *fs,
173 svn_repos_authz_func_t authz_read_func,
174 void *authz_read_baton,
175 apr_pool_t *pool)
177 apr_hash_t *changes;
178 apr_hash_index_t *hi;
179 apr_pool_t *subpool = svn_pool_create(pool);
180 svn_boolean_t found_readable = FALSE;
181 svn_boolean_t found_unreadable = FALSE;
183 *changed = apr_hash_make(pool);
184 SVN_ERR(svn_fs_paths_changed(&changes, root, pool));
186 if (apr_hash_count(changes) == 0)
187 /* No paths changed in this revision? Uh, sure, I guess the
188 revision is readable, then. */
189 return SVN_NO_ERROR;
191 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
193 /* NOTE: Much of this loop is going to look quite similar to
194 svn_repos_check_revision_access(), but we have to do more things
195 here, so we'll live with the duplication. */
196 const void *key;
197 void *val;
198 svn_fs_path_change_t *change;
199 const char *path;
200 char action;
201 svn_log_changed_path_t *item;
203 svn_pool_clear(subpool);
205 /* KEY will be the path, VAL the change. */
206 apr_hash_this(hi, &key, NULL, &val);
207 path = (const char *) key;
208 change = val;
210 /* Skip path if unreadable. */
211 if (authz_read_func)
213 svn_boolean_t readable;
214 SVN_ERR(authz_read_func(&readable,
215 root, path,
216 authz_read_baton, subpool));
217 if (! readable)
219 found_unreadable = TRUE;
220 continue;
224 /* At least one changed-path was readable. */
225 found_readable = TRUE;
227 switch (change->change_kind)
229 case svn_fs_path_change_reset:
230 continue;
232 case svn_fs_path_change_add:
233 action = 'A';
234 break;
236 case svn_fs_path_change_replace:
237 action = 'R';
238 break;
240 case svn_fs_path_change_delete:
241 action = 'D';
242 break;
244 case svn_fs_path_change_modify:
245 default:
246 action = 'M';
247 break;
250 item = apr_pcalloc(pool, sizeof(*item));
251 item->action = action;
252 item->copyfrom_rev = SVN_INVALID_REVNUM;
253 if ((action == 'A') || (action == 'R'))
255 const char *copyfrom_path;
256 svn_revnum_t copyfrom_rev;
258 SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
259 root, path, subpool));
261 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
263 svn_boolean_t readable = TRUE;
265 if (authz_read_func)
267 svn_fs_root_t *copyfrom_root;
269 SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
270 copyfrom_rev, subpool));
271 SVN_ERR(authz_read_func(&readable,
272 copyfrom_root, copyfrom_path,
273 authz_read_baton, subpool));
274 if (! readable)
275 found_unreadable = TRUE;
278 if (readable)
280 item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
281 item->copyfrom_rev = copyfrom_rev;
285 apr_hash_set(*changed, apr_pstrdup(pool, path),
286 APR_HASH_KEY_STRING, item);
289 svn_pool_destroy(subpool);
291 if (! found_readable)
292 /* Every changed-path was unreadable. */
293 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
294 NULL, NULL);
296 if (found_unreadable)
297 /* At least one changed-path was unreadable. */
298 return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
299 NULL, NULL);
301 /* Every changed-path was readable. */
302 return SVN_NO_ERROR;
305 /* This is used by svn_repos_get_logs to keep track of multiple
306 * path history information while working through history.
308 * The two pools are swapped after each iteration through history because
309 * to get the next history requires the previous one.
311 struct path_info
313 svn_stringbuf_t *path;
314 svn_revnum_t history_rev;
315 svn_boolean_t done;
316 svn_boolean_t first_time;
318 /* If possible, we like to keep open the history object for each path,
319 since it avoids needed to open and close it many times as we walk
320 backwards in time. To do so we need two pools, so that we can clear
321 one each time through. If we're not holding the history open for
322 this path then these three pointers will be NULL. */
323 svn_fs_history_t *hist;
324 apr_pool_t *newpool;
325 apr_pool_t *oldpool;
328 /* Advance to the next history for the path.
330 * If INFO->HIST is not NULL we do this using that existing history object,
331 * otherwise we open a new one.
333 * If no more history is available or the history revision is less
334 * (earlier) than START, or the history is not available due
335 * to authorization, then INFO->DONE is set to TRUE.
337 * A STRICT value of FALSE will indicate to follow history across copied
338 * paths.
340 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
341 * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
342 * we do indeed find more history for the path.
344 static svn_error_t *
345 get_history(struct path_info *info,
346 svn_fs_t *fs,
347 svn_boolean_t strict,
348 svn_repos_authz_func_t authz_read_func,
349 void *authz_read_baton,
350 svn_revnum_t start,
351 apr_pool_t *pool)
353 svn_fs_root_t *history_root = NULL;
354 svn_fs_history_t *hist;
355 apr_pool_t *subpool;
356 const char *path;
358 if (info->hist)
360 subpool = info->newpool;
362 SVN_ERR(svn_fs_history_prev(&info->hist, info->hist,
363 strict ? FALSE : TRUE, subpool));
365 hist = info->hist;
367 else
369 subpool = svn_pool_create(pool);
371 /* Open the history located at the last rev we were at. */
372 SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
373 subpool));
375 SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
376 subpool));
378 SVN_ERR(svn_fs_history_prev(&hist, hist, strict ? FALSE : TRUE,
379 subpool));
381 if (info->first_time)
382 info->first_time = FALSE;
383 else
384 SVN_ERR(svn_fs_history_prev(&hist, hist, strict ? FALSE : TRUE,
385 subpool));
388 if (! hist)
390 svn_pool_destroy(subpool);
391 if (info->oldpool)
392 svn_pool_destroy(info->oldpool);
393 info->done = TRUE;
394 return SVN_NO_ERROR;
397 /* Fetch the location information for this history step. */
398 SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
399 hist, subpool));
401 svn_stringbuf_set(info->path, path);
403 /* If this history item predates our START revision then
404 don't fetch any more for this path. */
405 if (info->history_rev < start)
407 svn_pool_destroy(subpool);
408 if (info->oldpool)
409 svn_pool_destroy(info->oldpool);
410 info->done = TRUE;
411 return SVN_NO_ERROR;
414 /* Is the history item readable? If not, done with path. */
415 if (authz_read_func)
417 svn_boolean_t readable;
418 SVN_ERR(svn_fs_revision_root(&history_root, fs,
419 info->history_rev,
420 subpool));
421 SVN_ERR(authz_read_func(&readable, history_root,
422 info->path->data,
423 authz_read_baton,
424 subpool));
425 if (! readable)
426 info->done = TRUE;
429 if (! info->hist)
431 svn_pool_destroy(subpool);
433 else
435 apr_pool_t *temppool = info->oldpool;
436 info->oldpool = info->newpool;
437 svn_pool_clear(temppool);
438 info->newpool = temppool;
441 return SVN_NO_ERROR;
444 /* Set INFO->HIST to the next history for the path *if* there is history
445 * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
447 * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
448 * otherwise it is not touched.
450 * If we do need to get the next history revision for the path, call
451 * get_history to do it -- see it for details.
453 static svn_error_t *
454 check_history(svn_boolean_t *changed,
455 struct path_info *info,
456 svn_fs_t *fs,
457 svn_revnum_t current,
458 svn_boolean_t strict,
459 svn_repos_authz_func_t authz_read_func,
460 void *authz_read_baton,
461 svn_revnum_t start,
462 apr_pool_t *pool)
464 /* If we're already done with histories for this path,
465 don't try to fetch any more. */
466 if (info->done)
467 return SVN_NO_ERROR;
469 /* If the last rev we got for this path is less than CURRENT,
470 then just return and don't fetch history for this path.
471 The caller will get to this rev eventually or else reach
472 the limit. */
473 if (info->history_rev < current)
474 return SVN_NO_ERROR;
476 /* If the last rev we got for this path is equal to CURRENT
477 then set *CHANGED to true and get the next history
478 rev where this path was changed. */
479 *changed = TRUE;
480 SVN_ERR(get_history(info, fs, strict, authz_read_func,
481 authz_read_baton, start, pool));
482 return SVN_NO_ERROR;
485 /* Return the next interesting revision in our list of HISTORIES. */
486 static svn_revnum_t
487 next_history_rev(apr_array_header_t *histories)
489 svn_revnum_t next_rev = SVN_INVALID_REVNUM;
490 int i;
492 for (i = 0; i < histories->nelts; ++i)
494 struct path_info *info = APR_ARRAY_IDX(histories, i,
495 struct path_info *);
496 if (info->done)
497 continue;
498 if (info->history_rev > next_rev)
499 next_rev = info->history_rev;
502 return next_rev;
505 /* Return the combined rangelists for everyone's mergeinfo for the
506 PATHS tree at REV in *RANGELIST. Perform all allocations in POOL. */
507 static svn_error_t *
508 get_combined_mergeinfo(svn_mergeinfo_t *combined_mergeinfo,
509 svn_fs_t *fs,
510 svn_revnum_t rev,
511 const apr_array_header_t *paths,
512 apr_pool_t *pool)
514 svn_fs_root_t *root;
515 apr_hash_index_t *hi;
516 svn_mergeinfo_catalog_t tree_mergeinfo;
517 apr_pool_t *subpool = svn_pool_create(pool);
518 apr_pool_t *iterpool = svn_pool_create(subpool);
519 apr_array_header_t *query_paths;
520 int i;
522 /* Revision 0 doesn't have any mergeinfo. */
523 if (rev == 0)
525 *combined_mergeinfo = apr_hash_make(pool);
526 svn_pool_destroy(subpool);
527 return SVN_NO_ERROR;
530 /* Get the mergeinfo for each tree roots in PATHS. */
531 SVN_ERR(svn_fs_revision_root(&root, fs, rev, subpool));
533 /* If we're looking at a previous revision, some of the paths
534 might not exist, and svn_fs_get_mergeinfo expects them to! */
535 query_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
536 for (i = 0; i < paths->nelts; i++)
538 const char *path = APR_ARRAY_IDX(paths, i, const char *);
539 svn_node_kind_t kind;
541 svn_pool_clear(iterpool);
542 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool));
543 if (kind == svn_node_none)
545 svn_revnum_t copy_rev;
546 const char *copy_path;
547 svn_fs_root_t *rev_root;
549 /* Check to see if the node was copied, and if so, use the previous
550 path to check for mergeinfo. */
551 SVN_ERR(svn_fs_revision_root(&rev_root, fs, rev + 1, iterpool));
552 SVN_ERR(svn_fs_copied_from(&copy_rev, &copy_path, rev_root, path,
553 subpool));
554 if (copy_path != NULL)
555 APR_ARRAY_PUSH(query_paths, const char *) = copy_path;
557 else
558 APR_ARRAY_PUSH(query_paths, const char *) = path;
561 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
562 because we are already doing authz on the changed paths and the log
563 messages when we go to fill the log entry. See fill_log_entry() for
564 details. */
565 SVN_ERR(svn_fs_get_mergeinfo(&tree_mergeinfo, root, query_paths,
566 svn_mergeinfo_inherited, TRUE,
567 subpool));
569 *combined_mergeinfo = apr_hash_make(subpool);
571 /* Merge all the mergeinfos into one mergeinfo */
572 for (hi = apr_hash_first(subpool, tree_mergeinfo); hi; hi = apr_hash_next(hi))
574 svn_mergeinfo_t mergeinfo;
576 apr_hash_this(hi, NULL, NULL, (void *)&mergeinfo);
577 SVN_ERR(svn_mergeinfo_merge(*combined_mergeinfo, mergeinfo, subpool));
580 *combined_mergeinfo = svn_mergeinfo_dup(*combined_mergeinfo, pool);
582 svn_pool_destroy(subpool);
584 return SVN_NO_ERROR;
587 /* Determine all the revisions which were merged into PATHS in REV. Return
588 them as a new MERGEINFO. */
589 static svn_error_t *
590 get_merged_rev_mergeinfo(svn_mergeinfo_t *mergeinfo,
591 svn_fs_t *fs,
592 const apr_array_header_t *paths,
593 svn_revnum_t rev,
594 apr_pool_t *pool)
596 apr_hash_t *curr_mergeinfo, *prev_mergeinfo;
597 apr_hash_t *deleted, *changed;
598 apr_pool_t *subpool;
600 /* Revision 0 is always empty. */
601 if (rev == 0)
603 *mergeinfo = apr_hash_make(pool);
604 return SVN_NO_ERROR;
607 subpool = svn_pool_create(pool);
609 SVN_ERR(get_combined_mergeinfo(&curr_mergeinfo, fs, rev, paths, subpool));
610 SVN_ERR(get_combined_mergeinfo(&prev_mergeinfo, fs, rev - 1, paths, subpool));
612 SVN_ERR(svn_mergeinfo_diff(&deleted, &changed, prev_mergeinfo,
613 curr_mergeinfo, FALSE,
614 subpool));
615 SVN_ERR(svn_mergeinfo_merge(changed, deleted, subpool));
617 *mergeinfo = svn_mergeinfo_dup(changed, pool);
618 svn_pool_destroy(subpool);
620 return SVN_NO_ERROR;
623 /* Fill LOG_ENTRY with history information in FS at REV. */
624 static svn_error_t *
625 fill_log_entry(svn_log_entry_t *log_entry,
626 svn_revnum_t rev,
627 svn_fs_t *fs,
628 svn_boolean_t discover_changed_paths,
629 const apr_array_header_t *revprops,
630 svn_repos_authz_func_t authz_read_func,
631 void *authz_read_baton,
632 apr_pool_t *pool)
634 apr_hash_t *r_props, *changed_paths = NULL;
635 svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
637 /* Discover changed paths if the user requested them
638 or if we need to check that they are readable. */
639 if ((rev > 0)
640 && (authz_read_func || discover_changed_paths))
642 svn_fs_root_t *newroot;
643 svn_error_t *patherr;
645 SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
646 patherr = detect_changed(&changed_paths,
647 newroot, fs,
648 authz_read_func, authz_read_baton,
649 pool);
651 if (patherr
652 && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
654 /* All changed-paths are unreadable, so clear all fields. */
655 svn_error_clear(patherr);
656 changed_paths = NULL;
657 get_revprops = FALSE;
659 else if (patherr
660 && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
662 /* At least one changed-path was unreadable, so censor all
663 but author and date. (The unreadable paths are already
664 missing from the hash.) */
665 svn_error_clear(patherr);
666 censor_revprops = TRUE;
668 else if (patherr)
669 return patherr;
671 /* It may be the case that an authz func was passed in, but
672 the user still doesn't want to see any changed-paths. */
673 if (! discover_changed_paths)
674 changed_paths = NULL;
677 if (get_revprops)
679 /* User is allowed to see at least some revprops. */
680 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
681 if (revprops == NULL)
683 /* Requested all revprops... */
684 if (censor_revprops)
686 /* ... but we can only return author/date. */
687 log_entry->revprops = apr_hash_make(pool);
688 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
689 APR_HASH_KEY_STRING,
690 apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
691 APR_HASH_KEY_STRING));
692 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
693 APR_HASH_KEY_STRING,
694 apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
695 APR_HASH_KEY_STRING));
697 else
698 /* ... so return all we got. */
699 log_entry->revprops = r_props;
701 else
703 /* Requested only some revprops... */
704 int i;
705 for (i = 0; i < revprops->nelts; i++)
707 char *name = APR_ARRAY_IDX(revprops, i, char *);
708 svn_string_t *value = apr_hash_get(r_props, name,
709 APR_HASH_KEY_STRING);
710 if (censor_revprops
711 && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
712 || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
713 /* ... but we can only return author/date. */
714 continue;
715 if (log_entry->revprops == NULL)
716 log_entry->revprops = apr_hash_make(pool);
717 apr_hash_set(log_entry->revprops, name,
718 APR_HASH_KEY_STRING, value);
723 log_entry->changed_paths = changed_paths;
724 log_entry->revision = rev;
726 return SVN_NO_ERROR;
729 /* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
731 * FS is used with REV to fetch the interesting history information,
732 * such as changed paths, revprops, etc.
734 * The detect_changed function is used if either AUTHZ_READ_FUNC is
735 * not NULL, or if DISCOVER_CHANGED_PATHS is TRUE. See it for details.
737 * If DESCENDING_ORDER is true, send child messages in descending order.
739 * If REVPROPS is NULL, retrieve all revprops; else, retrieve only the
740 * revprops named in the array (i.e. retrieve none if the array is empty).
742 static svn_error_t *
743 send_log(svn_revnum_t rev,
744 svn_fs_t *fs,
745 svn_boolean_t discover_changed_paths,
746 const apr_array_header_t *revprops,
747 svn_boolean_t has_children,
748 svn_log_entry_receiver_t receiver,
749 void *receiver_baton,
750 svn_repos_authz_func_t authz_read_func,
751 void *authz_read_baton,
752 apr_pool_t *pool)
754 svn_log_entry_t *log_entry;
756 log_entry = svn_log_entry_create(pool);
757 SVN_ERR(fill_log_entry(log_entry, rev, fs, discover_changed_paths,
758 revprops, authz_read_func, authz_read_baton,
759 pool));
760 log_entry->has_children = has_children;
762 /* Send the entry to the receiver. */
763 SVN_ERR((*receiver)(receiver_baton, log_entry, pool));
765 return SVN_NO_ERROR;
768 /* This controls how many history objects we keep open. For any targets
769 over this number we have to open and close their histories as needed,
770 which is CPU intensive, but keeps us from using an unbounded amount of
771 memory. */
772 #define MAX_OPEN_HISTORIES 32
774 /* Get the histories for PATHS, and store them in *HISTORIES. */
775 static svn_error_t *
776 get_path_histories(apr_array_header_t **histories,
777 svn_fs_t *fs,
778 const apr_array_header_t *paths,
779 svn_revnum_t hist_start,
780 svn_revnum_t hist_end,
781 svn_boolean_t strict_node_history,
782 svn_repos_authz_func_t authz_read_func,
783 void *authz_read_baton,
784 apr_pool_t *pool)
786 svn_fs_root_t *root;
787 apr_pool_t *iterpool;
788 int i;
790 /* Create a history object for each path so we can walk through
791 them all at the same time until we have all changes or LIMIT
792 is reached.
794 There is some pool fun going on due to the fact that we have
795 to hold on to the old pool with the history before we can
796 get the next history.
798 *histories = apr_array_make(pool, paths->nelts,
799 sizeof(struct path_info *));
801 SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
803 iterpool = svn_pool_create(pool);
804 for (i = 0; i < paths->nelts; i++)
806 const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
807 struct path_info *info = apr_palloc(pool,
808 sizeof(struct path_info));
810 if (authz_read_func)
812 svn_boolean_t readable;
814 svn_pool_clear(iterpool);
816 SVN_ERR(authz_read_func(&readable, root, this_path,
817 authz_read_baton, iterpool));
818 if (! readable)
819 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
822 info->path = svn_stringbuf_create(this_path, pool);
823 info->done = FALSE;
824 info->history_rev = hist_end;
825 info->first_time = TRUE;
827 if (i < MAX_OPEN_HISTORIES)
829 SVN_ERR(svn_fs_node_history(&info->hist, root, this_path, pool));
830 info->newpool = svn_pool_create(pool);
831 info->oldpool = svn_pool_create(pool);
833 else
835 info->hist = NULL;
836 info->oldpool = NULL;
837 info->newpool = NULL;
840 SVN_ERR(get_history(info, fs,
841 strict_node_history,
842 authz_read_func, authz_read_baton,
843 hist_start, pool));
844 APR_ARRAY_PUSH(*histories, struct path_info *) = info;
846 svn_pool_destroy(iterpool);
848 return SVN_NO_ERROR;
851 /* Remove and return the first item from ARR. */
852 static void *
853 array_pop_front(apr_array_header_t *arr)
855 void *item = arr->elts;
857 if (apr_is_empty_array(arr))
858 return NULL;
860 arr->elts += arr->elt_size;
861 arr->nelts -= 1;
862 arr->nalloc -= 1;
863 return item;
866 /* A struct which represents a single revision range, and the paths which
867 have mergeinfo in that range. */
868 struct path_list_range
870 apr_array_header_t *paths;
871 svn_merge_range_t range;
874 /* A struct which represents "inverse mergeinfo", that is, instead of having
875 a path->revision_range_list mapping, which is the way mergeinfo is commonly
876 represented, this struct enables a revision_range_list,path tuple, where
877 the paths can be accessed by revision. */
878 struct rangelist_path
880 apr_array_header_t *rangelist;
881 const char *path;
884 /* Comparator function for combine_mergeinfo_path_lists(). Sorts
885 rangelist_path structs in increasing order based upon starting revision,
886 then ending revision of the first element in the rangelist.
888 This does not sort rangelists based upon subsequent elements, only the
889 first range. We'll sort any subsequent ranges in the correct order
890 when they get bumped up to the front by removal of earlier ones, so we
891 don't really have to sort them here. See combine_mergeinfo_path_lists()
892 for details. */
893 static int
894 compare_rangelist_paths(const void *a, const void *b)
896 struct rangelist_path *rpa = *((struct rangelist_path **) a);
897 struct rangelist_path *rpb = *((struct rangelist_path **) b);
898 svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
899 svn_merge_range_t *);
900 svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
901 svn_merge_range_t *);
903 if (mra->start < mrb->start)
904 return -1;
905 if (mra->start > mrb->start)
906 return 1;
907 if (mra->end < mrb->end)
908 return -1;
909 if (mra->end > mrb->end)
910 return 1;
912 return 0;
915 /* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
916 'struct path_list_range's. This list represents the rangelists in
917 MERGEINFO and each path which has mergeinfo in that range. */
918 static svn_error_t *
919 combine_mergeinfo_path_lists(apr_array_header_t **combined_list,
920 svn_mergeinfo_t mergeinfo,
921 apr_pool_t *pool)
923 apr_hash_index_t *hi;
924 apr_array_header_t *rangelist_paths;
925 struct rangelist_path *first_rp;
926 apr_pool_t *subpool = svn_pool_create(pool);
928 /* Create a list of (revision range, path) tuples from MERGEINFO. */
929 rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
930 sizeof(struct rangelist_path *));
931 for (hi = apr_hash_first(subpool, mergeinfo); hi;
932 hi = apr_hash_next(hi))
934 int i;
935 struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
936 apr_hash_this(hi, (void *) &rp->path, NULL,
937 (void *) &rp->rangelist);
938 APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
940 /* We need to make local copies of the rangelist, since we will be
941 modifying it, below. */
942 rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
944 /* Make all of the rangelists inclusive, both start and end. */
945 for (i = 0; i < rp->rangelist->nelts; i++)
946 APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
949 /* Loop over the (revision range, path) tuples, chopping them into
950 (revision range, paths) tuples, and appending those to the output list. */
951 *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
952 while (rangelist_paths->nelts > 1)
954 svn_revnum_t youngest, next_youngest, tail, youngest_end;
955 struct path_list_range *plr;
956 struct rangelist_path *rp;
957 int num_revs;
958 int i;
960 /* First, sort the list such that the start revision of the first
961 revision arrays are sorted. */
962 qsort(rangelist_paths->elts, rangelist_paths->nelts,
963 rangelist_paths->elt_size, compare_rangelist_paths);
965 /* Next, find the number of revision ranges which start with the same
966 revision. */
967 rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
968 youngest =
969 APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
970 next_youngest = youngest;
971 for (num_revs = 1; next_youngest == youngest; num_revs++)
973 if (num_revs == rangelist_paths->nelts)
975 num_revs += 1;
976 break;
978 rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
979 struct rangelist_path *);
980 next_youngest =
981 APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
983 num_revs -= 1;
985 /* The start of the new range will be YOUNGEST, and we now find the end
986 of the new range, which should be either one less than the next
987 earliest start of a rangelist, or the end of the first rangelist. */
988 youngest_end = APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
989 struct rangelist_path *)->rangelist,
990 0, svn_merge_range_t *)->end;
991 if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
992 tail = youngest_end;
993 else
994 tail = next_youngest - 1;
996 /* Insert the (earliest, tail) tuple into the output list, along with
997 a list of paths which match it. */
998 plr = apr_palloc(pool, sizeof(*plr));
999 plr->range.start = youngest;
1000 plr->range.end = tail;
1001 plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
1002 for (i = 0; i < num_revs; i++)
1003 APR_ARRAY_PUSH(plr->paths, const char *) =
1004 APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
1005 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1007 /* Now, check to see which (rangelist path) combinations we can remove,
1008 and do so. */
1009 for (i = 0; i < num_revs; i++)
1011 svn_merge_range_t *range;
1012 rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
1013 range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
1015 /* Set the start of the range to beyond the end of the range we
1016 just built. If the range is now "inverted", we can get pop it
1017 off the list. */
1018 range->start = tail + 1;
1019 if (range->start > range->end)
1021 if (rp->rangelist->nelts == 1)
1023 /* The range is the only on its list, so we should remove
1024 the entire rangelist_path, adjusting our loop control
1025 variables appropriately. */
1026 array_pop_front(rangelist_paths);
1027 i--;
1028 num_revs--;
1030 else
1032 /* We have more than one range on the list, so just remove
1033 the first one. */
1034 array_pop_front(rp->rangelist);
1040 /* Finally, add the last remaining (revision range, path) to the output
1041 list. */
1042 first_rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1043 while (first_rp->rangelist->nelts > 0)
1045 struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
1047 plr->paths = apr_array_make(pool, 1, sizeof(const char *));
1048 APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
1049 plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0, svn_merge_range_t *);
1050 array_pop_front(first_rp->rangelist);
1051 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1054 svn_pool_destroy(subpool);
1056 return SVN_NO_ERROR;
1059 /* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
1060 RECEIVER with RECEIVER_BATON on them. If DESCENDING_ORDER is TRUE, send
1061 the logs back as we find them, else buffer the logs and send them back
1062 in youngest->oldest order.
1064 FOUND_REVISIONS is a list of revisions that have already been located,
1065 and which should not be sent again. It should only be NULL on the
1066 initial invocation, not on subsequent recursive calls.
1068 Unlike do_logs(), below, this function includes merged revisions in the
1069 list of revisions sent back. Other parameters are the same as
1070 svn_repos_get_logs4().
1072 In order to prevent log message overload, we always do merged logs in a
1073 non-streamy sort of way, using this algorithm:
1074 1) Get all mainline revisions for PATHS (regardless of LIMIT), marking
1075 branching revisions as such.
1076 - Stop if we encounter a revision which has already been retrieved,
1077 such as when a branch hits the mainline of history.
1078 2) Send the fetched revisions (up to LIMIT), in either forward or reverse
1079 order.
1080 3) When a merging revision is hit, recurse using the merged revisions.
1082 static svn_error_t *
1083 do_merged_logs(svn_fs_t *fs,
1084 const apr_array_header_t *paths,
1085 svn_revnum_t hist_start,
1086 svn_revnum_t hist_end,
1087 int limit,
1088 svn_boolean_t discover_changed_paths,
1089 svn_boolean_t strict_node_history,
1090 const apr_array_header_t *revprops,
1091 svn_boolean_t descending_order,
1092 apr_hash_t *found_revisions,
1093 svn_log_entry_receiver_t receiver,
1094 void *receiver_baton,
1095 svn_repos_authz_func_t authz_read_func,
1096 void *authz_read_baton,
1097 apr_pool_t *permpool,
1098 apr_pool_t *pool)
1100 apr_pool_t *iterpool;
1101 apr_array_header_t *revs = apr_array_make(pool, 0, sizeof(svn_revnum_t));
1102 svn_revnum_t current;
1103 apr_array_header_t *histories;
1104 svn_boolean_t any_histories_left = TRUE;
1105 svn_boolean_t mainline_run = FALSE;
1106 svn_boolean_t use_limit = TRUE;
1107 int i;
1109 if (found_revisions == NULL)
1111 mainline_run = TRUE;
1112 found_revisions = apr_hash_make(pool);
1115 if (limit == 0)
1116 use_limit = FALSE;
1118 /* We only really care about revisions in which those paths were changed.
1119 So we ask the filesystem for all the revisions in which any of the
1120 paths was changed. */
1121 SVN_ERR(get_path_histories(&histories, fs, paths, 0, hist_end,
1122 strict_node_history, authz_read_func,
1123 authz_read_baton, pool));
1125 /* Loop through all the revisions in the range and add any
1126 where a path was changed to the array. */
1127 iterpool = svn_pool_create(pool);
1128 for (current = hist_end; any_histories_left;
1129 current = next_history_rev(histories))
1131 svn_boolean_t changed = FALSE;
1132 any_histories_left = FALSE;
1134 /* Stop if we encounter a revision we've already seen before. */
1135 if (!mainline_run
1136 && apr_hash_get(found_revisions, &current, sizeof (svn_revnum_t)))
1137 break;
1139 svn_pool_clear(iterpool);
1140 for (i = 0; i < histories->nelts; i++)
1142 struct path_info *info = APR_ARRAY_IDX(histories, i,
1143 struct path_info *);
1145 /* Check history for this path in current rev. */
1146 SVN_ERR(check_history(&changed, info, fs, current,
1147 strict_node_history, authz_read_func,
1148 authz_read_baton, 0, pool));
1149 if (! info->done)
1150 any_histories_left = TRUE;
1153 /* If any of the paths changed in this rev then add it. */
1154 if (changed)
1156 svn_revnum_t *cur_rev = apr_palloc(permpool, sizeof(*cur_rev));
1157 svn_mergeinfo_t mergeinfo;
1158 apr_array_header_t *cur_paths = apr_array_make(iterpool, paths->nelts,
1159 sizeof(const char *));
1161 /* Get the current paths of our history objects. */
1162 for (i = 0; i < histories->nelts; i++)
1164 struct path_info *info = APR_ARRAY_IDX(histories, i,
1165 struct path_info *);
1166 APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
1169 APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
1170 SVN_ERR(get_merged_rev_mergeinfo(&mergeinfo, fs, cur_paths, current,
1171 permpool));
1173 *cur_rev = current;
1174 apr_hash_set(found_revisions, cur_rev, sizeof (svn_revnum_t),
1175 mergeinfo);
1179 /* Work loop for processing the revisions we found. */
1180 for (i = 0; (i < revs->nelts) && ( !(use_limit && limit == 0) ); ++i)
1182 svn_revnum_t rev = APR_ARRAY_IDX(revs,
1183 (descending_order ? i :
1184 revs->nelts - i - 1),
1185 svn_revnum_t);
1186 svn_mergeinfo_t mergeinfo = apr_hash_get(found_revisions, &rev,
1187 sizeof (svn_revnum_t));
1188 svn_boolean_t has_children = (apr_hash_count(mergeinfo) > 0);
1190 svn_pool_clear(iterpool);
1192 if (rev < hist_start)
1193 break;
1195 SVN_ERR(send_log(rev, fs, discover_changed_paths, revprops,
1196 has_children, receiver, receiver_baton, authz_read_func,
1197 authz_read_baton, iterpool));
1199 if (has_children)
1201 apr_array_header_t *combined_list;
1202 svn_log_entry_t *empty_log_entry;
1203 apr_pool_t *iterpool2 = svn_pool_create(iterpool);
1204 int j;
1206 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, mergeinfo,
1207 iterpool));
1209 /* Because the combined_lists are ordered youngest to oldest,
1210 iterate over them in reverse. */
1211 for (j = combined_list->nelts - 1; j >= 0; j--)
1213 struct path_list_range *pl_range = APR_ARRAY_IDX(combined_list, j,
1214 struct path_list_range *);
1216 svn_pool_clear(iterpool2);
1217 SVN_ERR(do_merged_logs(fs, pl_range->paths,
1218 pl_range->range.start, pl_range->range.end,
1219 0, discover_changed_paths,
1220 strict_node_history, revprops, TRUE,
1221 found_revisions, receiver, receiver_baton,
1222 authz_read_func, authz_read_baton,
1223 permpool, iterpool2));
1225 svn_pool_destroy(iterpool2);
1227 /* Send the empty revision. */
1228 empty_log_entry = svn_log_entry_create(iterpool);
1229 empty_log_entry->revision = SVN_INVALID_REVNUM;
1230 SVN_ERR((*receiver)(receiver_baton, empty_log_entry, iterpool));
1233 limit -= 1;
1236 svn_pool_destroy(iterpool);
1238 return SVN_NO_ERROR;
1241 /* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
1242 RECEIVER with RECEIVER_BATON on them. If DESCENDING_ORDER is TRUE, send
1243 the logs back as we find them, else buffer the logs and send them back
1244 in youngest->oldest order.
1246 Other parameters are the same as svn_repos_get_logs4(). */
1247 static svn_error_t *
1248 do_logs(svn_fs_t *fs,
1249 const apr_array_header_t *paths,
1250 svn_revnum_t hist_start,
1251 svn_revnum_t hist_end,
1252 int limit,
1253 svn_boolean_t discover_changed_paths,
1254 svn_boolean_t strict_node_history,
1255 const apr_array_header_t *revprops,
1256 svn_boolean_t descending_order,
1257 svn_log_entry_receiver_t receiver,
1258 void *receiver_baton,
1259 svn_repos_authz_func_t authz_read_func,
1260 void *authz_read_baton,
1261 apr_pool_t *pool)
1263 apr_pool_t *iterpool;
1264 apr_array_header_t *revs = NULL;
1265 svn_revnum_t current;
1266 apr_array_header_t *histories;
1267 svn_boolean_t any_histories_left = TRUE;
1268 int send_count = 0;
1269 int i;
1271 /* We only really care about revisions in which those paths were changed.
1272 So we ask the filesystem for all the revisions in which any of the
1273 paths was changed. */
1274 SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
1275 strict_node_history, authz_read_func,
1276 authz_read_baton, pool));
1278 /* Loop through all the revisions in the range and add any
1279 where a path was changed to the array, or if they wanted
1280 history in reverse order just send it to them right away.
1282 iterpool = svn_pool_create(pool);
1283 for (current = hist_end;
1284 current >= hist_start && any_histories_left;
1285 current = next_history_rev(histories))
1287 svn_boolean_t changed = FALSE;
1288 any_histories_left = FALSE;
1289 svn_pool_clear(iterpool);
1291 for (i = 0; i < histories->nelts; i++)
1293 struct path_info *info = APR_ARRAY_IDX(histories, i,
1294 struct path_info *);
1296 /* Check history for this path in current rev. */
1297 SVN_ERR(check_history(&changed, info, fs, current,
1298 strict_node_history,
1299 authz_read_func, authz_read_baton,
1300 hist_start, pool));
1301 if (! info->done)
1302 any_histories_left = TRUE;
1305 /* If any of the paths changed in this rev then add or send it. */
1306 if (changed)
1308 /* If they wanted it in reverse order we can send it completely
1309 streamily right now. */
1310 if (descending_order)
1312 SVN_ERR(send_log(current, fs, discover_changed_paths,
1313 revprops, FALSE, receiver, receiver_baton,
1314 authz_read_func, authz_read_baton, iterpool));
1316 if (limit && ++send_count >= limit)
1317 break;
1319 else
1321 /* They wanted it in forward order, so we have to buffer up
1322 a list of revs and process it later. */
1323 if (! revs)
1324 revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
1325 APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
1330 if (revs)
1332 /* Work loop for processing the revisions we found since they wanted
1333 history in forward order. */
1334 for (i = 0; i < revs->nelts; ++i)
1336 svn_pool_clear(iterpool);
1337 SVN_ERR(send_log(APR_ARRAY_IDX(revs, revs->nelts - i - 1,
1338 svn_revnum_t),
1339 fs, discover_changed_paths, revprops, FALSE,
1340 receiver, receiver_baton, authz_read_func,
1341 authz_read_baton, iterpool));
1343 if (limit && i + 1 >= limit)
1344 break;
1348 svn_pool_destroy(iterpool);
1350 return SVN_NO_ERROR;
1353 svn_error_t *
1354 svn_repos_get_logs4(svn_repos_t *repos,
1355 const apr_array_header_t *paths,
1356 svn_revnum_t start,
1357 svn_revnum_t end,
1358 int limit,
1359 svn_boolean_t discover_changed_paths,
1360 svn_boolean_t strict_node_history,
1361 svn_boolean_t include_merged_revisions,
1362 const apr_array_header_t *revprops,
1363 svn_repos_authz_func_t authz_read_func,
1364 void *authz_read_baton,
1365 svn_log_entry_receiver_t receiver,
1366 void *receiver_baton,
1367 apr_pool_t *pool)
1369 svn_revnum_t head = SVN_INVALID_REVNUM;
1370 svn_fs_t *fs = repos->fs;
1371 svn_boolean_t descending_order;
1372 svn_revnum_t hist_start = start;
1373 svn_revnum_t hist_end = end;
1375 /* Setup log range. */
1376 SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
1378 if (! SVN_IS_VALID_REVNUM(start))
1379 start = head;
1381 if (! SVN_IS_VALID_REVNUM(end))
1382 end = head;
1384 /* Check that revisions are sane before ever invoking receiver. */
1385 if (start > head)
1386 return svn_error_createf
1387 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
1388 _("No such revision %ld"), start);
1389 if (end > head)
1390 return svn_error_createf
1391 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
1392 _("No such revision %ld"), end);
1394 descending_order = start >= end;
1395 if (descending_order)
1397 hist_start = end;
1398 hist_end = start;
1402 /* If paths were specified, then we only really care about revisions
1403 in which those paths were changed. So we ask the filesystem for
1404 all the revisions in which any of the paths was changed.
1406 SPECIAL CASE: If we were given only path, and that path is empty,
1407 then the results are the same as if we were passed no paths at
1408 all. Why? Because the answer to the question "In which
1409 revisions was the root of the filesystem changed?" is always
1410 "Every single one of them." And since this section of code is
1411 only about answering that question, and we already know the
1412 answer ... well, you get the picture.
1414 if (! paths)
1415 paths = apr_array_make(pool, 0, sizeof(const char *));
1417 if ((! paths->nelts)
1418 || (paths->nelts == 1 &&
1419 svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))))
1421 int send_count = 0;
1422 int i;
1423 apr_pool_t *iterpool = svn_pool_create(pool);
1425 /* They want history for the root path, so every rev has a change. */
1426 send_count = hist_end - hist_start + 1;
1427 if (limit && send_count > limit)
1428 send_count = limit;
1429 for (i = 0; i < send_count; ++i)
1431 svn_revnum_t rev = hist_start + i;
1433 svn_pool_clear(iterpool);
1435 if (descending_order)
1436 rev = hist_end - i;
1437 SVN_ERR(send_log(rev, fs, discover_changed_paths, revprops, FALSE,
1438 receiver, receiver_baton, authz_read_func,
1439 authz_read_baton, iterpool));
1441 svn_pool_destroy(iterpool);
1443 return SVN_NO_ERROR;
1446 if (include_merged_revisions)
1447 SVN_ERR(do_merged_logs(repos->fs, paths, hist_start, hist_end, limit,
1448 discover_changed_paths, strict_node_history,
1449 revprops, descending_order, NULL,
1450 receiver, receiver_baton,
1451 authz_read_func, authz_read_baton, pool, pool));
1452 else
1453 SVN_ERR(do_logs(repos->fs, paths, hist_start, hist_end, limit,
1454 discover_changed_paths, strict_node_history,
1455 revprops, descending_order, receiver, receiver_baton,
1456 authz_read_func, authz_read_baton, pool));
1458 return SVN_NO_ERROR;
1462 svn_error_t *
1463 svn_repos_get_logs3(svn_repos_t *repos,
1464 const apr_array_header_t *paths,
1465 svn_revnum_t start,
1466 svn_revnum_t end,
1467 int limit,
1468 svn_boolean_t discover_changed_paths,
1469 svn_boolean_t strict_node_history,
1470 svn_repos_authz_func_t authz_read_func,
1471 void *authz_read_baton,
1472 svn_log_message_receiver_t receiver,
1473 void *receiver_baton,
1474 apr_pool_t *pool)
1476 svn_log_entry_receiver_t receiver2;
1477 void *receiver2_baton;
1479 svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
1480 receiver, receiver_baton,
1481 pool);
1483 return svn_repos_get_logs4(repos, paths, start, end, limit,
1484 discover_changed_paths, strict_node_history,
1485 FALSE, svn_compat_log_revprops_in(pool),
1486 authz_read_func, authz_read_baton,
1487 receiver2, receiver2_baton,
1488 pool);
1491 svn_error_t *
1492 svn_repos_get_logs2(svn_repos_t *repos,
1493 const apr_array_header_t *paths,
1494 svn_revnum_t start,
1495 svn_revnum_t end,
1496 svn_boolean_t discover_changed_paths,
1497 svn_boolean_t strict_node_history,
1498 svn_repos_authz_func_t authz_read_func,
1499 void *authz_read_baton,
1500 svn_log_message_receiver_t receiver,
1501 void *receiver_baton,
1502 apr_pool_t *pool)
1504 return svn_repos_get_logs3(repos, paths, start, end, 0,
1505 discover_changed_paths, strict_node_history,
1506 authz_read_func, authz_read_baton, receiver,
1507 receiver_baton, pool);
1511 svn_error_t *
1512 svn_repos_get_logs(svn_repos_t *repos,
1513 const apr_array_header_t *paths,
1514 svn_revnum_t start,
1515 svn_revnum_t end,
1516 svn_boolean_t discover_changed_paths,
1517 svn_boolean_t strict_node_history,
1518 svn_log_message_receiver_t receiver,
1519 void *receiver_baton,
1520 apr_pool_t *pool)
1522 return svn_repos_get_logs3(repos, paths, start, end, 0,
1523 discover_changed_paths, strict_node_history,
1524 NULL, NULL, /* no authz stuff */
1525 receiver, receiver_baton, pool);