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_mergeinfo_private.h"
43 #include "private/svn_subr_private.h"
48 svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
50 svn_revnum_t revision,
51 svn_repos_authz_func_t authz_read_func,
52 void *authz_read_baton,
55 svn_fs_t *fs = svn_repos_fs(repos);
56 svn_fs_root_t *rev_root;
59 svn_boolean_t found_readable = FALSE;
60 svn_boolean_t found_unreadable = FALSE;
63 /* By default, we'll grant full read access to REVISION. */
64 *access_level = svn_repos_revision_access_full;
66 /* No auth-checking function? We're done. */
67 if (! authz_read_func)
70 /* Fetch the changes associated with REVISION. */
71 SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
72 SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
74 /* No changed paths? We're done. */
75 if (apr_hash_count(changes) == 0)
78 /* Otherwise, we have to check the readability of each changed
79 path, or at least enough to answer the question asked. */
80 subpool = svn_pool_create(pool);
81 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
85 svn_fs_path_change2_t *change;
86 svn_boolean_t readable;
88 svn_pool_clear(subpool);
89 apr_hash_this(hi, &key, NULL, &val);
92 SVN_ERR(authz_read_func(&readable, rev_root, key,
93 authz_read_baton, subpool));
95 found_unreadable = TRUE;
97 found_readable = TRUE;
99 /* If we have at least one of each (readable/unreadable), we
101 if (found_readable && found_unreadable)
104 switch (change->change_kind)
106 case svn_fs_path_change_add:
107 case svn_fs_path_change_replace:
109 const char *copyfrom_path;
110 svn_revnum_t copyfrom_rev;
112 SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path,
113 rev_root, key, subpool));
114 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
116 svn_fs_root_t *copyfrom_root;
117 SVN_ERR(svn_fs_revision_root(©from_root, fs,
118 copyfrom_rev, subpool));
119 SVN_ERR(authz_read_func(&readable,
120 copyfrom_root, copyfrom_path,
121 authz_read_baton, subpool));
123 found_unreadable = TRUE;
125 /* If we have at least one of each (readable/unreadable), we
127 if (found_readable && found_unreadable)
133 case svn_fs_path_change_delete:
134 case svn_fs_path_change_modify:
141 svn_pool_destroy(subpool);
143 /* Either every changed path was unreadable... */
144 if (! found_readable)
145 *access_level = svn_repos_revision_access_none;
147 /* ... or some changed path was unreadable... */
148 else if (found_unreadable)
149 *access_level = svn_repos_revision_access_partial;
151 /* ... or every changed path was readable (the default). */
156 /* Store as keys in CHANGED the paths of all node in ROOT that show a
157 * significant change. "Significant" means that the text or
158 * properties of the node were changed, or that the node was added or
161 * The CHANGED hash set and its keys and values are allocated in POOL;
162 * keys are const char * paths and values are svn_log_changed_path_t.
164 * To prevent changes from being processed over and over again, the
165 * changed paths for ROOT may be passed in PREFETCHED_CHANGES. If the
166 * latter is NULL, we will request the list inside this function.
168 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
169 * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
170 * copyfrom_path) is readable:
172 * - If some paths are readable and some are not, then silently
173 * omit the unreadable paths from the CHANGED hash, and return
174 * SVN_ERR_AUTHZ_PARTIALLY_READABLE.
176 * - If absolutely every changed-path (and copyfrom_path) is
177 * unreadable, then return an empty CHANGED hash and
178 * SVN_ERR_AUTHZ_UNREADABLE. (This is to distinguish a revision
179 * which truly has no changed paths from a revision in which all
180 * paths are unreadable.)
183 detect_changed(apr_hash_t **changed,
186 apr_hash_t *prefetched_changes,
187 svn_repos_authz_func_t authz_read_func,
188 void *authz_read_baton,
191 apr_hash_t *changes = prefetched_changes;
192 apr_hash_index_t *hi;
194 svn_boolean_t found_readable = FALSE;
195 svn_boolean_t found_unreadable = FALSE;
197 *changed = svn_hash__make(pool);
199 SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
201 if (apr_hash_count(changes) == 0)
202 /* No paths changed in this revision? Uh, sure, I guess the
203 revision is readable, then. */
206 subpool = svn_pool_create(pool);
208 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
210 /* NOTE: Much of this loop is going to look quite similar to
211 svn_repos_check_revision_access(), but we have to do more things
212 here, so we'll live with the duplication. */
215 svn_fs_path_change2_t *change;
218 svn_log_changed_path2_t *item;
220 svn_pool_clear(subpool);
222 /* KEY will be the path, VAL the change. */
223 apr_hash_this(hi, &key, NULL, &val);
224 path = (const char *) key;
227 /* Skip path if unreadable. */
230 svn_boolean_t readable;
231 SVN_ERR(authz_read_func(&readable,
233 authz_read_baton, subpool));
236 found_unreadable = TRUE;
241 /* At least one changed-path was readable. */
242 found_readable = TRUE;
244 switch (change->change_kind)
246 case svn_fs_path_change_reset:
249 case svn_fs_path_change_add:
253 case svn_fs_path_change_replace:
257 case svn_fs_path_change_delete:
261 case svn_fs_path_change_modify:
267 item = svn_log_changed_path2_create(pool);
268 item->action = action;
269 item->node_kind = change->node_kind;
270 item->copyfrom_rev = SVN_INVALID_REVNUM;
271 item->text_modified = change->text_mod ? svn_tristate_true
272 : svn_tristate_false;
273 item->props_modified = change->prop_mod ? svn_tristate_true
274 : svn_tristate_false;
276 /* Pre-1.6 revision files don't store the change path kind, so fetch
278 if (item->node_kind == svn_node_unknown)
280 svn_fs_root_t *check_root = root;
281 const char *check_path = path;
283 /* Deleted items don't exist so check earlier revision. We
284 know the parent must exist and could be a copy */
285 if (change->change_kind == svn_fs_path_change_delete)
287 svn_fs_history_t *history;
288 svn_revnum_t prev_rev;
289 const char *parent_path, *name;
291 svn_fspath__split(&parent_path, &name, path, subpool);
293 SVN_ERR(svn_fs_node_history(&history, root, parent_path,
296 /* Two calls because the first call returns the original
297 revision as the deleted child means it is 'interesting' */
298 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
299 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
301 SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
303 SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool));
304 check_path = svn_fspath__join(parent_path, name, subpool);
307 SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
312 if ((action == 'A') || (action == 'R'))
314 const char *copyfrom_path = change->copyfrom_path;
315 svn_revnum_t copyfrom_rev = change->copyfrom_rev;
317 /* the following is a potentially expensive operation since on FSFS
318 we will follow the DAG from ROOT to PATH and that requires
319 actually reading the directories along the way. */
320 if (!change->copyfrom_known)
321 SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path,
322 root, path, subpool));
324 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
326 svn_boolean_t readable = TRUE;
330 svn_fs_root_t *copyfrom_root;
332 SVN_ERR(svn_fs_revision_root(©from_root, fs,
333 copyfrom_rev, subpool));
334 SVN_ERR(authz_read_func(&readable,
335 copyfrom_root, copyfrom_path,
336 authz_read_baton, subpool));
338 found_unreadable = TRUE;
343 item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
344 item->copyfrom_rev = copyfrom_rev;
348 svn_hash_sets(*changed, apr_pstrdup(pool, path), item);
351 svn_pool_destroy(subpool);
353 if (! found_readable)
354 /* Every changed-path was unreadable. */
355 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
358 if (found_unreadable)
359 /* At least one changed-path was unreadable. */
360 return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
363 /* Every changed-path was readable. */
367 /* This is used by svn_repos_get_logs to keep track of multiple
368 * path history information while working through history.
370 * The two pools are swapped after each iteration through history because
371 * to get the next history requires the previous one.
375 svn_stringbuf_t *path;
376 svn_revnum_t history_rev;
378 svn_boolean_t first_time;
380 /* If possible, we like to keep open the history object for each path,
381 since it avoids needed to open and close it many times as we walk
382 backwards in time. To do so we need two pools, so that we can clear
383 one each time through. If we're not holding the history open for
384 this path then these three pointers will be NULL. */
385 svn_fs_history_t *hist;
390 /* Advance to the next history for the path.
392 * If INFO->HIST is not NULL we do this using that existing history object,
393 * otherwise we open a new one.
395 * If no more history is available or the history revision is less
396 * (earlier) than START, or the history is not available due
397 * to authorization, then INFO->DONE is set to TRUE.
399 * A STRICT value of FALSE will indicate to follow history across copied
402 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
403 * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
404 * we do indeed find more history for the path.
407 get_history(struct path_info *info,
409 svn_boolean_t strict,
410 svn_repos_authz_func_t authz_read_func,
411 void *authz_read_baton,
415 svn_fs_root_t *history_root = NULL;
416 svn_fs_history_t *hist;
422 subpool = info->newpool;
424 SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool));
430 subpool = svn_pool_create(pool);
432 /* Open the history located at the last rev we were at. */
433 SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
436 SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
439 SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
441 if (info->first_time)
442 info->first_time = FALSE;
444 SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
449 svn_pool_destroy(subpool);
451 svn_pool_destroy(info->oldpool);
456 /* Fetch the location information for this history step. */
457 SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
460 svn_stringbuf_set(info->path, path);
462 /* If this history item predates our START revision then
463 don't fetch any more for this path. */
464 if (info->history_rev < start)
466 svn_pool_destroy(subpool);
468 svn_pool_destroy(info->oldpool);
473 /* Is the history item readable? If not, done with path. */
476 svn_boolean_t readable;
477 SVN_ERR(svn_fs_revision_root(&history_root, fs,
480 SVN_ERR(authz_read_func(&readable, history_root,
490 svn_pool_destroy(subpool);
494 apr_pool_t *temppool = info->oldpool;
495 info->oldpool = info->newpool;
496 svn_pool_clear(temppool);
497 info->newpool = temppool;
503 /* Set INFO->HIST to the next history for the path *if* there is history
504 * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
506 * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
507 * otherwise it is not touched.
509 * If we do need to get the next history revision for the path, call
510 * get_history to do it -- see it for details.
513 check_history(svn_boolean_t *changed,
514 struct path_info *info,
516 svn_revnum_t current,
517 svn_boolean_t strict,
518 svn_repos_authz_func_t authz_read_func,
519 void *authz_read_baton,
523 /* If we're already done with histories for this path,
524 don't try to fetch any more. */
528 /* If the last rev we got for this path is less than CURRENT,
529 then just return and don't fetch history for this path.
530 The caller will get to this rev eventually or else reach
532 if (info->history_rev < current)
535 /* If the last rev we got for this path is equal to CURRENT
536 then set *CHANGED to true and get the next history
537 rev where this path was changed. */
539 return get_history(info, fs, strict, authz_read_func,
540 authz_read_baton, start, pool);
543 /* Return the next interesting revision in our list of HISTORIES. */
545 next_history_rev(const apr_array_header_t *histories)
547 svn_revnum_t next_rev = SVN_INVALID_REVNUM;
550 for (i = 0; i < histories->nelts; ++i)
552 struct path_info *info = APR_ARRAY_IDX(histories, i,
556 if (info->history_rev > next_rev)
557 next_rev = info->history_rev;
563 /* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
564 catalogs describing how mergeinfo values on paths (which are the
565 keys of those catalogs) were changed in REV. If *PREFETCHED_CAHNGES
566 already contains the changed paths for REV, use that. Otherwise,
567 request that data and return it in *PREFETCHED_CHANGES. */
568 /* ### TODO: This would make a *great*, useful public function,
569 ### svn_repos_fs_mergeinfo_changed()! -- cmpilato */
571 fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
572 svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
573 apr_hash_t **prefetched_changes,
576 apr_pool_t *result_pool,
577 apr_pool_t *scratch_pool)
581 apr_pool_t *iterpool;
582 apr_hash_index_t *hi;
584 /* Initialize return variables. */
585 *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
586 *added_mergeinfo_catalog = svn_hash__make(result_pool);
588 /* Revision 0 has no mergeinfo and no mergeinfo changes. */
592 /* We're going to use the changed-paths information for REV to
593 narrow down our search. */
594 SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
595 if (*prefetched_changes == NULL)
596 SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
598 /* No changed paths? We're done. */
599 if (apr_hash_count(*prefetched_changes) == 0)
602 /* Loop over changes, looking for anything that might carry an
603 svn:mergeinfo change and is one of our paths of interest, or a
604 child or [grand]parent directory thereof. */
605 iterpool = svn_pool_create(scratch_pool);
606 for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
608 hi = apr_hash_next(hi))
612 svn_fs_path_change2_t *change;
613 const char *changed_path, *base_path = NULL;
614 svn_revnum_t base_rev = SVN_INVALID_REVNUM;
615 svn_fs_root_t *base_root = NULL;
616 svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
618 svn_pool_clear(iterpool);
620 /* KEY will be the path, VAL the change. */
621 apr_hash_this(hi, &key, NULL, &val);
625 /* If there was no property change on this item, ignore it. */
626 if (! change->prop_mod)
629 switch (change->change_kind)
632 /* ### TODO: Can the add, replace, and modify cases be joined
633 ### together to all use svn_repos__prev_location()? The
634 ### difference would be the fallback case (path/rev-1 for
635 ### modifies, NULL otherwise). -- cmpilato */
637 /* If the path was added or replaced, see if it was created via
638 copy. If so, that will tell us where its previous location
639 was. If not, there's no previous location to examine. */
640 case svn_fs_path_change_add:
641 case svn_fs_path_change_replace:
643 const char *copyfrom_path;
644 svn_revnum_t copyfrom_rev;
646 SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path,
647 root, changed_path, iterpool));
648 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
650 base_path = apr_pstrdup(scratch_pool, copyfrom_path);
651 base_rev = copyfrom_rev;
656 /* If the path was merely modified, see if its previous
657 location was affected by a copy which happened in this
658 revision before assuming it holds the same path it did the
659 previous revision. */
660 case svn_fs_path_change_modify:
662 svn_revnum_t appeared_rev;
664 SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
666 changed_path, iterpool));
668 /* If this path isn't the result of a copy that occurred
669 in this revision, we can find the previous version of
670 it in REV - 1 at the same path. */
671 if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
672 && (appeared_rev == rev)))
674 base_path = changed_path;
680 /* We don't care about any of the other cases. */
681 case svn_fs_path_change_delete:
682 case svn_fs_path_change_reset:
687 /* If there was a base location, fetch its mergeinfo property value. */
688 if (base_path && SVN_IS_VALID_REVNUM(base_rev))
690 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool));
691 SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path,
692 SVN_PROP_MERGEINFO, iterpool));
695 /* Now fetch the current (as of REV) mergeinfo property value. */
696 SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path,
697 SVN_PROP_MERGEINFO, iterpool));
699 /* No mergeinfo on either the new or previous location? Just
700 skip it. (If there *was* a change, it would have been in
701 inherited mergeinfo only, which should be picked up by the
702 iteration of this loop that finds the parent paths that
703 really got changed.) */
704 if (! (mergeinfo_value || prev_mergeinfo_value))
707 /* If mergeinfo was explicitly added or removed on this path, we
708 need to check to see if that was a real semantic change of
709 meaning. So, fill in the "missing" mergeinfo value with the
710 inherited mergeinfo for that path/revision. */
711 if (prev_mergeinfo_value && (! mergeinfo_value))
713 apr_array_header_t *query_paths =
714 apr_array_make(iterpool, 1, sizeof(const char *));
715 svn_mergeinfo_t tmp_mergeinfo;
716 svn_mergeinfo_catalog_t tmp_catalog;
718 APR_ARRAY_PUSH(query_paths, const char *) = changed_path;
719 SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root,
720 query_paths, svn_mergeinfo_inherited,
721 FALSE, TRUE, iterpool, iterpool));
722 tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path);
724 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
728 else if (mergeinfo_value && (! prev_mergeinfo_value)
729 && base_path && SVN_IS_VALID_REVNUM(base_rev))
731 apr_array_header_t *query_paths =
732 apr_array_make(iterpool, 1, sizeof(const char *));
733 svn_mergeinfo_t tmp_mergeinfo;
734 svn_mergeinfo_catalog_t tmp_catalog;
736 APR_ARRAY_PUSH(query_paths, const char *) = base_path;
737 SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root,
738 query_paths, svn_mergeinfo_inherited,
739 FALSE, TRUE, iterpool, iterpool));
740 tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path);
742 SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
747 /* If the old and new mergeinfo differ in any way, store the
748 before and after mergeinfo values in our return hashes. */
749 if ((prev_mergeinfo_value && (! mergeinfo_value))
750 || ((! prev_mergeinfo_value) && mergeinfo_value)
751 || (prev_mergeinfo_value && mergeinfo_value
752 && (! svn_string_compare(mergeinfo_value,
753 prev_mergeinfo_value))))
755 svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
756 svn_mergeinfo_t deleted, added;
757 const char *hash_path;
760 SVN_ERR(svn_mergeinfo_parse(&mergeinfo,
761 mergeinfo_value->data, iterpool));
762 if (prev_mergeinfo_value)
763 SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
764 prev_mergeinfo_value->data, iterpool));
765 SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
766 mergeinfo, FALSE, result_pool,
769 /* Toss interesting stuff into our return catalogs. */
770 hash_path = apr_pstrdup(result_pool, changed_path);
771 svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
772 svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
776 svn_pool_destroy(iterpool);
781 /* Determine what (if any) mergeinfo for PATHS was modified in
782 revision REV, returning the differences for added mergeinfo in
783 *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
784 If *PREFETCHED_CAHNGES already contains the changed paths for
785 REV, use that. Otherwise, request that data and return it in
787 Use POOL for all allocations. */
789 get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
790 svn_mergeinfo_t *deleted_mergeinfo,
791 apr_hash_t **prefetched_changes,
793 const apr_array_header_t *paths,
795 apr_pool_t *result_pool,
796 apr_pool_t *scratch_pool)
798 svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog;
799 apr_hash_index_t *hi;
801 apr_pool_t *iterpool;
805 /* Initialize return value. */
806 *added_mergeinfo = svn_hash__make(result_pool);
807 *deleted_mergeinfo = svn_hash__make(result_pool);
809 /* If we're asking about revision 0, there's no mergeinfo to be found. */
813 /* No paths? No mergeinfo. */
817 /* Create a work subpool and get a root for REV. */
818 SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
820 /* Fetch the mergeinfo changes for REV. */
821 err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
822 &added_mergeinfo_catalog,
824 fs, rev, scratch_pool, scratch_pool);
827 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
829 /* Issue #3896: If invalid mergeinfo is encountered the
830 best we can do is ignore it and act as if there were
831 no mergeinfo modifications. */
832 svn_error_clear(err);
837 return svn_error_trace(err);
841 /* In most revisions, there will be no mergeinfo change at all. */
842 if ( apr_hash_count(deleted_mergeinfo_catalog) == 0
843 && apr_hash_count(added_mergeinfo_catalog) == 0)
846 /* Check our PATHS for any changes to their inherited mergeinfo.
847 (We deal with changes to mergeinfo directly *on* the paths in the
849 iterpool = svn_pool_create(scratch_pool);
850 for (i = 0; i < paths->nelts; i++)
852 const char *path = APR_ARRAY_IDX(paths, i, const char *);
853 const char *prev_path;
855 svn_revnum_t appeared_rev, prev_rev;
856 svn_fs_root_t *prev_root;
857 svn_mergeinfo_catalog_t catalog, inherited_catalog;
858 svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
859 prev_inherited_mergeinfo, inherited_mergeinfo;
860 apr_array_header_t *query_paths;
862 svn_pool_clear(iterpool);
864 /* If this path is represented in the changed-mergeinfo hashes,
865 we'll deal with it in the loop below. */
866 if (svn_hash_gets(deleted_mergeinfo_catalog, path))
869 /* Figure out what path/rev to compare against. Ignore
870 not-found errors returned by the filesystem. */
871 err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
872 fs, rev, path, iterpool);
873 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
874 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
876 svn_error_clear(err);
882 /* If this path isn't the result of a copy that occurred in this
883 revision, we can find the previous version of it in REV - 1
885 if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev)
886 && (appeared_rev == rev)))
892 /* Fetch the previous mergeinfo (including inherited stuff) for
893 this path. Ignore not-found errors returned by the
894 filesystem or invalid mergeinfo (Issue #3896).*/
895 SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
896 query_paths = apr_array_make(iterpool, 1, sizeof(const char *));
897 APR_ARRAY_PUSH(query_paths, const char *) = prev_path;
898 err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths,
899 svn_mergeinfo_inherited, FALSE, TRUE,
901 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
902 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
903 err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
905 svn_error_clear(err);
911 /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
912 to move as a merge': A copy where the source and destination inherit
913 mergeinfo from the same parent means the inherited mergeinfo of the
914 source and destination will differ, but this diffrence is not
915 indicative of a merge unless the mergeinfo on the inherited parent
916 has actually changed.
918 To check for this we must fetch the "raw" previous inherited
919 mergeinfo and the "raw" mergeinfo @REV then compare these. */
920 SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths,
921 svn_mergeinfo_nearest_ancestor, FALSE,
922 FALSE, /* adjust_inherited_mergeinfo */
923 iterpool, iterpool));
925 klen = strlen(prev_path);
926 prev_mergeinfo = apr_hash_get(catalog, prev_path, klen);
927 prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen);
929 /* Fetch the current mergeinfo (as of REV, and including
930 inherited stuff) for this path. */
931 APR_ARRAY_IDX(query_paths, 0, const char *) = path;
932 SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths,
933 svn_mergeinfo_inherited, FALSE, TRUE,
934 iterpool, iterpool));
936 /* Issue #4022 again, fetch the raw inherited mergeinfo. */
937 SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths,
938 svn_mergeinfo_nearest_ancestor, FALSE,
939 FALSE, /* adjust_inherited_mergeinfo */
940 iterpool, iterpool));
943 mergeinfo = apr_hash_get(catalog, path, klen);
944 inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen);
946 if (!prev_mergeinfo && !mergeinfo)
949 /* Last bit of issue #4022 checking. */
950 if (prev_inherited_mergeinfo && inherited_mergeinfo)
952 svn_boolean_t inherits_same_mergeinfo;
954 SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
955 prev_inherited_mergeinfo,
958 /* If a copy rather than an actual merge brought about an
959 inherited mergeinfo change then we are finished. */
960 if (inherits_same_mergeinfo)
965 svn_boolean_t same_mergeinfo;
966 SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
967 prev_inherited_mergeinfo,
974 /* Compare, constrast, and combine the results. */
975 SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
976 mergeinfo, FALSE, result_pool, iterpool));
977 SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
978 result_pool, iterpool));
979 SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
980 result_pool, iterpool));
983 /* Merge all the mergeinfos which are, or are children of, one of
984 our paths of interest into one giant delta mergeinfo. */
985 for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
986 hi; hi = apr_hash_next(hi))
991 const char *changed_path;
992 svn_mergeinfo_t added, deleted;
994 /* The path is the key, the mergeinfo delta is the value. */
995 apr_hash_this(hi, &key, &klen, &val);
999 for (i = 0; i < paths->nelts; i++)
1001 const char *path = APR_ARRAY_IDX(paths, i, const char *);
1002 if (! svn_fspath__skip_ancestor(path, changed_path))
1004 svn_pool_clear(iterpool);
1005 deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen);
1006 SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
1007 svn_mergeinfo_dup(deleted, result_pool),
1008 result_pool, iterpool));
1009 SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
1010 svn_mergeinfo_dup(added, result_pool),
1011 result_pool, iterpool));
1017 svn_pool_destroy(iterpool);
1018 return SVN_NO_ERROR;
1022 /* Fill LOG_ENTRY with history information in FS at REV. */
1023 static svn_error_t *
1024 fill_log_entry(svn_log_entry_t *log_entry,
1027 apr_hash_t *prefetched_changes,
1028 svn_boolean_t discover_changed_paths,
1029 const apr_array_header_t *revprops,
1030 svn_repos_authz_func_t authz_read_func,
1031 void *authz_read_baton,
1034 apr_hash_t *r_props, *changed_paths = NULL;
1035 svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
1037 /* Discover changed paths if the user requested them
1038 or if we need to check that they are readable. */
1040 && (authz_read_func || discover_changed_paths))
1042 svn_fs_root_t *newroot;
1043 svn_error_t *patherr;
1045 SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
1046 patherr = detect_changed(&changed_paths,
1047 newroot, fs, prefetched_changes,
1048 authz_read_func, authz_read_baton,
1052 && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
1054 /* All changed-paths are unreadable, so clear all fields. */
1055 svn_error_clear(patherr);
1056 changed_paths = NULL;
1057 get_revprops = FALSE;
1060 && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
1062 /* At least one changed-path was unreadable, so censor all
1063 but author and date. (The unreadable paths are already
1064 missing from the hash.) */
1065 svn_error_clear(patherr);
1066 censor_revprops = TRUE;
1071 /* It may be the case that an authz func was passed in, but
1072 the user still doesn't want to see any changed-paths. */
1073 if (! discover_changed_paths)
1074 changed_paths = NULL;
1079 /* User is allowed to see at least some revprops. */
1080 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
1081 if (revprops == NULL)
1083 /* Requested all revprops... */
1084 if (censor_revprops)
1086 /* ... but we can only return author/date. */
1087 log_entry->revprops = svn_hash__make(pool);
1088 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1089 svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
1090 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1091 svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
1094 /* ... so return all we got. */
1095 log_entry->revprops = r_props;
1099 /* Requested only some revprops... */
1101 for (i = 0; i < revprops->nelts; i++)
1103 char *name = APR_ARRAY_IDX(revprops, i, char *);
1104 svn_string_t *value = svn_hash_gets(r_props, name);
1106 && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
1107 || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
1108 /* ... but we can only return author/date. */
1110 if (log_entry->revprops == NULL)
1111 log_entry->revprops = svn_hash__make(pool);
1112 svn_hash_sets(log_entry->revprops, name, value);
1117 log_entry->changed_paths = changed_paths;
1118 log_entry->changed_paths2 = changed_paths;
1119 log_entry->revision = rev;
1121 return SVN_NO_ERROR;
1124 /* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
1126 FS is used with REV to fetch the interesting history information,
1127 such as changed paths, revprops, etc.
1129 The detect_changed function is used if either AUTHZ_READ_FUNC is
1130 not NULL, or if DISCOVER_CHANGED_PATHS is TRUE. See it for details.
1132 If DESCENDING_ORDER is true, send child messages in descending order.
1134 If REVPROPS is NULL, retrieve all revision properties; else, retrieve
1135 only the revision properties named by the (const char *) array elements
1136 (i.e. retrieve none if the array is empty).
1138 LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
1139 NESTED_MERGES are as per the arguments of the same name to DO_LOGS. If
1140 HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
1141 already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
1142 the log message for REV. If SUBTRACTIVE_MERGE is true, then REV was
1145 If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES. Otherwise
1146 if NESTED_MERGES is not NULL and REV is contained in it, then don't send
1147 the log for REV, otherwise send it normally and add REV to
1149 static svn_error_t *
1150 send_log(svn_revnum_t rev,
1152 apr_hash_t *prefetched_changes,
1153 svn_mergeinfo_t log_target_history_as_mergeinfo,
1154 apr_hash_t *nested_merges,
1155 svn_boolean_t discover_changed_paths,
1156 svn_boolean_t subtractive_merge,
1157 svn_boolean_t handling_merged_revision,
1158 const apr_array_header_t *revprops,
1159 svn_boolean_t has_children,
1160 svn_log_entry_receiver_t receiver,
1161 void *receiver_baton,
1162 svn_repos_authz_func_t authz_read_func,
1163 void *authz_read_baton,
1166 svn_log_entry_t *log_entry;
1167 /* Assume we want to send the log for REV. */
1168 svn_boolean_t found_rev_of_interest = TRUE;
1170 log_entry = svn_log_entry_create(pool);
1171 SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
1172 discover_changed_paths || handling_merged_revision,
1173 revprops, authz_read_func, authz_read_baton,
1175 log_entry->has_children = has_children;
1176 log_entry->subtractive_merge = subtractive_merge;
1178 /* Is REV a merged revision that is already part of
1179 LOG_TARGET_HISTORY_AS_MERGEINFO? If so then there is no
1180 need to send it, since it already was (or will be) sent. */
1181 if (handling_merged_revision
1182 && log_entry->changed_paths2
1183 && log_target_history_as_mergeinfo
1184 && apr_hash_count(log_target_history_as_mergeinfo))
1186 apr_hash_index_t *hi;
1187 apr_pool_t *subpool = svn_pool_create(pool);
1189 /* REV was merged in, but it might already be part of the log target's
1190 natural history, so change our starting assumption. */
1191 found_rev_of_interest = FALSE;
1193 /* Look at each changed path in REV. */
1194 for (hi = apr_hash_first(subpool, log_entry->changed_paths2);
1196 hi = apr_hash_next(hi))
1198 svn_boolean_t path_is_in_history = FALSE;
1199 const char *changed_path = svn__apr_hash_index_key(hi);
1200 apr_hash_index_t *hi2;
1201 apr_pool_t *inner_subpool = svn_pool_create(subpool);
1203 /* Look at each path on the log target's mergeinfo. */
1204 for (hi2 = apr_hash_first(inner_subpool,
1205 log_target_history_as_mergeinfo);
1207 hi2 = apr_hash_next(hi2))
1209 const char *mergeinfo_path =
1210 svn__apr_hash_index_key(hi2);
1211 svn_rangelist_t *rangelist =
1212 svn__apr_hash_index_val(hi2);
1214 /* Check whether CHANGED_PATH at revision REV is a child of
1215 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
1216 if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
1220 for (i = 0; i < rangelist->nelts; i++)
1222 svn_merge_range_t *range =
1223 APR_ARRAY_IDX(rangelist, i,
1224 svn_merge_range_t *);
1225 if (rev > range->start && rev <= range->end)
1227 path_is_in_history = TRUE;
1232 if (path_is_in_history)
1235 svn_pool_destroy(inner_subpool);
1237 if (!path_is_in_history)
1239 /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
1240 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
1242 found_rev_of_interest = TRUE;
1246 svn_pool_destroy(subpool);
1249 /* If we only got changed paths the sake of detecting redundant merged
1250 revisions, then be sure we don't send that info to the receiver. */
1251 if (!discover_changed_paths && handling_merged_revision)
1252 log_entry->changed_paths = log_entry->changed_paths2 = NULL;
1254 /* Send the entry to the receiver, unless it is a redundant merged
1256 if (found_rev_of_interest)
1258 /* Is REV a merged revision we've already sent? */
1259 if (nested_merges && handling_merged_revision)
1261 svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev,
1262 sizeof(svn_revnum_t *));
1266 /* We already sent REV. */
1267 return SVN_NO_ERROR;
1271 /* NESTED_REVS needs to last across all the send_log, do_logs,
1272 handle_merged_revisions() recursions, so use the pool it
1273 was created in at the top of the recursion. */
1274 apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges);
1275 svn_revnum_t *long_lived_rev = apr_palloc(hash_pool,
1276 sizeof(svn_revnum_t));
1277 *long_lived_rev = rev;
1278 apr_hash_set(nested_merges, long_lived_rev,
1279 sizeof(svn_revnum_t *), long_lived_rev);
1283 return (*receiver)(receiver_baton, log_entry, pool);
1287 return SVN_NO_ERROR;
1291 /* This controls how many history objects we keep open. For any targets
1292 over this number we have to open and close their histories as needed,
1293 which is CPU intensive, but keeps us from using an unbounded amount of
1295 #define MAX_OPEN_HISTORIES 32
1297 /* Get the histories for PATHS, and store them in *HISTORIES.
1299 If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1300 repository locations as fatal -- just ignore them. */
1301 static svn_error_t *
1302 get_path_histories(apr_array_header_t **histories,
1304 const apr_array_header_t *paths,
1305 svn_revnum_t hist_start,
1306 svn_revnum_t hist_end,
1307 svn_boolean_t strict_node_history,
1308 svn_boolean_t ignore_missing_locations,
1309 svn_repos_authz_func_t authz_read_func,
1310 void *authz_read_baton,
1313 svn_fs_root_t *root;
1314 apr_pool_t *iterpool;
1318 /* Create a history object for each path so we can walk through
1319 them all at the same time until we have all changes or LIMIT
1322 There is some pool fun going on due to the fact that we have
1323 to hold on to the old pool with the history before we can
1324 get the next history.
1326 *histories = apr_array_make(pool, paths->nelts,
1327 sizeof(struct path_info *));
1329 SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
1331 iterpool = svn_pool_create(pool);
1332 for (i = 0; i < paths->nelts; i++)
1334 const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
1335 struct path_info *info = apr_palloc(pool,
1336 sizeof(struct path_info));
1338 if (authz_read_func)
1340 svn_boolean_t readable;
1342 svn_pool_clear(iterpool);
1344 SVN_ERR(authz_read_func(&readable, root, this_path,
1345 authz_read_baton, iterpool));
1347 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
1350 info->path = svn_stringbuf_create(this_path, pool);
1352 info->history_rev = hist_end;
1353 info->first_time = TRUE;
1355 if (i < MAX_OPEN_HISTORIES)
1357 err = svn_fs_node_history(&info->hist, root, this_path, pool);
1359 && ignore_missing_locations
1360 && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1361 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1362 err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1364 svn_error_clear(err);
1368 info->newpool = svn_pool_create(pool);
1369 info->oldpool = svn_pool_create(pool);
1374 info->oldpool = NULL;
1375 info->newpool = NULL;
1378 err = get_history(info, fs,
1379 strict_node_history,
1380 authz_read_func, authz_read_baton,
1383 && ignore_missing_locations
1384 && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1385 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1386 err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1388 svn_error_clear(err);
1392 APR_ARRAY_PUSH(*histories, struct path_info *) = info;
1394 svn_pool_destroy(iterpool);
1396 return SVN_NO_ERROR;
1399 /* Remove and return the first item from ARR. */
1401 array_pop_front(apr_array_header_t *arr)
1403 void *item = arr->elts;
1405 if (apr_is_empty_array(arr))
1408 arr->elts += arr->elt_size;
1414 /* A struct which represents a single revision range, and the paths which
1415 have mergeinfo in that range. */
1416 struct path_list_range
1418 apr_array_header_t *paths;
1419 svn_merge_range_t range;
1421 /* Is RANGE the result of a reverse merge? */
1422 svn_boolean_t reverse_merge;
1425 /* A struct which represents "inverse mergeinfo", that is, instead of having
1426 a path->revision_range_list mapping, which is the way mergeinfo is commonly
1427 represented, this struct enables a revision_range_list,path tuple, where
1428 the paths can be accessed by revision. */
1429 struct rangelist_path
1431 svn_rangelist_t *rangelist;
1435 /* Comparator function for combine_mergeinfo_path_lists(). Sorts
1436 rangelist_path structs in increasing order based upon starting revision,
1437 then ending revision of the first element in the rangelist.
1439 This does not sort rangelists based upon subsequent elements, only the
1440 first range. We'll sort any subsequent ranges in the correct order
1441 when they get bumped up to the front by removal of earlier ones, so we
1442 don't really have to sort them here. See combine_mergeinfo_path_lists()
1445 compare_rangelist_paths(const void *a, const void *b)
1447 struct rangelist_path *rpa = *((struct rangelist_path *const *) a);
1448 struct rangelist_path *rpb = *((struct rangelist_path *const *) b);
1449 svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
1450 svn_merge_range_t *);
1451 svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
1452 svn_merge_range_t *);
1454 if (mra->start < mrb->start)
1456 if (mra->start > mrb->start)
1458 if (mra->end < mrb->end)
1460 if (mra->end > mrb->end)
1466 /* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
1467 'struct path_list_range's. This list represents the rangelists in
1468 MERGEINFO and each path which has mergeinfo in that range.
1469 If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed
1470 as the result of a reverse merge. */
1471 static svn_error_t *
1472 combine_mergeinfo_path_lists(apr_array_header_t **combined_list,
1473 svn_mergeinfo_t mergeinfo,
1474 svn_boolean_t reverse_merge,
1477 apr_hash_index_t *hi;
1478 apr_array_header_t *rangelist_paths;
1479 apr_pool_t *subpool = svn_pool_create(pool);
1481 /* Create a list of (revision range, path) tuples from MERGEINFO. */
1482 rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
1483 sizeof(struct rangelist_path *));
1484 for (hi = apr_hash_first(subpool, mergeinfo); hi;
1485 hi = apr_hash_next(hi))
1488 struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
1489 apr_hash_this(hi, (void *) &rp->path, NULL,
1490 (void *) &rp->rangelist);
1491 APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
1493 /* We need to make local copies of the rangelist, since we will be
1494 modifying it, below. */
1495 rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
1497 /* Make all of the rangelists inclusive, both start and end. */
1498 for (i = 0; i < rp->rangelist->nelts; i++)
1499 APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
1502 /* Loop over the (revision range, path) tuples, chopping them into
1503 (revision range, paths) tuples, and appending those to the output
1505 if (! *combined_list)
1506 *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
1508 while (rangelist_paths->nelts > 1)
1510 svn_revnum_t youngest, next_youngest, tail, youngest_end;
1511 struct path_list_range *plr;
1512 struct rangelist_path *rp;
1516 /* First, sort the list such that the start revision of the first
1517 revision arrays are sorted. */
1518 qsort(rangelist_paths->elts, rangelist_paths->nelts,
1519 rangelist_paths->elt_size, compare_rangelist_paths);
1521 /* Next, find the number of revision ranges which start with the same
1523 rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1525 APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
1526 next_youngest = youngest;
1527 for (num_revs = 1; next_youngest == youngest; num_revs++)
1529 if (num_revs == rangelist_paths->nelts)
1534 rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
1535 struct rangelist_path *);
1536 next_youngest = APR_ARRAY_IDX(rp->rangelist, 0,
1537 struct svn_merge_range_t *)->start;
1541 /* The start of the new range will be YOUNGEST, and we now find the end
1542 of the new range, which should be either one less than the next
1543 earliest start of a rangelist, or the end of the first rangelist. */
1545 APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
1546 struct rangelist_path *)->rangelist,
1547 0, svn_merge_range_t *)->end;
1548 if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
1549 tail = youngest_end;
1551 tail = next_youngest - 1;
1553 /* Insert the (earliest, tail) tuple into the output list, along with
1554 a list of paths which match it. */
1555 plr = apr_palloc(pool, sizeof(*plr));
1556 plr->reverse_merge = reverse_merge;
1557 plr->range.start = youngest;
1558 plr->range.end = tail;
1559 plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
1560 for (i = 0; i < num_revs; i++)
1561 APR_ARRAY_PUSH(plr->paths, const char *) =
1562 APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
1563 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1565 /* Now, check to see which (rangelist path) combinations we can remove,
1567 for (i = 0; i < num_revs; i++)
1569 svn_merge_range_t *range;
1570 rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
1571 range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
1573 /* Set the start of the range to beyond the end of the range we
1574 just built. If the range is now "inverted", we can get pop it
1576 range->start = tail + 1;
1577 if (range->start > range->end)
1579 if (rp->rangelist->nelts == 1)
1581 /* The range is the only on its list, so we should remove
1582 the entire rangelist_path, adjusting our loop control
1583 variables appropriately. */
1584 array_pop_front(rangelist_paths);
1590 /* We have more than one range on the list, so just remove
1592 array_pop_front(rp->rangelist);
1598 /* Finally, add the last remaining (revision range, path) to the output
1600 if (rangelist_paths->nelts > 0)
1602 struct rangelist_path *first_rp =
1603 APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1604 while (first_rp->rangelist->nelts > 0)
1606 struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
1608 plr->reverse_merge = reverse_merge;
1609 plr->paths = apr_array_make(pool, 1, sizeof(const char *));
1610 APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
1611 plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0,
1612 svn_merge_range_t *);
1613 array_pop_front(first_rp->rangelist);
1614 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1618 svn_pool_destroy(subpool);
1620 return SVN_NO_ERROR;
1624 /* Pity that C is so ... linear. */
1625 static svn_error_t *
1626 do_logs(svn_fs_t *fs,
1627 const apr_array_header_t *paths,
1628 svn_mergeinfo_t log_target_history_as_mergeinfo,
1629 svn_mergeinfo_t processed,
1630 apr_hash_t *nested_merges,
1631 svn_revnum_t hist_start,
1632 svn_revnum_t hist_end,
1634 svn_boolean_t discover_changed_paths,
1635 svn_boolean_t strict_node_history,
1636 svn_boolean_t include_merged_revisions,
1637 svn_boolean_t handling_merged_revisions,
1638 svn_boolean_t subtractive_merge,
1639 svn_boolean_t ignore_missing_locations,
1640 const apr_array_header_t *revprops,
1641 svn_boolean_t descending_order,
1642 svn_log_entry_receiver_t receiver,
1643 void *receiver_baton,
1644 svn_repos_authz_func_t authz_read_func,
1645 void *authz_read_baton,
1648 /* Comparator function for handle_merged_revisions(). Sorts path_list_range
1649 structs in increasing order based on the struct's RANGE.START revision,
1650 then RANGE.END revision. */
1652 compare_path_list_range(const void *a, const void *b)
1654 struct path_list_range *plr_a = *((struct path_list_range *const *) a);
1655 struct path_list_range *plr_b = *((struct path_list_range *const *) b);
1657 if (plr_a->range.start < plr_b->range.start)
1659 if (plr_a->range.start > plr_b->range.start)
1661 if (plr_a->range.end < plr_b->range.end)
1663 if (plr_a->range.end > plr_b->range.end)
1669 /* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS
1670 (as collected by examining paths of interest to a log operation), and
1671 determine which revisions to report as having been merged or reverse-merged
1672 via the commit resulting in REV.
1674 Silently ignore some failures to find the revisions mentioned in the
1675 added/deleted mergeinfos, as might happen if there is invalid mergeinfo.
1677 Other parameters are as described by do_logs(), around which this
1678 is a recursion wrapper. */
1679 static svn_error_t *
1680 handle_merged_revisions(svn_revnum_t rev,
1682 svn_mergeinfo_t log_target_history_as_mergeinfo,
1683 apr_hash_t *nested_merges,
1684 svn_mergeinfo_t processed,
1685 svn_mergeinfo_t added_mergeinfo,
1686 svn_mergeinfo_t deleted_mergeinfo,
1687 svn_boolean_t discover_changed_paths,
1688 svn_boolean_t strict_node_history,
1689 const apr_array_header_t *revprops,
1690 svn_log_entry_receiver_t receiver,
1691 void *receiver_baton,
1692 svn_repos_authz_func_t authz_read_func,
1693 void *authz_read_baton,
1696 apr_array_header_t *combined_list = NULL;
1697 svn_log_entry_t *empty_log_entry;
1698 apr_pool_t *iterpool;
1701 if (apr_hash_count(added_mergeinfo) == 0
1702 && apr_hash_count(deleted_mergeinfo) == 0)
1703 return SVN_NO_ERROR;
1705 if (apr_hash_count(added_mergeinfo))
1706 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo,
1709 if (apr_hash_count(deleted_mergeinfo))
1710 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo,
1713 SVN_ERR_ASSERT(combined_list != NULL);
1714 qsort(combined_list->elts, combined_list->nelts,
1715 combined_list->elt_size, compare_path_list_range);
1717 /* Because the combined_lists are ordered youngest to oldest,
1718 iterate over them in reverse. */
1719 iterpool = svn_pool_create(pool);
1720 for (i = combined_list->nelts - 1; i >= 0; i--)
1722 struct path_list_range *pl_range
1723 = APR_ARRAY_IDX(combined_list, i, struct path_list_range *);
1725 svn_pool_clear(iterpool);
1726 SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
1727 processed, nested_merges,
1728 pl_range->range.start, pl_range->range.end, 0,
1729 discover_changed_paths, strict_node_history,
1730 TRUE, pl_range->reverse_merge, TRUE, TRUE,
1731 revprops, TRUE, receiver, receiver_baton,
1732 authz_read_func, authz_read_baton, iterpool));
1734 svn_pool_destroy(iterpool);
1736 /* Send the empty revision. */
1737 empty_log_entry = svn_log_entry_create(pool);
1738 empty_log_entry->revision = SVN_INVALID_REVNUM;
1739 return (*receiver)(receiver_baton, empty_log_entry, pool);
1742 /* This is used by do_logs to differentiate between forward and
1744 struct added_deleted_mergeinfo
1746 svn_mergeinfo_t added_mergeinfo;
1747 svn_mergeinfo_t deleted_mergeinfo;
1750 /* Reduce the search range PATHS, HIST_START, HIST_END by removing
1751 parts already covered by PROCESSED. If reduction is possible
1752 elements may be removed from PATHS and *START_REDUCED and
1753 *END_REDUCED may be set to a narrower range. */
1754 static svn_error_t *
1755 reduce_search(apr_array_header_t *paths,
1756 svn_revnum_t *hist_start,
1757 svn_revnum_t *hist_end,
1758 svn_mergeinfo_t processed,
1759 apr_pool_t *scratch_pool)
1761 /* We add 1 to end to compensate for store_search */
1762 svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end;
1763 svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1;
1766 for (i = 0; i < paths->nelts; ++i)
1768 const char *path = APR_ARRAY_IDX(paths, i, const char *);
1769 svn_rangelist_t *ranges = svn_hash_gets(processed, path);
1775 /* ranges is ordered, could we use some sort of binary search
1776 rather than iterating? */
1777 for (j = 0; j < ranges->nelts; ++j)
1779 svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j,
1780 svn_merge_range_t *);
1781 if (range->start <= start && range->end >= end)
1783 for (j = i; j < paths->nelts - 1; ++j)
1784 APR_ARRAY_IDX(paths, j, const char *)
1785 = APR_ARRAY_IDX(paths, j + 1, const char *);
1792 /* If there is only one path then we also check for a
1793 partial overlap rather than the full overlap above, and
1794 reduce the [hist_start, hist_end] range rather than
1795 dropping the path. */
1796 if (paths->nelts == 1)
1798 if (range->start <= start && range->end > start)
1800 if (start == *hist_start)
1801 *hist_start = range->end - 1;
1803 *hist_end = range->end - 1;
1806 if (range->start < end && range->end >= end)
1808 if (start == *hist_start)
1809 *hist_end = range->start;
1811 *hist_start = range->start;
1818 return SVN_NO_ERROR;
1821 /* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */
1822 static svn_error_t *
1823 store_search(svn_mergeinfo_t processed,
1824 const apr_array_header_t *paths,
1825 svn_revnum_t hist_start,
1826 svn_revnum_t hist_end,
1827 apr_pool_t *scratch_pool)
1829 /* We add 1 to end so that we can use the mergeinfo API to handle
1830 singe revisions where HIST_START is equal to HIST_END. */
1831 svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
1832 svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
1833 svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
1834 apr_pool_t *processed_pool = apr_hash_pool_get(processed);
1837 for (i = 0; i < paths->nelts; ++i)
1839 const char *path = APR_ARRAY_IDX(paths, i, const char *);
1840 svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
1841 sizeof(svn_merge_range_t*));
1842 svn_merge_range_t *range = apr_palloc(processed_pool,
1843 sizeof(svn_merge_range_t));
1845 range->start = start;
1847 range->inheritable = TRUE;
1848 APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
1849 svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
1851 SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
1852 apr_hash_pool_get(processed), scratch_pool));
1854 return SVN_NO_ERROR;
1857 /* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
1858 RECEIVER with RECEIVER_BATON on them. If DESCENDING_ORDER is TRUE, send
1859 the logs back as we find them, else buffer the logs and send them back
1860 in youngest->oldest order.
1862 If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1863 repository locations as fatal -- just ignore them.
1865 If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo
1866 representing the history of PATHS between HIST_START and HIST_END.
1868 If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
1869 merged revisions, see INCLUDE_MERGED_REVISIONS argument to
1870 svn_repos_get_logs4(). If SUBTRACTIVE_MERGE is true, then this is a
1871 recursive call for reverse merged revisions.
1873 If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
1874 mapped to svn_revnum_t *) for logs that were previously sent. On the first
1875 call to do_logs it should always be NULL. If INCLUDE_MERGED_REVISIONS is
1876 TRUE, then NESTED_MERGES will be created on the first call to do_logs,
1877 allocated in POOL. It is then shared across
1878 do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
1879 argument of the same name in send_logs().
1881 PROCESSED is a mergeinfo hash that represents the paths and
1882 revisions that have already been searched. Allocated like
1883 NESTED_MERGES above.
1885 All other parameters are the same as svn_repos_get_logs4().
1887 static svn_error_t *
1888 do_logs(svn_fs_t *fs,
1889 const apr_array_header_t *paths,
1890 svn_mergeinfo_t log_target_history_as_mergeinfo,
1891 svn_mergeinfo_t processed,
1892 apr_hash_t *nested_merges,
1893 svn_revnum_t hist_start,
1894 svn_revnum_t hist_end,
1896 svn_boolean_t discover_changed_paths,
1897 svn_boolean_t strict_node_history,
1898 svn_boolean_t include_merged_revisions,
1899 svn_boolean_t subtractive_merge,
1900 svn_boolean_t handling_merged_revisions,
1901 svn_boolean_t ignore_missing_locations,
1902 const apr_array_header_t *revprops,
1903 svn_boolean_t descending_order,
1904 svn_log_entry_receiver_t receiver,
1905 void *receiver_baton,
1906 svn_repos_authz_func_t authz_read_func,
1907 void *authz_read_baton,
1910 apr_pool_t *iterpool;
1911 apr_pool_t *subpool = NULL;
1912 apr_array_header_t *revs = NULL;
1913 apr_hash_t *rev_mergeinfo = NULL;
1914 svn_revnum_t current;
1915 apr_array_header_t *histories;
1916 svn_boolean_t any_histories_left = TRUE;
1922 /* Casting away const. This only happens on recursive calls when
1923 it is known to be safe because we allocated paths. */
1924 SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end,
1929 return SVN_NO_ERROR;
1932 SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool));
1934 /* We have a list of paths and a revision range. But we don't care
1935 about all the revisions in the range -- only the ones in which
1936 one of our paths was changed. So let's go figure out which
1937 revisions contain real changes to at least one of our paths. */
1938 SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
1939 strict_node_history, ignore_missing_locations,
1940 authz_read_func, authz_read_baton, pool));
1942 /* Loop through all the revisions in the range and add any
1943 where a path was changed to the array, or if they wanted
1944 history in reverse order just send it to them right away. */
1945 iterpool = svn_pool_create(pool);
1946 for (current = hist_end;
1948 current = next_history_rev(histories))
1950 svn_boolean_t changed = FALSE;
1951 any_histories_left = FALSE;
1952 svn_pool_clear(iterpool);
1954 for (i = 0; i < histories->nelts; i++)
1956 struct path_info *info = APR_ARRAY_IDX(histories, i,
1957 struct path_info *);
1959 /* Check history for this path in current rev. */
1960 SVN_ERR(check_history(&changed, info, fs, current,
1961 strict_node_history, authz_read_func,
1962 authz_read_baton, hist_start, pool));
1964 any_histories_left = TRUE;
1967 /* If any of the paths changed in this rev then add or send it. */
1970 svn_mergeinfo_t added_mergeinfo = NULL;
1971 svn_mergeinfo_t deleted_mergeinfo = NULL;
1972 svn_boolean_t has_children = FALSE;
1973 apr_hash_t *changes = NULL;
1975 /* If we're including merged revisions, we need to calculate
1976 the mergeinfo deltas committed in this revision to our
1978 if (include_merged_revisions)
1980 apr_array_header_t *cur_paths =
1981 apr_array_make(iterpool, paths->nelts, sizeof(const char *));
1983 /* Get the current paths of our history objects so we can
1985 /* ### TODO: Should this be ignoring depleted history items? */
1986 for (i = 0; i < histories->nelts; i++)
1988 struct path_info *info = APR_ARRAY_IDX(histories, i,
1989 struct path_info *);
1990 APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
1992 SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
1998 has_children = (apr_hash_count(added_mergeinfo) > 0
1999 || apr_hash_count(deleted_mergeinfo) > 0);
2002 /* If our caller wants logs in descending order, we can send
2003 'em now (because that's the order we're crawling history
2005 if (descending_order)
2007 SVN_ERR(send_log(current, fs, changes,
2008 log_target_history_as_mergeinfo, nested_merges,
2009 discover_changed_paths,
2010 subtractive_merge, handling_merged_revisions,
2011 revprops, has_children,
2012 receiver, receiver_baton,
2013 authz_read_func, authz_read_baton, iterpool));
2015 if (has_children) /* Implies include_merged_revisions == TRUE */
2019 /* We're at the start of the recursion stack, create a
2020 single hash to be shared across all of the merged
2021 recursions so we can track and squelch duplicates. */
2022 subpool = svn_pool_create(pool);
2023 nested_merges = svn_hash__make(subpool);
2024 processed = svn_hash__make(subpool);
2027 SVN_ERR(handle_merged_revisions(
2029 log_target_history_as_mergeinfo, nested_merges,
2031 added_mergeinfo, deleted_mergeinfo,
2032 discover_changed_paths,
2033 strict_node_history,
2035 receiver, receiver_baton,
2040 if (limit && ++send_count >= limit)
2043 /* Otherwise, the caller wanted logs in ascending order, so
2044 we have to buffer up a list of revs and (if doing
2045 mergeinfo) a hash of related mergeinfo deltas, and
2046 process them later. */
2050 revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
2051 APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
2053 if (added_mergeinfo || deleted_mergeinfo)
2055 svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev));
2056 struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2057 apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
2059 if (added_mergeinfo)
2060 add_and_del_mergeinfo->added_mergeinfo =
2061 svn_mergeinfo_dup(added_mergeinfo, pool);
2063 if (deleted_mergeinfo)
2064 add_and_del_mergeinfo->deleted_mergeinfo =
2065 svn_mergeinfo_dup(deleted_mergeinfo, pool);
2068 if (! rev_mergeinfo)
2069 rev_mergeinfo = svn_hash__make(pool);
2070 apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
2071 add_and_del_mergeinfo);
2076 svn_pool_destroy(iterpool);
2080 nested_merges = NULL;
2081 svn_pool_destroy(subpool);
2086 /* Work loop for processing the revisions we found since they wanted
2087 history in forward order. */
2088 iterpool = svn_pool_create(pool);
2089 for (i = 0; i < revs->nelts; ++i)
2091 svn_mergeinfo_t added_mergeinfo;
2092 svn_mergeinfo_t deleted_mergeinfo;
2093 svn_boolean_t has_children = FALSE;
2095 svn_pool_clear(iterpool);
2096 current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t);
2098 /* If we've got a hash of revision mergeinfo (which can only
2099 happen if INCLUDE_MERGED_REVISIONS was set), we check to
2100 see if this revision is one which merged in other
2101 revisions we need to handle recursively. */
2104 struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2105 apr_hash_get(rev_mergeinfo, ¤t, sizeof(svn_revnum_t));
2106 added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
2107 deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
2108 has_children = (apr_hash_count(added_mergeinfo) > 0
2109 || apr_hash_count(deleted_mergeinfo) > 0);
2112 SVN_ERR(send_log(current, fs, NULL,
2113 log_target_history_as_mergeinfo, nested_merges,
2114 discover_changed_paths, subtractive_merge,
2115 handling_merged_revisions, revprops, has_children,
2116 receiver, receiver_baton, authz_read_func,
2117 authz_read_baton, iterpool));
2122 subpool = svn_pool_create(pool);
2123 nested_merges = svn_hash__make(subpool);
2126 SVN_ERR(handle_merged_revisions(current, fs,
2127 log_target_history_as_mergeinfo,
2132 discover_changed_paths,
2133 strict_node_history, revprops,
2134 receiver, receiver_baton,
2139 if (limit && i + 1 >= limit)
2142 svn_pool_destroy(iterpool);
2145 return SVN_NO_ERROR;
2148 struct location_segment_baton
2150 apr_array_header_t *history_segments;
2154 /* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */
2155 static svn_error_t *
2156 location_segment_receiver(svn_location_segment_t *segment,
2160 struct location_segment_baton *b = baton;
2162 APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) =
2163 svn_location_segment_dup(segment, b->pool);
2165 return SVN_NO_ERROR;
2169 /* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined
2170 history of each path in PATHS between START_REV and END_REV in REPOS's
2171 filesystem. START_REV and END_REV must be valid revisions. RESULT_POOL
2172 is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
2173 other (temporary) allocations. Other parameters are the same as
2174 svn_repos_get_logs4(). */
2175 static svn_error_t *
2176 get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
2178 const apr_array_header_t *paths,
2179 svn_revnum_t start_rev,
2180 svn_revnum_t end_rev,
2181 svn_repos_authz_func_t authz_read_func,
2182 void *authz_read_baton,
2183 apr_pool_t *result_pool,
2184 apr_pool_t *scratch_pool)
2187 svn_mergeinfo_t path_history_mergeinfo;
2188 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2190 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev));
2191 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev));
2193 /* Ensure START_REV is the youngest revision, as required by
2194 svn_repos_node_location_segments, for which this is an iterative
2196 if (start_rev < end_rev)
2198 svn_revnum_t tmp_rev = start_rev;
2199 start_rev = end_rev;
2203 *paths_history_mergeinfo = svn_hash__make(result_pool);
2205 for (i = 0; i < paths->nelts; i++)
2207 const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
2208 struct location_segment_baton loc_seg_baton;
2210 svn_pool_clear(iterpool);
2211 loc_seg_baton.pool = scratch_pool;
2212 loc_seg_baton.history_segments =
2213 apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *));
2215 SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev,
2217 location_segment_receiver,
2223 SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
2224 &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
2225 SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
2226 svn_mergeinfo_dup(path_history_mergeinfo,
2228 result_pool, iterpool));
2230 svn_pool_destroy(iterpool);
2231 return SVN_NO_ERROR;
2235 svn_repos_get_logs4(svn_repos_t *repos,
2236 const apr_array_header_t *paths,
2240 svn_boolean_t discover_changed_paths,
2241 svn_boolean_t strict_node_history,
2242 svn_boolean_t include_merged_revisions,
2243 const apr_array_header_t *revprops,
2244 svn_repos_authz_func_t authz_read_func,
2245 void *authz_read_baton,
2246 svn_log_entry_receiver_t receiver,
2247 void *receiver_baton,
2250 svn_revnum_t head = SVN_INVALID_REVNUM;
2251 svn_fs_t *fs = repos->fs;
2252 svn_boolean_t descending_order;
2253 svn_mergeinfo_t paths_history_mergeinfo = NULL;
2255 /* Setup log range. */
2256 SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
2258 if (! SVN_IS_VALID_REVNUM(start))
2261 if (! SVN_IS_VALID_REVNUM(end))
2264 /* Check that revisions are sane before ever invoking receiver. */
2266 return svn_error_createf
2267 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2268 _("No such revision %ld"), start);
2270 return svn_error_createf
2271 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2272 _("No such revision %ld"), end);
2274 /* Ensure a youngest-to-oldest revision crawl ordering using our
2275 (possibly sanitized) range values. */
2276 descending_order = start >= end;
2277 if (descending_order)
2279 svn_revnum_t tmp_rev = start;
2285 paths = apr_array_make(pool, 0, sizeof(const char *));
2287 /* If we're not including merged revisions, and we were given no
2288 paths or a single empty (or "/") path, then we can bypass a bunch
2289 of complexity because we already know in which revisions the root
2290 directory was changed -- all of them. */
2291 if ((! include_merged_revisions)
2292 && ((! paths->nelts)
2293 || ((paths->nelts == 1)
2294 && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))
2295 || (strcmp(APR_ARRAY_IDX(paths, 0, const char *),
2298 apr_uint64_t send_count = 0;
2300 apr_pool_t *iterpool = svn_pool_create(pool);
2302 /* If we are provided an authz callback function, use it to
2303 verify that the user has read access to the root path in the
2304 first of our revisions.
2306 ### FIXME: Strictly speaking, we should be checking this
2307 ### access in every revision along the line. But currently,
2308 ### there are no known authz implementations which concern
2309 ### themselves with per-revision access. */
2310 if (authz_read_func)
2312 svn_boolean_t readable;
2313 svn_fs_root_t *rev_root;
2315 SVN_ERR(svn_fs_revision_root(&rev_root, fs,
2316 descending_order ? end : start, pool));
2317 SVN_ERR(authz_read_func(&readable, rev_root, "",
2318 authz_read_baton, pool));
2320 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
2323 send_count = end - start + 1;
2324 if (limit && send_count > limit)
2326 for (i = 0; i < send_count; ++i)
2330 svn_pool_clear(iterpool);
2332 if (descending_order)
2336 SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
2337 discover_changed_paths, FALSE,
2338 FALSE, revprops, FALSE, receiver,
2339 receiver_baton, authz_read_func,
2340 authz_read_baton, iterpool));
2342 svn_pool_destroy(iterpool);
2344 return SVN_NO_ERROR;
2347 /* If we are including merged revisions, then create mergeinfo that
2348 represents all of PATHS' history between START and END. We will use
2349 this later to squelch duplicate log revisions that might exist in
2350 both natural history and merged-in history. See
2351 http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
2352 if (include_merged_revisions)
2354 apr_pool_t *subpool = svn_pool_create(pool);
2356 SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
2357 repos, paths, start, end,
2361 svn_pool_destroy(subpool);
2364 return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
2365 limit, discover_changed_paths, strict_node_history,
2366 include_merged_revisions, FALSE, FALSE, FALSE, revprops,
2367 descending_order, receiver, receiver_baton,
2368 authz_read_func, authz_read_baton, pool);