1 /* log.c --- retrieving log messages
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
25 #define APR_WANT_STRFUNC
28 #include "svn_compat.h"
29 #include "svn_private_config.h"
31 #include "svn_pools.h"
32 #include "svn_error.h"
35 #include "svn_repos.h"
36 #include "svn_string.h"
37 #include "svn_sorts.h"
38 #include "svn_props.h"
39 #include "svn_mergeinfo.h"
41 #include "private/svn_fspath.h"
42 #include "private/svn_fs_private.h"
43 #include "private/svn_mergeinfo_private.h"
44 #include "private/svn_subr_private.h"
45 #include "private/svn_sorts_private.h"
46 #include "private/svn_string_private.h"
49 /* This is a mere convenience struct such that we don't need to pass that
50 many parameters around individually. */
51 typedef struct log_callbacks_t
53 svn_repos_path_change_receiver_t path_change_receiver;
54 void *path_change_receiver_baton;
55 svn_repos_log_entry_receiver_t revision_receiver;
56 void *revision_receiver_baton;
57 svn_repos_authz_func_t authz_read_func;
58 void *authz_read_baton;
62 svn_repos_path_change_t *
63 svn_repos_path_change_create(apr_pool_t *result_pool)
65 svn_repos_path_change_t *change = apr_pcalloc(result_pool, sizeof(*change));
67 change->path.data = "";
68 change->change_kind = svn_fs_path_change_reset;
69 change->mergeinfo_mod = svn_tristate_unknown;
70 change->copyfrom_rev = SVN_INVALID_REVNUM;
75 svn_repos_path_change_t *
76 svn_repos_path_change_dup(svn_repos_path_change_t *change,
77 apr_pool_t *result_pool)
79 svn_repos_path_change_t *new_change = apr_pmemdup(result_pool, change,
82 new_change->path.data = apr_pstrmemdup(result_pool, change->path.data,
84 if (change->copyfrom_path)
85 new_change->copyfrom_path = apr_pstrdup(result_pool,
86 change->copyfrom_path);
91 svn_repos_log_entry_t *
92 svn_repos_log_entry_create(apr_pool_t *result_pool)
94 svn_repos_log_entry_t *log_entry = apr_pcalloc(result_pool,
100 svn_repos_log_entry_t *
101 svn_repos_log_entry_dup(const svn_repos_log_entry_t *log_entry,
102 apr_pool_t *result_pool)
104 svn_repos_log_entry_t *new_entry = apr_pmemdup(result_pool, log_entry,
107 if (log_entry->revprops)
108 new_entry->revprops = svn_prop_hash_dup(log_entry->revprops, result_pool);
114 svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
116 svn_revnum_t revision,
117 svn_repos_authz_func_t authz_read_func,
118 void *authz_read_baton,
121 svn_fs_t *fs = svn_repos_fs(repos);
122 svn_fs_root_t *rev_root;
123 svn_fs_path_change_iterator_t *iterator;
124 svn_fs_path_change3_t *change;
125 svn_boolean_t found_readable = FALSE;
126 svn_boolean_t found_unreadable = FALSE;
127 apr_pool_t *iterpool;
129 /* By default, we'll grant full read access to REVISION. */
130 *access_level = svn_repos_revision_access_full;
132 /* No auth-checking function? We're done. */
133 if (! authz_read_func)
136 /* Fetch the changes associated with REVISION. */
137 SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
138 SVN_ERR(svn_fs_paths_changed3(&iterator, rev_root, pool, pool));
139 SVN_ERR(svn_fs_path_change_get(&change, iterator));
141 /* No changed paths? We're done.
143 Note that the check at "decision:" assumes that at least one
144 path has been processed. So, this actually affects functionality. */
148 /* Otherwise, we have to check the readability of each changed
149 path, or at least enough to answer the question asked. */
150 iterpool = svn_pool_create(pool);
153 svn_boolean_t readable;
155 svn_pool_clear(iterpool);
157 SVN_ERR(authz_read_func(&readable, rev_root, change->path.data,
158 authz_read_baton, iterpool));
160 found_unreadable = TRUE;
162 found_readable = TRUE;
164 /* If we have at least one of each (readable/unreadable), we
166 if (found_readable && found_unreadable)
169 switch (change->change_kind)
171 case svn_fs_path_change_add:
172 case svn_fs_path_change_replace:
174 const char *copyfrom_path;
175 svn_revnum_t copyfrom_rev;
177 SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path,
178 rev_root, change->path.data,
180 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
182 svn_fs_root_t *copyfrom_root;
183 SVN_ERR(svn_fs_revision_root(©from_root, fs,
184 copyfrom_rev, iterpool));
185 SVN_ERR(authz_read_func(&readable,
186 copyfrom_root, copyfrom_path,
187 authz_read_baton, iterpool));
189 found_unreadable = TRUE;
191 /* If we have at least one of each (readable/unreadable), we
193 if (found_readable && found_unreadable)
199 case svn_fs_path_change_delete:
200 case svn_fs_path_change_modify:
205 SVN_ERR(svn_fs_path_change_get(&change, iterator));
209 svn_pool_destroy(iterpool);
211 /* Either every changed path was unreadable... */
212 if (! found_readable)
213 *access_level = svn_repos_revision_access_none;
215 /* ... or some changed path was unreadable... */
216 else if (found_unreadable)
217 *access_level = svn_repos_revision_access_partial;
219 /* ... or every changed path was readable (the default). */
224 /* Find all significant changes under ROOT and, if not NULL, report them
225 * to the CALLBACKS->PATH_CHANGE_RECEIVER. "Significant" means that the
226 * text or properties of the node were changed, or that the node was added
229 * If optional CALLBACKS->AUTHZ_READ_FUNC is non-NULL, then use it (with
230 * CALLBACKS->AUTHZ_READ_BATON and FS) to check whether each changed-path
231 * (and copyfrom_path) is readable:
233 * - If absolutely every changed-path (and copyfrom_path) is
234 * readable, then return the full CHANGED hash, and set
235 * *ACCESS_LEVEL to svn_repos_revision_access_full.
237 * - If some paths are readable and some are not, then silently
238 * omit the unreadable paths from the CHANGED hash, and set
239 * *ACCESS_LEVEL to svn_repos_revision_access_partial.
241 * - If absolutely every changed-path (and copyfrom_path) is
242 * unreadable, then return an empty CHANGED hash, and set
243 * *ACCESS_LEVEL to svn_repos_revision_access_none. (This is
244 * to distinguish a revision which truly has no changed paths
245 * from a revision in which all paths are unreadable.)
248 detect_changed(svn_repos_revision_access_level_t *access_level,
251 const log_callbacks_t *callbacks,
252 apr_pool_t *scratch_pool)
254 svn_fs_path_change_iterator_t *iterator;
255 svn_fs_path_change3_t *change;
256 apr_pool_t *iterpool;
257 svn_boolean_t found_readable = FALSE;
258 svn_boolean_t found_unreadable = FALSE;
260 /* Retrieve the first change in the list. */
261 SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
262 SVN_ERR(svn_fs_path_change_get(&change, iterator));
266 /* No paths changed in this revision? Uh, sure, I guess the
267 revision is readable, then. */
268 *access_level = svn_repos_revision_access_full;
272 iterpool = svn_pool_create(scratch_pool);
275 /* NOTE: Much of this loop is going to look quite similar to
276 svn_repos_check_revision_access(), but we have to do more things
277 here, so we'll live with the duplication. */
278 const char *path = change->path.data;
279 svn_pool_clear(iterpool);
281 /* Skip path if unreadable. */
282 if (callbacks->authz_read_func)
284 svn_boolean_t readable;
285 SVN_ERR(callbacks->authz_read_func(&readable, root, path,
286 callbacks->authz_read_baton,
290 found_unreadable = TRUE;
291 SVN_ERR(svn_fs_path_change_get(&change, iterator));
296 /* At least one changed-path was readable. */
297 found_readable = TRUE;
299 /* Pre-1.6 revision files don't store the change path kind, so fetch
301 if (change->node_kind == svn_node_unknown)
303 svn_fs_root_t *check_root = root;
304 const char *check_path = path;
306 /* Deleted items don't exist so check earlier revision. We
307 know the parent must exist and could be a copy */
308 if (change->change_kind == svn_fs_path_change_delete)
310 svn_fs_history_t *history;
311 svn_revnum_t prev_rev;
312 const char *parent_path, *name;
314 svn_fspath__split(&parent_path, &name, path, iterpool);
316 SVN_ERR(svn_fs_node_history2(&history, root, parent_path,
317 iterpool, iterpool));
319 /* Two calls because the first call returns the original
320 revision as the deleted child means it is 'interesting' */
321 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
323 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
326 SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev,
328 SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev,
330 check_path = svn_fspath__join(parent_path, name, iterpool);
333 SVN_ERR(svn_fs_check_path(&change->node_kind, check_root, check_path,
337 if ( (change->change_kind == svn_fs_path_change_add)
338 || (change->change_kind == svn_fs_path_change_replace))
340 const char *copyfrom_path = change->copyfrom_path;
341 svn_revnum_t copyfrom_rev = change->copyfrom_rev;
343 /* the following is a potentially expensive operation since on FSFS
344 we will follow the DAG from ROOT to PATH and that requires
345 actually reading the directories along the way. */
346 if (!change->copyfrom_known)
348 SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path,
349 root, path, iterpool));
350 change->copyfrom_known = TRUE;
353 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
355 svn_boolean_t readable = TRUE;
357 if (callbacks->authz_read_func)
359 svn_fs_root_t *copyfrom_root;
361 SVN_ERR(svn_fs_revision_root(©from_root, fs,
362 copyfrom_rev, iterpool));
363 SVN_ERR(callbacks->authz_read_func(&readable,
366 callbacks->authz_read_baton,
369 found_unreadable = TRUE;
374 change->copyfrom_path = copyfrom_path;
375 change->copyfrom_rev = copyfrom_rev;
380 if (callbacks->path_change_receiver)
381 SVN_ERR(callbacks->path_change_receiver(
382 callbacks->path_change_receiver_baton,
386 /* Next changed path. */
387 SVN_ERR(svn_fs_path_change_get(&change, iterator));
390 svn_pool_destroy(iterpool);
392 if (! found_readable)
394 /* Every changed-path was unreadable. */
395 *access_level = svn_repos_revision_access_none;
397 else if (found_unreadable)
399 /* At least one changed-path was unreadable. */
400 *access_level = svn_repos_revision_access_partial;
404 /* Every changed-path was readable. */
405 *access_level = svn_repos_revision_access_full;
411 /* This is used by svn_repos_get_logs to keep track of multiple
412 * path history information while working through history.
414 * The two pools are swapped after each iteration through history because
415 * to get the next history requires the previous one.
419 svn_stringbuf_t *path;
420 svn_revnum_t history_rev;
422 svn_boolean_t first_time;
424 /* If possible, we like to keep open the history object for each path,
425 since it avoids needed to open and close it many times as we walk
426 backwards in time. To do so we need two pools, so that we can clear
427 one each time through. If we're not holding the history open for
428 this path then these three pointers will be NULL. */
429 svn_fs_history_t *hist;
434 /* Advance to the next history for the path.
436 * If INFO->HIST is not NULL we do this using that existing history object,
437 * otherwise we open a new one.
439 * If no more history is available or the history revision is less
440 * (earlier) than START, or the history is not available due
441 * to authorization, then INFO->DONE is set to TRUE.
443 * A STRICT value of FALSE will indicate to follow history across copied
446 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
447 * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
448 * we do indeed find more history for the path.
451 get_history(struct path_info *info,
453 svn_boolean_t strict,
454 svn_repos_authz_func_t authz_read_func,
455 void *authz_read_baton,
457 apr_pool_t *result_pool,
458 apr_pool_t *scratch_pool)
460 svn_fs_root_t *history_root = NULL;
461 svn_fs_history_t *hist;
467 subpool = info->newpool;
469 SVN_ERR(svn_fs_history_prev2(&info->hist, info->hist, ! strict,
470 subpool, scratch_pool));
476 subpool = svn_pool_create(result_pool);
478 /* Open the history located at the last rev we were at. */
479 SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
482 SVN_ERR(svn_fs_node_history2(&hist, history_root, info->path->data,
483 subpool, scratch_pool));
485 SVN_ERR(svn_fs_history_prev2(&hist, hist, ! strict, subpool,
488 if (info->first_time)
489 info->first_time = FALSE;
491 SVN_ERR(svn_fs_history_prev2(&hist, hist, ! strict, subpool,
497 svn_pool_destroy(subpool);
499 svn_pool_destroy(info->oldpool);
504 /* Fetch the location information for this history step. */
505 SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
508 svn_stringbuf_set(info->path, path);
510 /* If this history item predates our START revision then
511 don't fetch any more for this path. */
512 if (info->history_rev < start)
514 svn_pool_destroy(subpool);
516 svn_pool_destroy(info->oldpool);
521 /* Is the history item readable? If not, done with path. */
524 svn_boolean_t readable;
525 SVN_ERR(svn_fs_revision_root(&history_root, fs,
528 SVN_ERR(authz_read_func(&readable, history_root,
538 svn_pool_destroy(subpool);
542 apr_pool_t *temppool = info->oldpool;
543 info->oldpool = info->newpool;
544 svn_pool_clear(temppool);
545 info->newpool = temppool;
551 /* Set INFO->HIST to the next history for the path *if* there is history
552 * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
554 * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
555 * otherwise it is not touched.
557 * If we do need to get the next history revision for the path, call
558 * get_history to do it -- see it for details.
561 check_history(svn_boolean_t *changed,
562 struct path_info *info,
564 svn_revnum_t current,
565 svn_boolean_t strict,
566 svn_repos_authz_func_t authz_read_func,
567 void *authz_read_baton,
569 apr_pool_t *result_pool,
570 apr_pool_t *scratch_pool)
572 /* If we're already done with histories for this path,
573 don't try to fetch any more. */
577 /* If the last rev we got for this path is less than CURRENT,
578 then just return and don't fetch history for this path.
579 The caller will get to this rev eventually or else reach
581 if (info->history_rev < current)
584 /* If the last rev we got for this path is equal to CURRENT
585 then set *CHANGED to true and get the next history
586 rev where this path was changed. */
588 return get_history(info, fs, strict, authz_read_func,
589 authz_read_baton, start, result_pool, scratch_pool);
592 /* Return the next interesting revision in our list of HISTORIES. */
594 next_history_rev(const apr_array_header_t *histories)
596 svn_revnum_t next_rev = SVN_INVALID_REVNUM;
599 for (i = 0; i < histories->nelts; ++i)
601 struct path_info *info = APR_ARRAY_IDX(histories, i,
605 if (info->history_rev > next_rev)
606 next_rev = info->history_rev;
612 /* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
613 catalogs describing how mergeinfo values on paths (which are the
614 keys of those catalogs) were changed in REV. */
615 /* ### TODO: This would make a *great*, useful public function,
616 ### svn_repos_fs_mergeinfo_changed()! -- cmpilato */
618 fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
619 svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
622 apr_pool_t *result_pool,
623 apr_pool_t *scratch_pool)
626 apr_pool_t *iterpool, *iterator_pool;
627 svn_fs_path_change_iterator_t *iterator;
628 svn_fs_path_change3_t *change;
629 svn_boolean_t any_mergeinfo = FALSE;
630 svn_boolean_t any_copy = FALSE;
632 /* Initialize return variables. */
633 *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
634 *added_mergeinfo_catalog = svn_hash__make(result_pool);
636 /* Revision 0 has no mergeinfo and no mergeinfo changes. */
640 /* FS iterators are potentially heavy objects.
641 * Hold them in a separate pool to clean them up asap. */
642 iterator_pool = svn_pool_create(scratch_pool);
644 /* We're going to use the changed-paths information for REV to
645 narrow down our search. */
646 SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
647 SVN_ERR(svn_fs_paths_changed3(&iterator, root, iterator_pool,
649 SVN_ERR(svn_fs_path_change_get(&change, iterator));
651 /* Look for copies and (potential) mergeinfo changes.
652 We will use both flags to take shortcuts further down the road.
654 The critical information here is whether there are any copies
655 because that greatly influences the costs for log processing.
656 So, it is faster to iterate over the changes twice - in the worst
657 case b/c most times there is no m/i at all and we exit out early
658 without any overhead.
660 while (change && (!any_mergeinfo || !any_copy))
662 /* If there was a prop change and we are not positive that _no_
663 mergeinfo change happened, we must assume that it might have. */
664 if (change->mergeinfo_mod != svn_tristate_false && change->prop_mod)
665 any_mergeinfo = TRUE;
667 if ( (change->change_kind == svn_fs_path_change_add)
668 || (change->change_kind == svn_fs_path_change_replace))
671 SVN_ERR(svn_fs_path_change_get(&change, iterator));
674 /* No potential mergeinfo changes? We're done. */
677 svn_pool_destroy(iterator_pool);
681 /* There is or may be some m/i change. Look closely now. */
682 svn_pool_clear(iterator_pool);
683 SVN_ERR(svn_fs_paths_changed3(&iterator, root, iterator_pool,
686 /* Loop over changes, looking for anything that might carry an
687 svn:mergeinfo change and is one of our paths of interest, or a
688 child or [grand]parent directory thereof. */
689 iterpool = svn_pool_create(scratch_pool);
692 const char *changed_path;
693 const char *base_path = NULL;
694 svn_revnum_t base_rev = SVN_INVALID_REVNUM;
695 svn_fs_root_t *base_root = NULL;
696 svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
699 SVN_ERR(svn_fs_path_change_get(&change, iterator));
703 /* Cheap pre-checks that don't require memory allocation etc. */
705 /* No mergeinfo change? -> nothing to do here. */
706 if (change->mergeinfo_mod == svn_tristate_false)
709 /* If there was no property change on this item, ignore it. */
710 if (! change->prop_mod)
713 /* Begin actual processing */
714 changed_path = change->path.data;
715 svn_pool_clear(iterpool);
717 switch (change->change_kind)
720 /* ### TODO: Can the add, replace, and modify cases be joined
721 ### together to all use svn_repos__prev_location()? The
722 ### difference would be the fallback case (path/rev-1 for
723 ### modifies, NULL otherwise). -- cmpilato */
725 /* If the path was merely modified, see if its previous
726 location was affected by a copy which happened in this
727 revision before assuming it holds the same path it did the
728 previous revision. */
729 case svn_fs_path_change_modify:
731 svn_revnum_t appeared_rev;
733 /* If there were no copies in this revision, the path will have
734 existed in the previous rev. Otherwise, we might just got
735 copied here and need to check for that eventuality. */
738 SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
740 changed_path, iterpool));
742 /* If this path isn't the result of a copy that occurred
743 in this revision, we can find the previous version of
744 it in REV - 1 at the same path. */
745 if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
746 && (appeared_rev == rev)))
748 base_path = changed_path;
754 base_path = changed_path;
760 /* If the path was added or replaced, see if it was created via
761 copy. If so, set BASE_REV/BASE_PATH to its previous location.
762 If not, there's no previous location to examine -- leave
763 BASE_REV/BASE_PATH = -1/NULL. */
764 case svn_fs_path_change_add:
765 case svn_fs_path_change_replace:
767 if (change->copyfrom_known)
769 base_rev = change->copyfrom_rev;
770 base_path = change->copyfrom_path;
774 SVN_ERR(svn_fs_copied_from(&base_rev, &base_path,
775 root, changed_path, iterpool));
780 /* We don't care about any of the other cases. */
781 case svn_fs_path_change_delete:
782 case svn_fs_path_change_reset:
787 /* If there was a base location, fetch its mergeinfo property value. */
788 if (base_path && SVN_IS_VALID_REVNUM(base_rev))
790 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool));
791 SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path,
792 SVN_PROP_MERGEINFO, iterpool));
795 /* Now fetch the current (as of REV) mergeinfo property value. */
796 SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path,
797 SVN_PROP_MERGEINFO, iterpool));
799 /* No mergeinfo on either the new or previous location? Just
800 skip it. (If there *was* a change, it would have been in
801 inherited mergeinfo only, which should be picked up by the
802 iteration of this loop that finds the parent paths that
803 really got changed.) */
804 if (! (mergeinfo_value || prev_mergeinfo_value))
807 /* Mergeinfo on both sides but it did not change? Skip that too. */
808 if ( mergeinfo_value && prev_mergeinfo_value
809 && svn_string_compare(mergeinfo_value, prev_mergeinfo_value))
812 /* If mergeinfo was explicitly added or removed on this path, we
813 need to check to see if that was a real semantic change of
814 meaning. So, fill in the "missing" mergeinfo value with the
815 inherited mergeinfo for that path/revision. */
816 if (prev_mergeinfo_value && (! mergeinfo_value))
818 svn_mergeinfo_t tmp_mergeinfo;
820 SVN_ERR(svn_fs__get_mergeinfo_for_path(&tmp_mergeinfo,
822 svn_mergeinfo_inherited, TRUE,
823 iterpool, iterpool));
825 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
829 else if (mergeinfo_value && (! prev_mergeinfo_value)
830 && base_path && SVN_IS_VALID_REVNUM(base_rev))
832 svn_mergeinfo_t tmp_mergeinfo;
834 SVN_ERR(svn_fs__get_mergeinfo_for_path(&tmp_mergeinfo,
835 base_root, base_path,
836 svn_mergeinfo_inherited, TRUE,
837 iterpool, iterpool));
839 SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
844 /* Old and new mergeinfo probably differ in some way (we already
845 checked for textual equality further up). Store the before and
846 after mergeinfo values in our return hashes. They may still be
847 equal as manual intervention may have only changed the formatting
848 but not the relevant contents. */
850 svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
851 svn_mergeinfo_t deleted, added;
852 const char *hash_path;
855 SVN_ERR(svn_mergeinfo_parse(&mergeinfo,
856 mergeinfo_value->data, iterpool));
857 if (prev_mergeinfo_value)
858 SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
859 prev_mergeinfo_value->data, iterpool));
860 SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
861 mergeinfo, FALSE, result_pool,
864 /* Toss interesting stuff into our return catalogs. */
865 hash_path = apr_pstrdup(result_pool, changed_path);
866 svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
867 svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
871 svn_pool_destroy(iterpool);
872 svn_pool_destroy(iterator_pool);
878 /* Determine what (if any) mergeinfo for PATHS was modified in
879 revision REV, returning the differences for added mergeinfo in
880 *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO. */
882 get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
883 svn_mergeinfo_t *deleted_mergeinfo,
885 const apr_array_header_t *paths,
887 apr_pool_t *result_pool,
888 apr_pool_t *scratch_pool)
890 svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog;
891 apr_hash_index_t *hi;
893 apr_pool_t *iterpool;
897 /* Initialize return value. */
898 *added_mergeinfo = svn_hash__make(result_pool);
899 *deleted_mergeinfo = svn_hash__make(result_pool);
901 /* If we're asking about revision 0, there's no mergeinfo to be found. */
905 /* No paths? No mergeinfo. */
909 /* Fetch the mergeinfo changes for REV. */
910 err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
911 &added_mergeinfo_catalog,
913 scratch_pool, scratch_pool);
916 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
918 /* Issue #3896: If invalid mergeinfo is encountered the
919 best we can do is ignore it and act as if there were
920 no mergeinfo modifications. */
921 svn_error_clear(err);
926 return svn_error_trace(err);
930 /* In most revisions, there will be no mergeinfo change at all. */
931 if ( apr_hash_count(deleted_mergeinfo_catalog) == 0
932 && apr_hash_count(added_mergeinfo_catalog) == 0)
935 /* Create a work subpool and get a root for REV. */
936 SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
938 /* Check our PATHS for any changes to their inherited mergeinfo.
939 (We deal with changes to mergeinfo directly *on* the paths in the
941 iterpool = svn_pool_create(scratch_pool);
942 for (i = 0; i < paths->nelts; i++)
944 const char *path = APR_ARRAY_IDX(paths, i, const char *);
945 const char *prev_path;
946 svn_revnum_t appeared_rev, prev_rev;
947 svn_fs_root_t *prev_root;
948 svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
949 prev_inherited_mergeinfo, inherited_mergeinfo;
951 svn_pool_clear(iterpool);
953 /* If this path is represented in the changed-mergeinfo hashes,
954 we'll deal with it in the loop below. */
955 if (svn_hash_gets(deleted_mergeinfo_catalog, path))
958 /* Figure out what path/rev to compare against. Ignore
959 not-found errors returned by the filesystem. */
960 err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
961 fs, rev, path, iterpool);
962 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
963 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
965 svn_error_clear(err);
971 /* If this path isn't the result of a copy that occurred in this
972 revision, we can find the previous version of it in REV - 1
974 if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev)
975 && (appeared_rev == rev)))
981 /* Fetch the previous mergeinfo (including inherited stuff) for
982 this path. Ignore not-found errors returned by the
983 filesystem or invalid mergeinfo (Issue #3896).*/
984 SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
985 err = svn_fs__get_mergeinfo_for_path(&prev_mergeinfo,
986 prev_root, prev_path,
987 svn_mergeinfo_inherited, TRUE,
989 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
990 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
991 err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
993 svn_error_clear(err);
999 /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
1000 to move as a merge': A copy where the source and destination inherit
1001 mergeinfo from the same parent means the inherited mergeinfo of the
1002 source and destination will differ, but this diffrence is not
1003 indicative of a merge unless the mergeinfo on the inherited parent
1004 has actually changed.
1006 To check for this we must fetch the "raw" previous inherited
1007 mergeinfo and the "raw" mergeinfo @REV then compare these. */
1008 SVN_ERR(svn_fs__get_mergeinfo_for_path(&prev_inherited_mergeinfo,
1009 prev_root, prev_path,
1010 svn_mergeinfo_nearest_ancestor,
1011 FALSE, /* adjust_inherited_mergeinfo */
1012 iterpool, iterpool));
1014 /* Fetch the current mergeinfo (as of REV, and including
1015 inherited stuff) for this path. */
1016 SVN_ERR(svn_fs__get_mergeinfo_for_path(&mergeinfo,
1018 svn_mergeinfo_inherited, TRUE,
1019 iterpool, iterpool));
1021 /* Issue #4022 again, fetch the raw inherited mergeinfo. */
1022 SVN_ERR(svn_fs__get_mergeinfo_for_path(&inherited_mergeinfo,
1024 svn_mergeinfo_nearest_ancestor,
1025 FALSE, /* adjust_inherited_mergeinfo */
1026 iterpool, iterpool));
1028 if (!prev_mergeinfo && !mergeinfo)
1031 /* Last bit of issue #4022 checking. */
1032 if (prev_inherited_mergeinfo && inherited_mergeinfo)
1034 svn_boolean_t inherits_same_mergeinfo;
1036 SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
1037 prev_inherited_mergeinfo,
1038 inherited_mergeinfo,
1040 /* If a copy rather than an actual merge brought about an
1041 inherited mergeinfo change then we are finished. */
1042 if (inherits_same_mergeinfo)
1047 svn_boolean_t same_mergeinfo;
1048 SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
1049 prev_inherited_mergeinfo,
1056 /* Compare, constrast, and combine the results. */
1057 SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
1058 mergeinfo, FALSE, result_pool, iterpool));
1059 SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
1060 result_pool, iterpool));
1061 SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
1062 result_pool, iterpool));
1065 /* Merge all the mergeinfos which are, or are children of, one of
1066 our paths of interest into one giant delta mergeinfo. */
1067 for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
1068 hi; hi = apr_hash_next(hi))
1070 const char *changed_path = apr_hash_this_key(hi);
1071 apr_ssize_t klen = apr_hash_this_key_len(hi);
1072 svn_mergeinfo_t added = apr_hash_this_val(hi);
1073 svn_mergeinfo_t deleted;
1075 for (i = 0; i < paths->nelts; i++)
1077 const char *path = APR_ARRAY_IDX(paths, i, const char *);
1078 if (! svn_fspath__skip_ancestor(path, changed_path))
1080 svn_pool_clear(iterpool);
1081 deleted = apr_hash_get(deleted_mergeinfo_catalog, changed_path, klen);
1082 SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
1083 svn_mergeinfo_dup(deleted, result_pool),
1084 result_pool, iterpool));
1085 SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
1086 svn_mergeinfo_dup(added, result_pool),
1087 result_pool, iterpool));
1093 svn_pool_destroy(iterpool);
1094 return SVN_NO_ERROR;
1098 /* Fill LOG_ENTRY with history information in FS at REV. */
1099 static svn_error_t *
1100 fill_log_entry(svn_repos_log_entry_t *log_entry,
1103 const apr_array_header_t *revprops,
1104 const log_callbacks_t *callbacks,
1107 apr_hash_t *r_props;
1108 svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
1109 svn_boolean_t want_revprops = !revprops || revprops->nelts;
1111 /* Discover changed paths if the user requested them
1112 or if we need to check that they are readable. */
1114 && (callbacks->authz_read_func || callbacks->path_change_receiver))
1116 svn_fs_root_t *newroot;
1117 svn_repos_revision_access_level_t access_level;
1119 SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
1120 SVN_ERR(detect_changed(&access_level, newroot, fs, callbacks, pool));
1122 if (access_level == svn_repos_revision_access_none)
1124 /* All changed-paths are unreadable, so clear all fields. */
1125 get_revprops = FALSE;
1127 else if (access_level == svn_repos_revision_access_partial)
1129 /* At least one changed-path was unreadable, so censor all
1130 but author and date. (The unreadable paths are already
1131 missing from the hash.) */
1132 censor_revprops = TRUE;
1136 if (get_revprops && want_revprops)
1138 /* User is allowed to see at least some revprops. */
1139 SVN_ERR(svn_fs_revision_proplist2(&r_props, fs, rev, FALSE, pool,
1141 if (revprops == NULL)
1143 /* Requested all revprops... */
1144 if (censor_revprops)
1146 /* ... but we can only return author/date. */
1147 log_entry->revprops = svn_hash__make(pool);
1148 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1149 svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
1150 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1151 svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
1154 /* ... so return all we got. */
1155 log_entry->revprops = r_props;
1161 /* Requested only some revprops... */
1163 /* Make "svn:author" and "svn:date" available as svn_string_t
1164 for efficient comparison via svn_string_compare(). Note that
1165 we want static initialization here and must therefore emulate
1166 strlen(x) by sizeof(x)-1. */
1167 static const svn_string_t svn_prop_revision_author
1168 = SVN__STATIC_STRING(SVN_PROP_REVISION_AUTHOR);
1169 static const svn_string_t svn_prop_revision_date
1170 = SVN__STATIC_STRING(SVN_PROP_REVISION_DATE);
1172 /* often only the standard revprops got requested and delivered.
1173 In that case, we can simply pass the hash on. */
1174 if (revprops->nelts == apr_hash_count(r_props) && !censor_revprops)
1176 log_entry->revprops = r_props;
1177 for (i = 0; i < revprops->nelts; i++)
1179 const svn_string_t *name
1180 = APR_ARRAY_IDX(revprops, i, const svn_string_t *);
1181 if (!apr_hash_get(r_props, name->data, name->len))
1183 /* hash does not match list of revprops we want */
1184 log_entry->revprops = NULL;
1190 /* slow, revprop-by-revprop filtering */
1191 if (log_entry->revprops == NULL)
1192 for (i = 0; i < revprops->nelts; i++)
1194 const svn_string_t *name
1195 = APR_ARRAY_IDX(revprops, i, const svn_string_t *);
1197 = apr_hash_get(r_props, name->data, name->len);
1199 && !svn_string_compare(name, &svn_prop_revision_author)
1200 && !svn_string_compare(name, &svn_prop_revision_date))
1201 /* ... but we can only return author/date. */
1203 if (log_entry->revprops == NULL)
1204 log_entry->revprops = svn_hash__make(pool);
1205 apr_hash_set(log_entry->revprops, name->data, name->len, value);
1210 log_entry->revision = rev;
1212 return SVN_NO_ERROR;
1215 /* Baton type to be used with the interesting_merge callback. */
1216 typedef struct interesting_merge_baton_t
1218 /* What we are looking for. */
1220 svn_mergeinfo_t log_target_history_as_mergeinfo;
1222 /* Set to TRUE if we found it. */
1223 svn_boolean_t found_rev_of_interest;
1225 /* We need to invoke this user-provided callback if not NULL. */
1226 svn_repos_path_change_receiver_t inner;
1228 } interesting_merge_baton_t;
1230 /* Implements svn_repos_path_change_receiver_t.
1231 * *BATON is a interesting_merge_baton_t.
1233 * If BATON->REV a merged revision that is not already part of
1234 * BATON->LOG_TARGET_HISTORY_AS_MERGEINFO, set BATON->FOUND_REV_OF_INTEREST.
1236 static svn_error_t *
1237 interesting_merge(void *baton,
1238 svn_repos_path_change_t *change,
1239 apr_pool_t *scratch_pool)
1241 interesting_merge_baton_t *b = baton;
1242 apr_hash_index_t *hi;
1245 SVN_ERR(b->inner(b->inner_baton, change, scratch_pool));
1247 if (b->found_rev_of_interest)
1248 return SVN_NO_ERROR;
1250 /* Look at each path on the log target's mergeinfo. */
1251 for (hi = apr_hash_first(scratch_pool, b->log_target_history_as_mergeinfo);
1253 hi = apr_hash_next(hi))
1255 const char *mergeinfo_path = apr_hash_this_key(hi);
1256 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
1258 /* Check whether CHANGED_PATH at revision REV is a child of
1259 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
1260 if (svn_fspath__skip_ancestor(mergeinfo_path, change->path.data))
1264 for (i = 0; i < rangelist->nelts; i++)
1266 svn_merge_range_t *range
1267 = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
1268 if (b->rev > range->start && b->rev <= range->end)
1269 return SVN_NO_ERROR;
1274 b->found_rev_of_interest = TRUE;
1276 return SVN_NO_ERROR;
1279 /* Send a log message for REV to the CALLBACKS.
1281 FS is used with REV to fetch the interesting history information,
1282 such as changed paths, revprops, etc.
1284 The detect_changed function is used if either CALLBACKS->AUTHZ_READ_FUNC
1285 is not NULL, or if CALLBACKS->PATH_CHANGE_RECEIVER is not NULL.
1288 If DESCENDING_ORDER is true, send child messages in descending order.
1290 If REVPROPS is NULL, retrieve all revision properties; else, retrieve
1291 only the revision properties named by the (const char *) array elements
1292 (i.e. retrieve none if the array is empty).
1294 LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
1295 NESTED_MERGES are as per the arguments of the same name to DO_LOGS.
1296 If HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
1297 already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
1298 the log message for REV. If SUBTRACTIVE_MERGE is true, then REV was
1301 If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES. Otherwise
1302 if NESTED_MERGES is not NULL and REV is contained in it, then don't send
1303 the log for REV, otherwise send it normally and add REV to
1305 static svn_error_t *
1306 send_log(svn_revnum_t rev,
1308 svn_mergeinfo_t log_target_history_as_mergeinfo,
1309 svn_bit_array__t *nested_merges,
1310 svn_boolean_t subtractive_merge,
1311 svn_boolean_t handling_merged_revision,
1312 const apr_array_header_t *revprops,
1313 svn_boolean_t has_children,
1314 const log_callbacks_t *callbacks,
1317 svn_repos_log_entry_t log_entry = { 0 };
1318 log_callbacks_t my_callbacks = *callbacks;
1320 interesting_merge_baton_t baton;
1322 /* Is REV a merged revision that is already part of
1323 LOG_TARGET_HISTORY_AS_MERGEINFO? If so then there is no
1324 need to send it, since it already was (or will be) sent.
1326 Use our callback to snoop through the changes. */
1327 if (handling_merged_revision
1328 && log_target_history_as_mergeinfo
1329 && apr_hash_count(log_target_history_as_mergeinfo))
1331 baton.found_rev_of_interest = FALSE;
1333 baton.log_target_history_as_mergeinfo = log_target_history_as_mergeinfo;
1334 baton.inner = callbacks->path_change_receiver;
1335 baton.inner_baton = callbacks->path_change_receiver_baton;
1337 my_callbacks.path_change_receiver = interesting_merge;
1338 my_callbacks.path_change_receiver_baton = &baton;
1339 callbacks = &my_callbacks;
1343 baton.found_rev_of_interest = TRUE;
1346 SVN_ERR(fill_log_entry(&log_entry, rev, fs, revprops, callbacks, pool));
1347 log_entry.has_children = has_children;
1348 log_entry.subtractive_merge = subtractive_merge;
1350 /* Send the entry to the receiver, unless it is a redundant merged
1352 if (baton.found_rev_of_interest)
1354 apr_pool_t *scratch_pool;
1356 /* Is REV a merged revision we've already sent? */
1357 if (nested_merges && handling_merged_revision)
1359 if (svn_bit_array__get(nested_merges, rev))
1361 /* We already sent REV. */
1362 return SVN_NO_ERROR;
1366 /* NESTED_REVS needs to last across all the send_log, do_logs,
1367 handle_merged_revisions() recursions, so use the pool it
1368 was created in at the top of the recursion. */
1369 svn_bit_array__set(nested_merges, rev, TRUE);
1373 /* Pass a scratch pool to ensure no temporary state stored
1374 by the receiver callback persists. */
1375 scratch_pool = svn_pool_create(pool);
1376 SVN_ERR(callbacks->revision_receiver(callbacks->revision_receiver_baton,
1377 &log_entry, scratch_pool));
1378 svn_pool_destroy(scratch_pool);
1381 return SVN_NO_ERROR;
1384 /* This controls how many history objects we keep open. For any targets
1385 over this number we have to open and close their histories as needed,
1386 which is CPU intensive, but keeps us from using an unbounded amount of
1388 #define MAX_OPEN_HISTORIES 32
1390 /* Get the histories for PATHS, and store them in *HISTORIES.
1392 If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1393 repository locations as fatal -- just ignore them. */
1394 static svn_error_t *
1395 get_path_histories(apr_array_header_t **histories,
1397 const apr_array_header_t *paths,
1398 svn_revnum_t hist_start,
1399 svn_revnum_t hist_end,
1400 svn_boolean_t strict_node_history,
1401 svn_boolean_t ignore_missing_locations,
1402 svn_repos_authz_func_t authz_read_func,
1403 void *authz_read_baton,
1406 svn_fs_root_t *root;
1407 apr_pool_t *iterpool;
1411 /* Create a history object for each path so we can walk through
1412 them all at the same time until we have all changes or LIMIT
1415 There is some pool fun going on due to the fact that we have
1416 to hold on to the old pool with the history before we can
1417 get the next history.
1419 *histories = apr_array_make(pool, paths->nelts,
1420 sizeof(struct path_info *));
1422 SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
1424 iterpool = svn_pool_create(pool);
1425 for (i = 0; i < paths->nelts; i++)
1427 const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
1428 struct path_info *info = apr_palloc(pool,
1429 sizeof(struct path_info));
1430 svn_pool_clear(iterpool);
1432 if (authz_read_func)
1434 svn_boolean_t readable;
1435 SVN_ERR(authz_read_func(&readable, root, this_path,
1436 authz_read_baton, iterpool));
1438 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
1441 info->path = svn_stringbuf_create(this_path, pool);
1443 info->history_rev = hist_end;
1444 info->first_time = TRUE;
1446 if (i < MAX_OPEN_HISTORIES)
1448 err = svn_fs_node_history2(&info->hist, root, this_path, pool,
1451 && ignore_missing_locations
1452 && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1453 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1454 err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1456 svn_error_clear(err);
1460 info->newpool = svn_pool_create(pool);
1461 info->oldpool = svn_pool_create(pool);
1466 info->oldpool = NULL;
1467 info->newpool = NULL;
1470 err = get_history(info, fs,
1471 strict_node_history,
1472 authz_read_func, authz_read_baton,
1473 hist_start, pool, iterpool);
1475 && ignore_missing_locations
1476 && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1477 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1478 err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1480 svn_error_clear(err);
1484 APR_ARRAY_PUSH(*histories, struct path_info *) = info;
1486 svn_pool_destroy(iterpool);
1488 return SVN_NO_ERROR;
1491 /* Remove and return the first item from ARR. */
1493 array_pop_front(apr_array_header_t *arr)
1495 void *item = arr->elts;
1497 if (apr_is_empty_array(arr))
1500 arr->elts += arr->elt_size;
1506 /* A struct which represents a single revision range, and the paths which
1507 have mergeinfo in that range. */
1508 struct path_list_range
1510 apr_array_header_t *paths;
1511 svn_merge_range_t range;
1513 /* Is RANGE the result of a reverse merge? */
1514 svn_boolean_t reverse_merge;
1517 /* A struct which represents "inverse mergeinfo", that is, instead of having
1518 a path->revision_range_list mapping, which is the way mergeinfo is commonly
1519 represented, this struct enables a revision_range_list,path tuple, where
1520 the paths can be accessed by revision. */
1521 struct rangelist_path
1523 svn_rangelist_t *rangelist;
1527 /* Comparator function for combine_mergeinfo_path_lists(). Sorts
1528 rangelist_path structs in increasing order based upon starting revision,
1529 then ending revision of the first element in the rangelist.
1531 This does not sort rangelists based upon subsequent elements, only the
1532 first range. We'll sort any subsequent ranges in the correct order
1533 when they get bumped up to the front by removal of earlier ones, so we
1534 don't really have to sort them here. See combine_mergeinfo_path_lists()
1537 compare_rangelist_paths(const void *a, const void *b)
1539 struct rangelist_path *rpa = *((struct rangelist_path *const *) a);
1540 struct rangelist_path *rpb = *((struct rangelist_path *const *) b);
1541 svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
1542 svn_merge_range_t *);
1543 svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
1544 svn_merge_range_t *);
1546 if (mra->start < mrb->start)
1548 if (mra->start > mrb->start)
1550 if (mra->end < mrb->end)
1552 if (mra->end > mrb->end)
1558 /* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
1559 'struct path_list_range's. This list represents the rangelists in
1560 MERGEINFO and each path which has mergeinfo in that range.
1561 If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed
1562 as the result of a reverse merge. */
1563 static svn_error_t *
1564 combine_mergeinfo_path_lists(apr_array_header_t **combined_list,
1565 svn_mergeinfo_t mergeinfo,
1566 svn_boolean_t reverse_merge,
1569 apr_hash_index_t *hi;
1570 apr_array_header_t *rangelist_paths;
1571 apr_pool_t *subpool = svn_pool_create(pool);
1573 /* Create a list of (revision range, path) tuples from MERGEINFO. */
1574 rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
1575 sizeof(struct rangelist_path *));
1576 for (hi = apr_hash_first(subpool, mergeinfo); hi;
1577 hi = apr_hash_next(hi))
1580 struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
1582 rp->path = apr_hash_this_key(hi);
1583 rp->rangelist = apr_hash_this_val(hi);
1584 APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
1586 /* We need to make local copies of the rangelist, since we will be
1587 modifying it, below. */
1588 rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
1590 /* Make all of the rangelists inclusive, both start and end. */
1591 for (i = 0; i < rp->rangelist->nelts; i++)
1592 APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
1595 /* Loop over the (revision range, path) tuples, chopping them into
1596 (revision range, paths) tuples, and appending those to the output
1598 if (! *combined_list)
1599 *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
1601 while (rangelist_paths->nelts > 1)
1603 svn_revnum_t youngest, next_youngest, tail, youngest_end;
1604 struct path_list_range *plr;
1605 struct rangelist_path *rp;
1609 /* First, sort the list such that the start revision of the first
1610 revision arrays are sorted. */
1611 svn_sort__array(rangelist_paths, compare_rangelist_paths);
1613 /* Next, find the number of revision ranges which start with the same
1615 rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1617 APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
1618 next_youngest = youngest;
1619 for (num_revs = 1; next_youngest == youngest; num_revs++)
1621 if (num_revs == rangelist_paths->nelts)
1626 rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
1627 struct rangelist_path *);
1628 next_youngest = APR_ARRAY_IDX(rp->rangelist, 0,
1629 struct svn_merge_range_t *)->start;
1633 /* The start of the new range will be YOUNGEST, and we now find the end
1634 of the new range, which should be either one less than the next
1635 earliest start of a rangelist, or the end of the first rangelist. */
1637 APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
1638 struct rangelist_path *)->rangelist,
1639 0, svn_merge_range_t *)->end;
1640 if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
1641 tail = youngest_end;
1643 tail = next_youngest - 1;
1645 /* Insert the (earliest, tail) tuple into the output list, along with
1646 a list of paths which match it. */
1647 plr = apr_palloc(pool, sizeof(*plr));
1648 plr->reverse_merge = reverse_merge;
1649 plr->range.start = youngest;
1650 plr->range.end = tail;
1651 plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
1652 for (i = 0; i < num_revs; i++)
1653 APR_ARRAY_PUSH(plr->paths, const char *) =
1654 APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
1655 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1657 /* Now, check to see which (rangelist path) combinations we can remove,
1659 for (i = 0; i < num_revs; i++)
1661 svn_merge_range_t *range;
1662 rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
1663 range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
1665 /* Set the start of the range to beyond the end of the range we
1666 just built. If the range is now "inverted", we can get pop it
1668 range->start = tail + 1;
1669 if (range->start > range->end)
1671 if (rp->rangelist->nelts == 1)
1673 /* The range is the only on its list, so we should remove
1674 the entire rangelist_path, adjusting our loop control
1675 variables appropriately. */
1676 array_pop_front(rangelist_paths);
1682 /* We have more than one range on the list, so just remove
1684 array_pop_front(rp->rangelist);
1690 /* Finally, add the last remaining (revision range, path) to the output
1692 if (rangelist_paths->nelts > 0)
1694 struct rangelist_path *first_rp =
1695 APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1696 while (first_rp->rangelist->nelts > 0)
1698 struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
1700 plr->reverse_merge = reverse_merge;
1701 plr->paths = apr_array_make(pool, 1, sizeof(const char *));
1702 APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
1703 plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0,
1704 svn_merge_range_t *);
1705 array_pop_front(first_rp->rangelist);
1706 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1710 svn_pool_destroy(subpool);
1712 return SVN_NO_ERROR;
1716 /* Pity that C is so ... linear. */
1717 static svn_error_t *
1718 do_logs(svn_fs_t *fs,
1719 const apr_array_header_t *paths,
1720 svn_mergeinfo_t log_target_history_as_mergeinfo,
1721 svn_mergeinfo_t processed,
1722 svn_bit_array__t *nested_merges,
1723 svn_revnum_t hist_start,
1724 svn_revnum_t hist_end,
1726 svn_boolean_t strict_node_history,
1727 svn_boolean_t include_merged_revisions,
1728 svn_boolean_t handling_merged_revisions,
1729 svn_boolean_t subtractive_merge,
1730 svn_boolean_t ignore_missing_locations,
1731 const apr_array_header_t *revprops,
1732 svn_boolean_t descending_order,
1733 log_callbacks_t *callbacks,
1736 /* Comparator function for handle_merged_revisions(). Sorts path_list_range
1737 structs in increasing order based on the struct's RANGE.START revision,
1738 then RANGE.END revision. */
1740 compare_path_list_range(const void *a, const void *b)
1742 struct path_list_range *plr_a = *((struct path_list_range *const *) a);
1743 struct path_list_range *plr_b = *((struct path_list_range *const *) b);
1745 if (plr_a->range.start < plr_b->range.start)
1747 if (plr_a->range.start > plr_b->range.start)
1749 if (plr_a->range.end < plr_b->range.end)
1751 if (plr_a->range.end > plr_b->range.end)
1757 /* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS
1758 (as collected by examining paths of interest to a log operation), and
1759 determine which revisions to report as having been merged or reverse-merged
1760 via the commit resulting in REV.
1762 Silently ignore some failures to find the revisions mentioned in the
1763 added/deleted mergeinfos, as might happen if there is invalid mergeinfo.
1765 Other parameters are as described by do_logs(), around which this
1766 is a recursion wrapper. */
1767 static svn_error_t *
1768 handle_merged_revisions(svn_revnum_t rev,
1770 svn_mergeinfo_t log_target_history_as_mergeinfo,
1771 svn_bit_array__t *nested_merges,
1772 svn_mergeinfo_t processed,
1773 svn_mergeinfo_t added_mergeinfo,
1774 svn_mergeinfo_t deleted_mergeinfo,
1775 svn_boolean_t strict_node_history,
1776 const apr_array_header_t *revprops,
1777 log_callbacks_t *callbacks,
1780 apr_array_header_t *combined_list = NULL;
1781 svn_repos_log_entry_t empty_log_entry = { 0 };
1782 apr_pool_t *iterpool;
1785 if (apr_hash_count(added_mergeinfo) == 0
1786 && apr_hash_count(deleted_mergeinfo) == 0)
1787 return SVN_NO_ERROR;
1789 if (apr_hash_count(added_mergeinfo))
1790 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo,
1793 if (apr_hash_count(deleted_mergeinfo))
1794 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo,
1797 SVN_ERR_ASSERT(combined_list != NULL);
1798 svn_sort__array(combined_list, compare_path_list_range);
1800 /* Because the combined_lists are ordered youngest to oldest,
1801 iterate over them in reverse. */
1802 iterpool = svn_pool_create(pool);
1803 for (i = combined_list->nelts - 1; i >= 0; i--)
1805 struct path_list_range *pl_range
1806 = APR_ARRAY_IDX(combined_list, i, struct path_list_range *);
1808 svn_pool_clear(iterpool);
1809 SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
1810 processed, nested_merges,
1811 pl_range->range.start, pl_range->range.end, 0,
1812 strict_node_history,
1813 TRUE, pl_range->reverse_merge, TRUE, TRUE,
1814 revprops, TRUE, callbacks, iterpool));
1816 svn_pool_destroy(iterpool);
1818 /* Send the empty revision. */
1819 empty_log_entry.revision = SVN_INVALID_REVNUM;
1820 return (callbacks->revision_receiver)(callbacks->revision_receiver_baton,
1821 &empty_log_entry, pool);
1824 /* This is used by do_logs to differentiate between forward and
1826 struct added_deleted_mergeinfo
1828 svn_mergeinfo_t added_mergeinfo;
1829 svn_mergeinfo_t deleted_mergeinfo;
1832 /* Reduce the search range PATHS, HIST_START, HIST_END by removing
1833 parts already covered by PROCESSED. If reduction is possible
1834 elements may be removed from PATHS and *START_REDUCED and
1835 *END_REDUCED may be set to a narrower range. */
1836 static svn_error_t *
1837 reduce_search(apr_array_header_t *paths,
1838 svn_revnum_t *hist_start,
1839 svn_revnum_t *hist_end,
1840 svn_mergeinfo_t processed,
1841 apr_pool_t *scratch_pool)
1843 /* We add 1 to end to compensate for store_search */
1844 svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end;
1845 svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1;
1848 for (i = 0; i < paths->nelts; ++i)
1850 const char *path = APR_ARRAY_IDX(paths, i, const char *);
1851 svn_rangelist_t *ranges = svn_hash_gets(processed, path);
1857 /* ranges is ordered, could we use some sort of binary search
1858 rather than iterating? */
1859 for (j = 0; j < ranges->nelts; ++j)
1861 svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j,
1862 svn_merge_range_t *);
1863 if (range->start <= start && range->end >= end)
1865 for (j = i; j < paths->nelts - 1; ++j)
1866 APR_ARRAY_IDX(paths, j, const char *)
1867 = APR_ARRAY_IDX(paths, j + 1, const char *);
1874 /* If there is only one path then we also check for a
1875 partial overlap rather than the full overlap above, and
1876 reduce the [hist_start, hist_end] range rather than
1877 dropping the path. */
1878 if (paths->nelts == 1)
1880 if (range->start <= start && range->end > start)
1882 if (start == *hist_start)
1883 *hist_start = range->end - 1;
1885 *hist_end = range->end - 1;
1888 if (range->start < end && range->end >= end)
1890 if (start == *hist_start)
1891 *hist_end = range->start;
1893 *hist_start = range->start;
1900 return SVN_NO_ERROR;
1903 /* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */
1904 static svn_error_t *
1905 store_search(svn_mergeinfo_t processed,
1906 const apr_array_header_t *paths,
1907 svn_revnum_t hist_start,
1908 svn_revnum_t hist_end,
1909 apr_pool_t *scratch_pool)
1911 /* We add 1 to end so that we can use the mergeinfo API to handle
1912 singe revisions where HIST_START is equal to HIST_END. */
1913 svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
1914 svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
1915 svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
1916 apr_pool_t *processed_pool = apr_hash_pool_get(processed);
1919 for (i = 0; i < paths->nelts; ++i)
1921 const char *path = APR_ARRAY_IDX(paths, i, const char *);
1922 svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
1923 sizeof(svn_merge_range_t*));
1924 svn_merge_range_t *range = apr_palloc(processed_pool, sizeof(*range));
1926 range->start = start;
1928 range->inheritable = TRUE;
1929 APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
1930 svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
1932 SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
1933 apr_hash_pool_get(processed), scratch_pool));
1935 return SVN_NO_ERROR;
1938 /* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke the
1939 CALLBACKS on them. If DESCENDING_ORDER is TRUE, send the logs back as
1940 we find them, else buffer the logs and send them back in youngest->oldest
1943 If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1944 repository locations as fatal -- just ignore them.
1946 If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo
1947 representing the history of PATHS between HIST_START and HIST_END.
1949 If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
1950 merged revisions, see INCLUDE_MERGED_REVISIONS argument to
1951 svn_repos_get_logs5(). If SUBTRACTIVE_MERGE is true, then this is a
1952 recursive call for reverse merged revisions.
1954 If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
1955 mapped to svn_revnum_t *) for logs that were previously sent. On the first
1956 call to do_logs it should always be NULL. If INCLUDE_MERGED_REVISIONS is
1957 TRUE, then NESTED_MERGES will be created on the first call to do_logs,
1958 allocated in POOL. It is then shared across
1959 do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
1960 argument of the same name in send_logs().
1962 PROCESSED is a mergeinfo hash that represents the paths and
1963 revisions that have already been searched. Allocated like
1964 NESTED_MERGES above.
1966 All other parameters are the same as svn_repos_get_logs5().
1968 static svn_error_t *
1969 do_logs(svn_fs_t *fs,
1970 const apr_array_header_t *paths,
1971 svn_mergeinfo_t log_target_history_as_mergeinfo,
1972 svn_mergeinfo_t processed,
1973 svn_bit_array__t *nested_merges,
1974 svn_revnum_t hist_start,
1975 svn_revnum_t hist_end,
1977 svn_boolean_t strict_node_history,
1978 svn_boolean_t include_merged_revisions,
1979 svn_boolean_t subtractive_merge,
1980 svn_boolean_t handling_merged_revisions,
1981 svn_boolean_t ignore_missing_locations,
1982 const apr_array_header_t *revprops,
1983 svn_boolean_t descending_order,
1984 log_callbacks_t *callbacks,
1987 apr_pool_t *iterpool, *iterpool2;
1988 apr_pool_t *subpool = NULL;
1989 apr_array_header_t *revs = NULL;
1990 apr_hash_t *rev_mergeinfo = NULL;
1991 svn_revnum_t current;
1992 apr_array_header_t *histories;
1993 svn_boolean_t any_histories_left = TRUE;
1999 /* Casting away const. This only happens on recursive calls when
2000 it is known to be safe because we allocated paths. */
2001 SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end,
2006 return SVN_NO_ERROR;
2009 SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool));
2011 /* We have a list of paths and a revision range. But we don't care
2012 about all the revisions in the range -- only the ones in which
2013 one of our paths was changed. So let's go figure out which
2014 revisions contain real changes to at least one of our paths. */
2015 SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
2016 strict_node_history, ignore_missing_locations,
2017 callbacks->authz_read_func,
2018 callbacks->authz_read_baton, pool));
2020 /* Loop through all the revisions in the range and add any
2021 where a path was changed to the array, or if they wanted
2022 history in reverse order just send it to them right away. */
2023 iterpool = svn_pool_create(pool);
2024 iterpool2 = svn_pool_create(pool);
2025 for (current = hist_end;
2027 current = next_history_rev(histories))
2029 svn_boolean_t changed = FALSE;
2030 any_histories_left = FALSE;
2031 svn_pool_clear(iterpool);
2033 for (i = 0; i < histories->nelts; i++)
2035 struct path_info *info = APR_ARRAY_IDX(histories, i,
2036 struct path_info *);
2038 svn_pool_clear(iterpool2);
2040 /* Check history for this path in current rev. */
2041 SVN_ERR(check_history(&changed, info, fs, current,
2042 strict_node_history,
2043 callbacks->authz_read_func,
2044 callbacks->authz_read_baton,
2045 hist_start, pool, iterpool2));
2047 any_histories_left = TRUE;
2050 svn_pool_clear(iterpool2);
2052 /* If any of the paths changed in this rev then add or send it. */
2055 svn_mergeinfo_t added_mergeinfo = NULL;
2056 svn_mergeinfo_t deleted_mergeinfo = NULL;
2057 svn_boolean_t has_children = FALSE;
2059 /* If we're including merged revisions, we need to calculate
2060 the mergeinfo deltas committed in this revision to our
2062 if (include_merged_revisions)
2064 apr_array_header_t *cur_paths =
2065 apr_array_make(iterpool, paths->nelts, sizeof(const char *));
2067 /* Get the current paths of our history objects so we can
2069 /* ### TODO: Should this be ignoring depleted history items? */
2070 for (i = 0; i < histories->nelts; i++)
2072 struct path_info *info = APR_ARRAY_IDX(histories, i,
2073 struct path_info *);
2074 APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
2076 SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
2080 iterpool, iterpool));
2081 has_children = (apr_hash_count(added_mergeinfo) > 0
2082 || apr_hash_count(deleted_mergeinfo) > 0);
2085 /* If our caller wants logs in descending order, we can send
2086 'em now (because that's the order we're crawling history
2088 if (descending_order)
2090 SVN_ERR(send_log(current, fs,
2091 log_target_history_as_mergeinfo, nested_merges,
2092 subtractive_merge, handling_merged_revisions,
2093 revprops, has_children, callbacks, iterpool));
2095 if (has_children) /* Implies include_merged_revisions == TRUE */
2099 /* We're at the start of the recursion stack, create a
2100 single hash to be shared across all of the merged
2101 recursions so we can track and squelch duplicates. */
2102 subpool = svn_pool_create(pool);
2103 nested_merges = svn_bit_array__create(hist_end, subpool);
2104 processed = svn_hash__make(subpool);
2107 SVN_ERR(handle_merged_revisions(
2109 log_target_history_as_mergeinfo, nested_merges,
2111 added_mergeinfo, deleted_mergeinfo,
2112 strict_node_history,
2117 if (limit && ++send_count >= limit)
2120 /* Otherwise, the caller wanted logs in ascending order, so
2121 we have to buffer up a list of revs and (if doing
2122 mergeinfo) a hash of related mergeinfo deltas, and
2123 process them later. */
2127 revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
2128 APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
2130 if (added_mergeinfo || deleted_mergeinfo)
2132 svn_revnum_t *cur_rev =
2133 apr_pmemdup(pool, ¤t, sizeof(*cur_rev));
2134 struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2135 apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
2137 /* If we have added or deleted mergeinfo, both are non-null */
2138 SVN_ERR_ASSERT(added_mergeinfo && deleted_mergeinfo);
2139 add_and_del_mergeinfo->added_mergeinfo =
2140 svn_mergeinfo_dup(added_mergeinfo, pool);
2141 add_and_del_mergeinfo->deleted_mergeinfo =
2142 svn_mergeinfo_dup(deleted_mergeinfo, pool);
2144 if (! rev_mergeinfo)
2145 rev_mergeinfo = svn_hash__make(pool);
2146 apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
2147 add_and_del_mergeinfo);
2152 svn_pool_destroy(iterpool2);
2153 svn_pool_destroy(iterpool);
2157 nested_merges = NULL;
2158 svn_pool_destroy(subpool);
2163 /* Work loop for processing the revisions we found since they wanted
2164 history in forward order. */
2165 iterpool = svn_pool_create(pool);
2166 for (i = 0; i < revs->nelts; ++i)
2168 svn_mergeinfo_t added_mergeinfo;
2169 svn_mergeinfo_t deleted_mergeinfo;
2170 svn_boolean_t has_children = FALSE;
2172 svn_pool_clear(iterpool);
2173 current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t);
2175 /* If we've got a hash of revision mergeinfo (which can only
2176 happen if INCLUDE_MERGED_REVISIONS was set), we check to
2177 see if this revision is one which merged in other
2178 revisions we need to handle recursively. */
2181 struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2182 apr_hash_get(rev_mergeinfo, ¤t, sizeof(current));
2183 added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
2184 deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
2185 has_children = (apr_hash_count(added_mergeinfo) > 0
2186 || apr_hash_count(deleted_mergeinfo) > 0);
2189 SVN_ERR(send_log(current, fs,
2190 log_target_history_as_mergeinfo, nested_merges,
2191 subtractive_merge, handling_merged_revisions,
2192 revprops, has_children, callbacks, iterpool));
2197 subpool = svn_pool_create(pool);
2198 nested_merges = svn_bit_array__create(current, subpool);
2201 SVN_ERR(handle_merged_revisions(current, fs,
2202 log_target_history_as_mergeinfo,
2207 strict_node_history,
2208 revprops, callbacks,
2211 if (limit && i + 1 >= limit)
2214 svn_pool_destroy(iterpool);
2217 return SVN_NO_ERROR;
2220 struct location_segment_baton
2222 apr_array_header_t *history_segments;
2226 /* svn_location_segment_receiver_t implementation for svn_repos_get_logs5. */
2227 static svn_error_t *
2228 location_segment_receiver(svn_location_segment_t *segment,
2232 struct location_segment_baton *b = baton;
2234 APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) =
2235 svn_location_segment_dup(segment, b->pool);
2237 return SVN_NO_ERROR;
2241 /* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined
2242 history of each path in PATHS between START_REV and END_REV in REPOS's
2243 filesystem. START_REV and END_REV must be valid revisions. RESULT_POOL
2244 is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
2245 other (temporary) allocations. Other parameters are the same as
2246 svn_repos_get_logs5(). */
2247 static svn_error_t *
2248 get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
2250 const apr_array_header_t *paths,
2251 svn_revnum_t start_rev,
2252 svn_revnum_t end_rev,
2253 svn_repos_authz_func_t authz_read_func,
2254 void *authz_read_baton,
2255 apr_pool_t *result_pool,
2256 apr_pool_t *scratch_pool)
2259 svn_mergeinfo_t path_history_mergeinfo;
2260 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2262 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev));
2263 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev));
2265 /* Ensure START_REV is the youngest revision, as required by
2266 svn_repos_node_location_segments, for which this is an iterative
2268 if (start_rev < end_rev)
2270 svn_revnum_t tmp_rev = start_rev;
2271 start_rev = end_rev;
2275 *paths_history_mergeinfo = svn_hash__make(result_pool);
2277 for (i = 0; i < paths->nelts; i++)
2279 const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
2280 struct location_segment_baton loc_seg_baton;
2282 svn_pool_clear(iterpool);
2283 loc_seg_baton.pool = scratch_pool;
2284 loc_seg_baton.history_segments =
2285 apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *));
2287 SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev,
2289 location_segment_receiver,
2295 SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
2296 &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
2297 SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
2298 svn_mergeinfo_dup(path_history_mergeinfo,
2300 result_pool, iterpool));
2302 svn_pool_destroy(iterpool);
2303 return SVN_NO_ERROR;
2307 svn_repos_get_logs5(svn_repos_t *repos,
2308 const apr_array_header_t *paths,
2312 svn_boolean_t strict_node_history,
2313 svn_boolean_t include_merged_revisions,
2314 const apr_array_header_t *revprops,
2315 svn_repos_authz_func_t authz_read_func,
2316 void *authz_read_baton,
2317 svn_repos_path_change_receiver_t path_change_receiver,
2318 void *path_change_receiver_baton,
2319 svn_repos_log_entry_receiver_t revision_receiver,
2320 void *revision_receiver_baton,
2321 apr_pool_t *scratch_pool)
2323 svn_revnum_t head = SVN_INVALID_REVNUM;
2324 svn_fs_t *fs = repos->fs;
2325 svn_boolean_t descending_order;
2326 svn_mergeinfo_t paths_history_mergeinfo = NULL;
2327 log_callbacks_t callbacks;
2329 callbacks.path_change_receiver = path_change_receiver;
2330 callbacks.path_change_receiver_baton = path_change_receiver_baton;
2331 callbacks.revision_receiver = revision_receiver;
2332 callbacks.revision_receiver_baton = revision_receiver_baton;
2333 callbacks.authz_read_func = authz_read_func;
2334 callbacks.authz_read_baton = authz_read_baton;
2339 apr_array_header_t *new_revprops
2340 = apr_array_make(scratch_pool, revprops->nelts,
2341 sizeof(svn_string_t *));
2343 for (i = 0; i < revprops->nelts; ++i)
2344 APR_ARRAY_PUSH(new_revprops, svn_string_t *)
2345 = svn_string_create(APR_ARRAY_IDX(revprops, i, const char *),
2348 revprops = new_revprops;
2351 /* Make sure we catch up on the latest revprop changes. This is the only
2352 * time we will refresh the revprop data in this query. */
2353 SVN_ERR(svn_fs_refresh_revision_props(fs, scratch_pool));
2355 /* Setup log range. */
2356 SVN_ERR(svn_fs_youngest_rev(&head, fs, scratch_pool));
2358 if (! SVN_IS_VALID_REVNUM(start))
2361 if (! SVN_IS_VALID_REVNUM(end))
2364 /* Check that revisions are sane before ever invoking receiver. */
2366 return svn_error_createf
2367 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2368 _("No such revision %ld"), start);
2370 return svn_error_createf
2371 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2372 _("No such revision %ld"), end);
2374 /* Ensure a youngest-to-oldest revision crawl ordering using our
2375 (possibly sanitized) range values. */
2376 descending_order = start >= end;
2377 if (descending_order)
2379 svn_revnum_t tmp_rev = start;
2385 paths = apr_array_make(scratch_pool, 0, sizeof(const char *));
2387 /* If we're not including merged revisions, and we were given no
2388 paths or a single empty (or "/") path, then we can bypass a bunch
2389 of complexity because we already know in which revisions the root
2390 directory was changed -- all of them. */
2391 if ((! include_merged_revisions)
2392 && ((! paths->nelts)
2393 || ((paths->nelts == 1)
2394 && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))
2395 || (strcmp(APR_ARRAY_IDX(paths, 0, const char *),
2398 apr_uint64_t send_count = 0;
2400 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2402 /* If we are provided an authz callback function, use it to
2403 verify that the user has read access to the root path in the
2404 first of our revisions.
2406 ### FIXME: Strictly speaking, we should be checking this
2407 ### access in every revision along the line. But currently,
2408 ### there are no known authz implementations which concern
2409 ### themselves with per-revision access. */
2410 if (authz_read_func)
2412 svn_boolean_t readable;
2413 svn_fs_root_t *rev_root;
2415 SVN_ERR(svn_fs_revision_root(&rev_root, fs,
2416 descending_order ? end : start,
2418 SVN_ERR(authz_read_func(&readable, rev_root, "",
2419 authz_read_baton, scratch_pool));
2421 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
2424 send_count = end - start + 1;
2425 if (limit > 0 && send_count > limit)
2427 for (i = 0; i < send_count; ++i)
2431 svn_pool_clear(iterpool);
2433 if (descending_order)
2437 SVN_ERR(send_log(rev, fs, NULL, NULL,
2438 FALSE, FALSE, revprops, FALSE,
2439 &callbacks, iterpool));
2441 svn_pool_destroy(iterpool);
2443 return SVN_NO_ERROR;
2446 /* If we are including merged revisions, then create mergeinfo that
2447 represents all of PATHS' history between START and END. We will use
2448 this later to squelch duplicate log revisions that might exist in
2449 both natural history and merged-in history. See
2450 http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
2451 if (include_merged_revisions)
2453 apr_pool_t *subpool = svn_pool_create(scratch_pool);
2455 SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
2456 repos, paths, start, end,
2459 scratch_pool, subpool));
2460 svn_pool_destroy(subpool);
2463 return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL,
2464 start, end, limit, strict_node_history,
2465 include_merged_revisions, FALSE, FALSE, FALSE,
2466 revprops, descending_order, &callbacks, scratch_pool);