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 /* Remove and return the first item from ARR. */
853 array_pop_front(apr_array_header_t
*arr
)
855 void *item
= arr
->elts
;
857 if (apr_is_empty_array(arr
))
860 arr
->elts
+= arr
->elt_size
;
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
;
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()
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
)
905 if (mra
->start
> mrb
->start
)
907 if (mra
->end
< mrb
->end
)
909 if (mra
->end
> mrb
->end
)
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. */
919 combine_mergeinfo_path_lists(apr_array_header_t
**combined_list
,
920 svn_mergeinfo_t mergeinfo
,
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
))
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
;
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
967 rp
= APR_ARRAY_IDX(rangelist_paths
, 0, struct rangelist_path
*);
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
)
978 rp
= APR_ARRAY_IDX(rangelist_paths
, num_revs
,
979 struct rangelist_path
*);
981 APR_ARRAY_IDX(rp
->rangelist
, 0, struct svn_merge_range_t
*)->start
;
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
) )
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,
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
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
);
1032 /* We have more than one range on the list, so just remove
1034 array_pop_front(rp
->rangelist
);
1040 /* Finally, add the last remaining (revision range, path) to the output
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
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
,
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
,
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
;
1109 if (found_revisions
== NULL
)
1111 mainline_run
= TRUE
;
1112 found_revisions
= apr_hash_make(pool
);
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. */
1136 && apr_hash_get(found_revisions
, ¤t
, sizeof (svn_revnum_t
)))
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
));
1150 any_histories_left
= TRUE
;
1153 /* If any of the paths changed in this rev then add it. */
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
,
1174 apr_hash_set(found_revisions
, cur_rev
, sizeof (svn_revnum_t
),
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),
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
)
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
));
1201 apr_array_header_t
*combined_list
;
1202 svn_log_entry_t
*empty_log_entry
;
1203 apr_pool_t
*iterpool2
= svn_pool_create(iterpool
);
1206 SVN_ERR(combine_mergeinfo_path_lists(&combined_list
, mergeinfo
,
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
));
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
,
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
,
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
;
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
,
1302 any_histories_left
= TRUE
;
1305 /* If any of the paths changed in this rev then add or send it. */
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
)
1321 /* They wanted it in forward order, so we have to buffer up
1322 a list of revs and process it later. */
1324 revs
= apr_array_make(pool
, 64, sizeof(svn_revnum_t
));
1325 APR_ARRAY_PUSH(revs
, svn_revnum_t
) = current
;
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,
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
)
1348 svn_pool_destroy(iterpool
);
1350 return SVN_NO_ERROR
;
1354 svn_repos_get_logs4(svn_repos_t
*repos
,
1355 const apr_array_header_t
*paths
,
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
,
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
))
1381 if (! SVN_IS_VALID_REVNUM(end
))
1384 /* Check that revisions are sane before ever invoking receiver. */
1386 return svn_error_createf
1387 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
1388 _("No such revision %ld"), start
);
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
)
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.
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 *))))
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
)
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
)
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
));
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
;
1463 svn_repos_get_logs3(svn_repos_t
*repos
,
1464 const apr_array_header_t
*paths
,
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
,
1476 svn_log_entry_receiver_t receiver2
;
1477 void *receiver2_baton
;
1479 svn_compat_wrap_log_receiver(&receiver2
, &receiver2_baton
,
1480 receiver
, receiver_baton
,
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
,
1492 svn_repos_get_logs2(svn_repos_t
*repos
,
1493 const apr_array_header_t
*paths
,
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
,
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
);
1512 svn_repos_get_logs(svn_repos_t
*repos
,
1513 const apr_array_header_t
*paths
,
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
,
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
);