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 * ====================================================================
20 #define APR_WANT_STRFUNC
23 #include "svn_compat.h"
24 #include "svn_private_config.h"
25 #include "svn_pools.h"
26 #include "svn_error.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"
39 svn_repos_check_revision_access(svn_repos_revision_access_level_t
*access_level
,
41 svn_revnum_t revision
,
42 svn_repos_authz_func_t authz_read_func
,
43 void *authz_read_baton
,
46 svn_fs_t
*fs
= svn_repos_fs(repos
);
47 svn_fs_root_t
*rev_root
;
50 svn_boolean_t found_readable
= FALSE
;
51 svn_boolean_t found_unreadable
= FALSE
;
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
)
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)
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
))
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
);
83 SVN_ERR(authz_read_func(&readable
, rev_root
, key
,
84 authz_read_baton
, subpool
));
86 found_unreadable
= TRUE
;
88 found_readable
= TRUE
;
90 /* If we have at least one of each (readable/unreadable), we
92 if (found_readable
&& found_unreadable
)
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(©from_rev
, ©from_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(©from_root
, fs
,
109 copyfrom_rev
, subpool
));
110 SVN_ERR(authz_read_func(&readable
,
111 copyfrom_root
, copyfrom_path
,
112 authz_read_baton
, subpool
));
114 found_unreadable
= TRUE
;
116 /* If we have at least one of each (readable/unreadable), we
118 if (found_readable
&& found_unreadable
)
124 case svn_fs_path_change_delete
:
125 case svn_fs_path_change_modify
:
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). */
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
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.)
170 detect_changed(apr_hash_t
**changed
,
173 svn_repos_authz_func_t authz_read_func
,
174 void *authz_read_baton
,
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. */
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. */
198 svn_fs_path_change_t
*change
;
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
;
210 /* Skip path if unreadable. */
213 svn_boolean_t readable
;
214 SVN_ERR(authz_read_func(&readable
,
216 authz_read_baton
, subpool
));
219 found_unreadable
= TRUE
;
224 /* At least one changed-path was readable. */
225 found_readable
= TRUE
;
227 switch (change
->change_kind
)
229 case svn_fs_path_change_reset
:
232 case svn_fs_path_change_add
:
236 case svn_fs_path_change_replace
:
240 case svn_fs_path_change_delete
:
244 case svn_fs_path_change_modify
:
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(©from_rev
, ©from_path
,
259 root
, path
, subpool
));
261 if (copyfrom_path
&& SVN_IS_VALID_REVNUM(copyfrom_rev
))
263 svn_boolean_t readable
= TRUE
;
267 svn_fs_root_t
*copyfrom_root
;
269 SVN_ERR(svn_fs_revision_root(©from_root
, fs
,
270 copyfrom_rev
, subpool
));
271 SVN_ERR(authz_read_func(&readable
,
272 copyfrom_root
, copyfrom_path
,
273 authz_read_baton
, subpool
));
275 found_unreadable
= TRUE
;
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
,
296 if (found_unreadable
)
297 /* At least one changed-path was unreadable. */
298 return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE
,
301 /* Every changed-path was readable. */
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.
313 svn_stringbuf_t
*path
;
314 svn_revnum_t history_rev
;
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
;
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
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.
345 get_history(struct path_info
*info
,
347 svn_boolean_t strict
,
348 svn_repos_authz_func_t authz_read_func
,
349 void *authz_read_baton
,
353 svn_fs_root_t
*history_root
= NULL
;
354 svn_fs_history_t
*hist
;
360 subpool
= info
->newpool
;
362 SVN_ERR(svn_fs_history_prev(&info
->hist
, info
->hist
,
363 strict
? FALSE
: TRUE
, subpool
));
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
,
375 SVN_ERR(svn_fs_node_history(&hist
, history_root
, info
->path
->data
,
378 SVN_ERR(svn_fs_history_prev(&hist
, hist
, strict
? FALSE
: TRUE
,
381 if (info
->first_time
)
382 info
->first_time
= FALSE
;
384 SVN_ERR(svn_fs_history_prev(&hist
, hist
, strict
? FALSE
: TRUE
,
390 svn_pool_destroy(subpool
);
392 svn_pool_destroy(info
->oldpool
);
397 /* Fetch the location information for this history step. */
398 SVN_ERR(svn_fs_history_location(&path
, &info
->history_rev
,
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
);
409 svn_pool_destroy(info
->oldpool
);
414 /* Is the history item readable? If not, done with path. */
417 svn_boolean_t readable
;
418 SVN_ERR(svn_fs_revision_root(&history_root
, fs
,
421 SVN_ERR(authz_read_func(&readable
, history_root
,
431 svn_pool_destroy(subpool
);
435 apr_pool_t
*temppool
= info
->oldpool
;
436 info
->oldpool
= info
->newpool
;
437 svn_pool_clear(temppool
);
438 info
->newpool
= temppool
;
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.
454 check_history(svn_boolean_t
*changed
,
455 struct path_info
*info
,
457 svn_revnum_t current
,
458 svn_boolean_t strict
,
459 svn_repos_authz_func_t authz_read_func
,
460 void *authz_read_baton
,
464 /* If we're already done with histories for this path,
465 don't try to fetch any more. */
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
473 if (info
->history_rev
< current
)
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. */
480 SVN_ERR(get_history(info
, fs
, strict
, authz_read_func
,
481 authz_read_baton
, start
, pool
));
485 /* Return the next interesting revision in our list of HISTORIES. */
487 next_history_rev(apr_array_header_t
*histories
)
489 svn_revnum_t next_rev
= SVN_INVALID_REVNUM
;
492 for (i
= 0; i
< histories
->nelts
; ++i
)
494 struct path_info
*info
= APR_ARRAY_IDX(histories
, i
,
498 if (info
->history_rev
> next_rev
)
499 next_rev
= info
->history_rev
;
505 /* Return the combined rangelists for everyone's mergeinfo for the
506 PATHS tree at REV in *RANGELIST. Perform all allocations in POOL. */
508 get_combined_mergeinfo(svn_mergeinfo_t
*combined_mergeinfo
,
511 const apr_array_header_t
*paths
,
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
;
522 /* Revision 0 doesn't have any mergeinfo. */
525 *combined_mergeinfo
= apr_hash_make(pool
);
526 svn_pool_destroy(subpool
);
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(©_rev
, ©_path
, rev_root
, path
,
554 if (copy_path
!= NULL
)
555 APR_ARRAY_PUSH(query_paths
, const char *) = copy_path
;
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
565 SVN_ERR(svn_fs_get_mergeinfo(&tree_mergeinfo
, root
, query_paths
,
566 svn_mergeinfo_inherited
, TRUE
,
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
);
587 /* Determine all the revisions which were merged into PATHS in REV. Return
588 them as a new MERGEINFO. */
590 get_merged_rev_mergeinfo(svn_mergeinfo_t
*mergeinfo
,
592 const apr_array_header_t
*paths
,
596 apr_hash_t
*curr_mergeinfo
, *prev_mergeinfo
;
597 apr_hash_t
*deleted
, *changed
;
600 /* Revision 0 is always empty. */
603 *mergeinfo
= apr_hash_make(pool
);
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
,
615 SVN_ERR(svn_mergeinfo_merge(changed
, deleted
, subpool
));
617 *mergeinfo
= svn_mergeinfo_dup(changed
, pool
);
618 svn_pool_destroy(subpool
);
623 /* Fill LOG_ENTRY with history information in FS at REV. */
625 fill_log_entry(svn_log_entry_t
*log_entry
,
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
,
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. */
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
,
648 authz_read_func
, authz_read_baton
,
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
;
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
;
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
;
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... */
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
,
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
,
694 apr_hash_get(r_props
, SVN_PROP_REVISION_DATE
,
695 APR_HASH_KEY_STRING
));
698 /* ... so return all we got. */
699 log_entry
->revprops
= r_props
;
703 /* Requested only some revprops... */
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
);
711 && !(strcmp(name
, SVN_PROP_REVISION_AUTHOR
) == 0
712 || strcmp(name
, SVN_PROP_REVISION_DATE
) == 0))
713 /* ... but we can only return author/date. */
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
;
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).
743 send_log(svn_revnum_t rev
,
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
,
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
,
760 log_entry
->has_children
= has_children
;
762 /* Send the entry to the receiver. */
763 SVN_ERR((*receiver
)(receiver_baton
, log_entry
, pool
));
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
772 #define MAX_OPEN_HISTORIES 32
774 /* Get the histories for PATHS, and store them in *HISTORIES. */
776 get_path_histories(apr_array_header_t
**histories
,
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
,
787 apr_pool_t
*iterpool
;
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
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
));
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
));
819 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE
, NULL
, NULL
);
822 info
->path
= svn_stringbuf_create(this_path
, pool
);
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
);
836 info
->oldpool
= NULL
;
837 info
->newpool
= NULL
;
840 SVN_ERR(get_history(info
, fs
,
842 authz_read_func
, authz_read_baton
,
844 APR_ARRAY_PUSH(*histories
, struct path_info
*) = info
;
846 svn_pool_destroy(iterpool
);
851 /* Unpack a rangelist into a list of discrete revisions. */
853 rangelist_to_revs(apr_array_header_t
**revs
,
854 const apr_array_header_t
*rangelist
,
859 *revs
= apr_array_make(pool
, rangelist
->nelts
, sizeof(svn_revnum_t
));
861 for (i
= 0; i
< rangelist
->nelts
; i
++)
863 svn_merge_range_t
*range
= APR_ARRAY_IDX(rangelist
, i
,
864 svn_merge_range_t
*);
865 svn_revnum_t rev
= range
->start
+ 1;
867 while (rev
<= range
->end
)
869 APR_ARRAY_PUSH(*revs
, svn_revnum_t
) = rev
;
877 /* Look through paths in MERGEINFO, and find the one(s) in which REVISION is
878 part of it's rangelist. */
880 find_merge_sources(apr_array_header_t
**merge_sources
,
881 svn_revnum_t revision
,
882 svn_mergeinfo_t mergeinfo
,
885 apr_hash_index_t
*hi
;
887 *merge_sources
= apr_array_make(pool
, 0, sizeof(const char *));
888 for (hi
= apr_hash_first(pool
, mergeinfo
); hi
; hi
= apr_hash_next(hi
))
890 apr_array_header_t
*rangelist
;
894 apr_hash_this(hi
, (void*) &key
, NULL
, (void*) &rangelist
);
896 for (i
= 0; i
< rangelist
->nelts
; i
++)
898 svn_merge_range_t
*range
= APR_ARRAY_IDX(rangelist
, i
,
899 svn_merge_range_t
*);
901 if (revision
> range
->start
&& revision
<= range
->end
)
902 APR_ARRAY_PUSH(*merge_sources
, const char *) = key
;
909 /* Return TRUE is the paths in PATHLIST1 are the same as those in PATHLIST2,
912 pathlists_are_equal(apr_array_header_t
*pathlist1
,
913 apr_array_header_t
*pathlist2
)
917 if (pathlist1
->nelts
!= pathlist2
->nelts
)
920 for (i
= 0; i
< pathlist1
->nelts
; i
++)
922 const char *path1
= APR_ARRAY_IDX(pathlist1
, i
, const char *);
923 const char *path2
= APR_ARRAY_IDX(pathlist2
, i
, const char *);
925 if (strcmp(path1
, path2
) != 0)
932 struct path_list_range
934 apr_array_header_t
*paths
;
935 svn_merge_range_t range
;
939 combine_mergeinfo_path_lists(apr_array_header_t
**combined_list
,
940 svn_mergeinfo_t mergeinfo
,
943 apr_hash_index_t
*hi
;
944 apr_array_header_t
*rangelist
;
945 apr_array_header_t
*revs
;
946 apr_array_header_t
*path_lists
;
947 struct path_list_range
*plr
;
948 apr_pool_t
*subpool
= svn_pool_create(pool
);
951 /* Get all the revisions in all the mergeinfo. */
952 rangelist
= apr_array_make(subpool
, 0, sizeof(svn_merge_range_t
*));
953 for (hi
= apr_hash_first(subpool
, mergeinfo
); hi
;
954 hi
= apr_hash_next(hi
))
957 apr_array_header_t
*changes
;
959 apr_hash_this(hi
, (void *) &path
, NULL
, (void *) &changes
);
960 SVN_ERR(svn_rangelist_merge(&rangelist
, changes
, subpool
));
962 SVN_ERR(rangelist_to_revs(&revs
, rangelist
, subpool
));
964 /* For each revision, find the mergeinfo path(s) it belongs to.
965 TODO: Figure out a clever algorithm to do this on a per-rangelist basis. */
966 path_lists
= apr_array_make(subpool
, revs
->nelts
,
967 sizeof(apr_array_header_t
*));
968 for (i
= 0; i
< revs
->nelts
; i
++)
970 svn_revnum_t rev
= APR_ARRAY_IDX(revs
, i
, svn_revnum_t
);
971 apr_array_header_t
*paths
;
973 SVN_ERR(find_merge_sources(&paths
, rev
, mergeinfo
, pool
));
974 APR_ARRAY_PUSH(path_lists
, apr_array_header_t
*) = paths
;
977 /* Condense the revision and pathlist lists back to rangelist notiation. */
978 *combined_list
= apr_array_make(pool
, 0, sizeof(struct path_list_range
*));
979 plr
= apr_palloc(pool
, sizeof(*plr
));
980 plr
->range
.start
= APR_ARRAY_IDX(revs
, 0, svn_revnum_t
);
981 plr
->paths
= APR_ARRAY_IDX(path_lists
, 0, apr_array_header_t
*);
983 for (i
= 1; i
< revs
->nelts
; i
++)
985 apr_array_header_t
*cur_pathlist
= APR_ARRAY_IDX(path_lists
, i
,
986 apr_array_header_t
*);
987 apr_array_header_t
*prev_pathlist
= APR_ARRAY_IDX(path_lists
, i
- 1,
988 apr_array_header_t
*);
989 if (! pathlists_are_equal(cur_pathlist
, prev_pathlist
))
991 plr
->range
.end
= APR_ARRAY_IDX(revs
, i
- 1, svn_revnum_t
);
992 APR_ARRAY_PUSH(*combined_list
, struct path_list_range
*) = plr
;
994 plr
= apr_palloc(pool
, sizeof(*plr
));
995 plr
->range
.start
= APR_ARRAY_IDX(revs
, i
, svn_revnum_t
);
996 plr
->paths
= cur_pathlist
;
999 plr
->range
.end
= APR_ARRAY_IDX(revs
, i
- 1, svn_revnum_t
);
1000 APR_ARRAY_PUSH(*combined_list
, struct path_list_range
*) = plr
;
1002 svn_pool_destroy(subpool
);
1004 return SVN_NO_ERROR
;
1007 /* In order to prevent log message overload, we always do merged logs in a
1008 non-streamy sort of way, using this algorithm:
1009 1) Get all mainline revisions for PATHS (regardless of LIMIT), marking
1010 branching revisions as such.
1011 - Stop if we encounter a revision which has already been retrieved,
1012 such as when a branch hits the mainline of history.
1013 2) Send the fetched revisions (up to LIMIT), in either forward or reverse
1015 3) When a merging revision is hit, recurse using the merged revisions.
1017 static svn_error_t
*
1018 do_merged_logs(svn_fs_t
*fs
,
1019 const apr_array_header_t
*paths
,
1020 svn_revnum_t hist_start
,
1021 svn_revnum_t hist_end
,
1023 svn_boolean_t discover_changed_paths
,
1024 svn_boolean_t strict_node_history
,
1025 const apr_array_header_t
*revprops
,
1026 svn_boolean_t descending_order
,
1027 apr_hash_t
*found_revisions
,
1028 svn_log_entry_receiver_t receiver
,
1029 void *receiver_baton
,
1030 svn_repos_authz_func_t authz_read_func
,
1031 void *authz_read_baton
,
1032 apr_pool_t
*permpool
,
1035 apr_pool_t
*iterpool
;
1036 apr_array_header_t
*revs
= apr_array_make(pool
, 0, sizeof(svn_revnum_t
));
1037 svn_revnum_t current
;
1038 apr_array_header_t
*histories
;
1039 svn_boolean_t any_histories_left
= TRUE
;
1040 svn_boolean_t mainline_run
= FALSE
;
1041 svn_boolean_t use_limit
= TRUE
;
1044 if (found_revisions
== NULL
)
1046 mainline_run
= TRUE
;
1047 found_revisions
= apr_hash_make(pool
);
1053 /* We only really care about revisions in which those paths were changed.
1054 So we ask the filesystem for all the revisions in which any of the
1055 paths was changed. */
1056 SVN_ERR(get_path_histories(&histories
, fs
, paths
, 0, hist_end
,
1057 strict_node_history
, authz_read_func
,
1058 authz_read_baton
, pool
));
1060 /* Loop through all the revisions in the range and add any
1061 where a path was changed to the array. */
1062 iterpool
= svn_pool_create(pool
);
1063 for (current
= hist_end
; any_histories_left
;
1064 current
= next_history_rev(histories
))
1066 svn_boolean_t changed
= FALSE
;
1067 any_histories_left
= FALSE
;
1069 /* Stop if we encounter a revision we've already seen before. */
1071 && apr_hash_get(found_revisions
, ¤t
, sizeof (svn_revnum_t
)))
1074 svn_pool_clear(iterpool
);
1075 for (i
= 0; i
< histories
->nelts
; i
++)
1077 struct path_info
*info
= APR_ARRAY_IDX(histories
, i
,
1078 struct path_info
*);
1080 /* Check history for this path in current rev. */
1081 SVN_ERR(check_history(&changed
, info
, fs
, current
,
1082 strict_node_history
, authz_read_func
,
1083 authz_read_baton
, 0, pool
));
1085 any_histories_left
= TRUE
;
1088 /* If any of the paths changed in this rev then add it. */
1091 svn_revnum_t
*cur_rev
= apr_palloc(permpool
, sizeof(*cur_rev
));
1092 svn_mergeinfo_t mergeinfo
;
1093 apr_array_header_t
*cur_paths
= apr_array_make(iterpool
, paths
->nelts
,
1094 sizeof(const char *));
1096 /* Get the current paths of our history objects. */
1097 for (i
= 0; i
< histories
->nelts
; i
++)
1099 struct path_info
*info
= APR_ARRAY_IDX(histories
, i
,
1100 struct path_info
*);
1101 APR_ARRAY_PUSH(cur_paths
, const char *) = info
->path
->data
;
1104 APR_ARRAY_PUSH(revs
, svn_revnum_t
) = current
;
1105 SVN_ERR(get_merged_rev_mergeinfo(&mergeinfo
, fs
, cur_paths
, current
,
1109 apr_hash_set(found_revisions
, cur_rev
, sizeof (svn_revnum_t
),
1114 /* Work loop for processing the revisions we found. */
1115 for (i
= 0; (i
< revs
->nelts
) && ( !(use_limit
&& limit
== 0) ); ++i
)
1117 svn_revnum_t rev
= APR_ARRAY_IDX(revs
,
1118 (descending_order
? i
:
1119 revs
->nelts
- i
- 1),
1121 svn_mergeinfo_t mergeinfo
= apr_hash_get(found_revisions
, &rev
,
1122 sizeof (svn_revnum_t
));
1123 svn_boolean_t has_children
= (apr_hash_count(mergeinfo
) > 0);
1125 svn_pool_clear(iterpool
);
1127 if (rev
< hist_start
)
1130 SVN_ERR(send_log(rev
, fs
, discover_changed_paths
, revprops
,
1131 has_children
, receiver
, receiver_baton
, authz_read_func
,
1132 authz_read_baton
, iterpool
));
1136 apr_array_header_t
*combined_list
;
1137 svn_log_entry_t
*empty_log_entry
;
1138 apr_pool_t
*iterpool2
= svn_pool_create(iterpool
);
1141 SVN_ERR(combine_mergeinfo_path_lists(&combined_list
, mergeinfo
,
1144 /* Because the combined_lists are ordered youngest to oldest,
1145 iterate over them in reverse. */
1146 for (j
= combined_list
->nelts
- 1; j
>= 0; j
--)
1148 struct path_list_range
*pl_range
= APR_ARRAY_IDX(combined_list
, j
,
1149 struct path_list_range
*);
1151 svn_pool_clear(iterpool2
);
1152 SVN_ERR(do_merged_logs(fs
, pl_range
->paths
,
1153 pl_range
->range
.start
, pl_range
->range
.end
,
1154 0, discover_changed_paths
,
1155 strict_node_history
, revprops
, TRUE
,
1156 found_revisions
, receiver
, receiver_baton
,
1157 authz_read_func
, authz_read_baton
,
1158 permpool
, iterpool2
));
1160 svn_pool_destroy(iterpool2
);
1162 /* Send the empty revision. */
1163 empty_log_entry
= svn_log_entry_create(iterpool
);
1164 empty_log_entry
->revision
= SVN_INVALID_REVNUM
;
1165 SVN_ERR((*receiver
)(receiver_baton
, empty_log_entry
, iterpool
));
1171 svn_pool_destroy(iterpool
);
1173 return SVN_NO_ERROR
;
1176 static svn_error_t
*
1177 do_logs(svn_fs_t
*fs
,
1178 const apr_array_header_t
*paths
,
1179 svn_revnum_t hist_start
,
1180 svn_revnum_t hist_end
,
1182 svn_boolean_t discover_changed_paths
,
1183 svn_boolean_t strict_node_history
,
1184 const apr_array_header_t
*revprops
,
1185 svn_boolean_t descending_order
,
1186 svn_log_entry_receiver_t receiver
,
1187 void *receiver_baton
,
1188 svn_repos_authz_func_t authz_read_func
,
1189 void *authz_read_baton
,
1192 apr_pool_t
*iterpool
;
1193 apr_array_header_t
*revs
= NULL
;
1194 svn_revnum_t current
;
1195 apr_array_header_t
*histories
;
1196 svn_boolean_t any_histories_left
= TRUE
;
1200 /* We only really care about revisions in which those paths were changed.
1201 So we ask the filesystem for all the revisions in which any of the
1202 paths was changed. */
1203 SVN_ERR(get_path_histories(&histories
, fs
, paths
, hist_start
, hist_end
,
1204 strict_node_history
, authz_read_func
,
1205 authz_read_baton
, pool
));
1207 /* Loop through all the revisions in the range and add any
1208 where a path was changed to the array, or if they wanted
1209 history in reverse order just send it to them right away.
1211 iterpool
= svn_pool_create(pool
);
1212 for (current
= hist_end
;
1213 current
>= hist_start
&& any_histories_left
;
1214 current
= next_history_rev(histories
))
1216 svn_boolean_t changed
= FALSE
;
1217 any_histories_left
= FALSE
;
1218 svn_pool_clear(iterpool
);
1220 for (i
= 0; i
< histories
->nelts
; i
++)
1222 struct path_info
*info
= APR_ARRAY_IDX(histories
, i
,
1223 struct path_info
*);
1225 /* Check history for this path in current rev. */
1226 SVN_ERR(check_history(&changed
, info
, fs
, current
,
1227 strict_node_history
,
1228 authz_read_func
, authz_read_baton
,
1231 any_histories_left
= TRUE
;
1234 /* If any of the paths changed in this rev then add or send it. */
1237 /* If they wanted it in reverse order we can send it completely
1238 streamily right now. */
1239 if (descending_order
)
1241 SVN_ERR(send_log(current
, fs
, discover_changed_paths
,
1242 revprops
, FALSE
, receiver
, receiver_baton
,
1243 authz_read_func
, authz_read_baton
, iterpool
));
1245 if (limit
&& ++send_count
>= limit
)
1250 /* They wanted it in forward order, so we have to buffer up
1251 a list of revs and process it later. */
1253 revs
= apr_array_make(pool
, 64, sizeof(svn_revnum_t
));
1254 APR_ARRAY_PUSH(revs
, svn_revnum_t
) = current
;
1261 /* Work loop for processing the revisions we found since they wanted
1262 history in forward order. */
1263 for (i
= 0; i
< revs
->nelts
; ++i
)
1265 svn_pool_clear(iterpool
);
1266 SVN_ERR(send_log(APR_ARRAY_IDX(revs
, revs
->nelts
- i
- 1,
1268 fs
, discover_changed_paths
, revprops
, FALSE
,
1269 receiver
, receiver_baton
, authz_read_func
,
1270 authz_read_baton
, iterpool
));
1272 if (limit
&& i
+ 1 >= limit
)
1277 svn_pool_destroy(iterpool
);
1279 return SVN_NO_ERROR
;
1283 svn_repos_get_logs4(svn_repos_t
*repos
,
1284 const apr_array_header_t
*paths
,
1288 svn_boolean_t discover_changed_paths
,
1289 svn_boolean_t strict_node_history
,
1290 svn_boolean_t include_merged_revisions
,
1291 const apr_array_header_t
*revprops
,
1292 svn_repos_authz_func_t authz_read_func
,
1293 void *authz_read_baton
,
1294 svn_log_entry_receiver_t receiver
,
1295 void *receiver_baton
,
1298 svn_revnum_t head
= SVN_INVALID_REVNUM
;
1299 svn_fs_t
*fs
= repos
->fs
;
1300 svn_boolean_t descending_order
;
1301 svn_revnum_t hist_start
= start
;
1302 svn_revnum_t hist_end
= end
;
1304 /* Setup log range. */
1305 SVN_ERR(svn_fs_youngest_rev(&head
, fs
, pool
));
1307 if (! SVN_IS_VALID_REVNUM(start
))
1310 if (! SVN_IS_VALID_REVNUM(end
))
1313 /* Check that revisions are sane before ever invoking receiver. */
1315 return svn_error_createf
1316 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
1317 _("No such revision %ld"), start
);
1319 return svn_error_createf
1320 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
1321 _("No such revision %ld"), end
);
1323 descending_order
= start
>= end
;
1324 if (descending_order
)
1331 /* If paths were specified, then we only really care about revisions
1332 in which those paths were changed. So we ask the filesystem for
1333 all the revisions in which any of the paths was changed.
1335 SPECIAL CASE: If we were given only path, and that path is empty,
1336 then the results are the same as if we were passed no paths at
1337 all. Why? Because the answer to the question "In which
1338 revisions was the root of the filesystem changed?" is always
1339 "Every single one of them." And since this section of code is
1340 only about answering that question, and we already know the
1341 answer ... well, you get the picture.
1344 paths
= apr_array_make(pool
, 0, sizeof(const char *));
1346 if ((! paths
->nelts
)
1347 || (paths
->nelts
== 1 &&
1348 svn_path_is_empty(APR_ARRAY_IDX(paths
, 0, const char *))))
1352 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1354 /* They want history for the root path, so every rev has a change. */
1355 send_count
= hist_end
- hist_start
+ 1;
1356 if (limit
&& send_count
> limit
)
1358 for (i
= 0; i
< send_count
; ++i
)
1360 svn_revnum_t rev
= hist_start
+ i
;
1362 svn_pool_clear(iterpool
);
1364 if (descending_order
)
1366 SVN_ERR(send_log(rev
, fs
, discover_changed_paths
, revprops
, FALSE
,
1367 receiver
, receiver_baton
, authz_read_func
,
1368 authz_read_baton
, iterpool
));
1370 svn_pool_destroy(iterpool
);
1372 return SVN_NO_ERROR
;
1375 if (include_merged_revisions
)
1376 SVN_ERR(do_merged_logs(repos
->fs
, paths
, hist_start
, hist_end
, limit
,
1377 discover_changed_paths
, strict_node_history
,
1378 revprops
, descending_order
, NULL
,
1379 receiver
, receiver_baton
,
1380 authz_read_func
, authz_read_baton
, pool
, pool
));
1382 SVN_ERR(do_logs(repos
->fs
, paths
, hist_start
, hist_end
, limit
,
1383 discover_changed_paths
, strict_node_history
,
1384 revprops
, descending_order
, receiver
, receiver_baton
,
1385 authz_read_func
, authz_read_baton
, pool
));
1387 return SVN_NO_ERROR
;
1392 svn_repos_get_logs3(svn_repos_t
*repos
,
1393 const apr_array_header_t
*paths
,
1397 svn_boolean_t discover_changed_paths
,
1398 svn_boolean_t strict_node_history
,
1399 svn_repos_authz_func_t authz_read_func
,
1400 void *authz_read_baton
,
1401 svn_log_message_receiver_t receiver
,
1402 void *receiver_baton
,
1405 svn_log_entry_receiver_t receiver2
;
1406 void *receiver2_baton
;
1408 svn_compat_wrap_log_receiver(&receiver2
, &receiver2_baton
,
1409 receiver
, receiver_baton
,
1412 return svn_repos_get_logs4(repos
, paths
, start
, end
, limit
,
1413 discover_changed_paths
, strict_node_history
,
1414 FALSE
, svn_compat_log_revprops_in(pool
),
1415 authz_read_func
, authz_read_baton
,
1416 receiver2
, receiver2_baton
,
1421 svn_repos_get_logs2(svn_repos_t
*repos
,
1422 const apr_array_header_t
*paths
,
1425 svn_boolean_t discover_changed_paths
,
1426 svn_boolean_t strict_node_history
,
1427 svn_repos_authz_func_t authz_read_func
,
1428 void *authz_read_baton
,
1429 svn_log_message_receiver_t receiver
,
1430 void *receiver_baton
,
1433 return svn_repos_get_logs3(repos
, paths
, start
, end
, 0,
1434 discover_changed_paths
, strict_node_history
,
1435 authz_read_func
, authz_read_baton
, receiver
,
1436 receiver_baton
, pool
);
1441 svn_repos_get_logs(svn_repos_t
*repos
,
1442 const apr_array_header_t
*paths
,
1445 svn_boolean_t discover_changed_paths
,
1446 svn_boolean_t strict_node_history
,
1447 svn_log_message_receiver_t receiver
,
1448 void *receiver_baton
,
1451 return svn_repos_get_logs3(repos
, paths
, start
, end
, 0,
1452 discover_changed_paths
, strict_node_history
,
1453 NULL
, NULL
, /* no authz stuff */
1454 receiver
, receiver_baton
, pool
);