1 /* rev_hunt.c --- routines to hunt down particular fs revisions and
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
26 #include "svn_compat.h"
27 #include "svn_private_config.h"
29 #include "svn_pools.h"
30 #include "svn_error.h"
31 #include "svn_error_codes.h"
33 #include "svn_repos.h"
34 #include "svn_string.h"
36 #include "svn_sorts.h"
37 #include "svn_props.h"
38 #include "svn_mergeinfo.h"
40 #include "private/svn_fspath.h"
43 /* Note: this binary search assumes that the datestamp properties on
44 each revision are in chronological order. That is if revision A >
45 revision B, then A's datestamp is younger then B's datestamp.
47 If someone comes along and sets a bogus datestamp, this routine
50 ### todo: you know, we *could* have svn_fs_change_rev_prop() do
51 some semantic checking when it's asked to change special reserved
52 svn: properties. It could prevent such a problem. */
55 /* helper for svn_repos_dated_revision().
57 Set *TM to the apr_time_t datestamp on revision REV in FS. */
59 get_time(apr_time_t *tm,
64 svn_string_t *date_str;
66 SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
69 return svn_error_createf
70 (SVN_ERR_FS_GENERAL, NULL,
71 _("Failed to find time on revision %ld"), rev);
73 return svn_time_from_cstring(tm, date_str->data, pool);
78 svn_repos_dated_revision(svn_revnum_t *revision,
83 svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
85 svn_fs_t *fs = repos->fs;
87 /* Initialize top and bottom values of binary search. */
88 SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool));
92 while (rev_bot <= rev_top)
94 rev_mid = (rev_top + rev_bot) / 2;
95 SVN_ERR(get_time(&this_time, fs, rev_mid, pool));
97 if (this_time > tm)/* we've overshot */
99 apr_time_t previous_time;
101 if ((rev_mid - 1) < 0)
107 /* see if time falls between rev_mid and rev_mid-1: */
108 SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool));
109 if (previous_time <= tm)
111 *revision = rev_mid - 1;
115 rev_top = rev_mid - 1;
118 else if (this_time < tm) /* we've undershot */
120 apr_time_t next_time;
122 if ((rev_mid + 1) > rev_latest)
124 *revision = rev_latest;
128 /* see if time falls between rev_mid and rev_mid+1: */
129 SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
136 rev_bot = rev_mid + 1;
141 *revision = rev_mid; /* exact match! */
151 svn_repos_get_committed_info(svn_revnum_t *committed_rev,
152 const char **committed_date,
153 const char **last_author,
158 apr_hash_t *revprops;
160 svn_fs_t *fs = svn_fs_root_fs(root);
162 /* ### It might be simpler just to declare that revision
163 properties have char * (i.e., UTF-8) values, not arbitrary
164 binary values, hmmm. */
165 svn_string_t *committed_date_s, *last_author_s;
167 /* Get the CR field out of the node's skel. */
168 SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
170 /* Get the revision properties of this revision. */
171 SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
173 /* Extract date and author from these revprops. */
174 committed_date_s = apr_hash_get(revprops,
175 SVN_PROP_REVISION_DATE,
176 sizeof(SVN_PROP_REVISION_DATE)-1);
177 last_author_s = apr_hash_get(revprops,
178 SVN_PROP_REVISION_AUTHOR,
179 sizeof(SVN_PROP_REVISION_AUTHOR)-1);
181 *committed_date = committed_date_s ? committed_date_s->data : NULL;
182 *last_author = last_author_s ? last_author_s->data : NULL;
188 svn_repos_history2(svn_fs_t *fs,
190 svn_repos_history_func_t history_func,
192 svn_repos_authz_func_t authz_read_func,
193 void *authz_read_baton,
196 svn_boolean_t cross_copies,
199 svn_fs_history_t *history;
200 apr_pool_t *oldpool = svn_pool_create(pool);
201 apr_pool_t *newpool = svn_pool_create(pool);
202 const char *history_path;
203 svn_revnum_t history_rev;
206 /* Validate the revisions. */
207 if (! SVN_IS_VALID_REVNUM(start))
208 return svn_error_createf
209 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
210 _("Invalid start revision %ld"), start);
211 if (! SVN_IS_VALID_REVNUM(end))
212 return svn_error_createf
213 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
214 _("Invalid end revision %ld"), end);
216 /* Ensure that the input is ordered. */
219 svn_revnum_t tmprev = start;
224 /* Get a revision root for END, and an initial HISTORY baton. */
225 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
229 svn_boolean_t readable;
230 SVN_ERR(authz_read_func(&readable, root, path,
231 authz_read_baton, pool));
233 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
236 SVN_ERR(svn_fs_node_history(&history, root, path, oldpool));
238 /* Now, we loop over the history items, calling svn_fs_history_prev(). */
241 /* Note that we have to do some crazy pool work here. We can't
242 get rid of the old history until we use it to get the new, so
243 we alternate back and forth between our subpools. */
247 SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool));
249 /* Only continue if there is further history to deal with. */
253 /* Fetch the location information for this history step. */
254 SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
257 /* If this history item predates our START revision, quit
259 if (history_rev < start)
262 /* Is the history item readable? If not, quit. */
265 svn_boolean_t readable;
266 svn_fs_root_t *history_root;
267 SVN_ERR(svn_fs_revision_root(&history_root, fs,
268 history_rev, newpool));
269 SVN_ERR(authz_read_func(&readable, history_root, history_path,
270 authz_read_baton, newpool));
275 /* Call the user-provided callback function. */
276 err = history_func(history_baton, history_path, history_rev, newpool);
279 if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
281 svn_error_clear(err);
286 return svn_error_trace(err);
290 /* We're done with the old history item, so we can clear its
291 pool, and then toggle our notion of "the old pool". */
292 svn_pool_clear(oldpool);
297 while (history); /* shouldn't hit this */
300 svn_pool_destroy(oldpool);
301 svn_pool_destroy(newpool);
307 svn_repos_deleted_rev(svn_fs_t *fs,
311 svn_revnum_t *deleted,
315 svn_fs_root_t *root, *copy_root;
316 const char *copy_path;
317 svn_revnum_t mid_rev;
318 const svn_fs_id_t *start_node_id, *curr_node_id;
321 /* Validate the revision range. */
322 if (! SVN_IS_VALID_REVNUM(start))
323 return svn_error_createf
324 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
325 _("Invalid start revision %ld"), start);
326 if (! SVN_IS_VALID_REVNUM(end))
327 return svn_error_createf
328 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
329 _("Invalid end revision %ld"), end);
331 /* Ensure that the input is ordered. */
334 svn_revnum_t tmprev = start;
339 /* Ensure path exists in fs at start revision. */
340 SVN_ERR(svn_fs_revision_root(&root, fs, start, pool));
341 err = svn_fs_node_id(&start_node_id, root, path, pool);
344 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
346 /* Path must exist in fs at start rev. */
347 *deleted = SVN_INVALID_REVNUM;
348 svn_error_clear(err);
351 return svn_error_trace(err);
354 /* Ensure path was deleted at or before end revision. */
355 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
356 err = svn_fs_node_id(&curr_node_id, root, path, pool);
357 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
359 svn_error_clear(err);
363 return svn_error_trace(err);
367 /* path exists in the end node and the end node is equivalent
368 or otherwise equivalent to the start node. This can mean
371 1) The end node *is* simply the start node, uncopied
372 and unmodified in the start to end range.
374 2) The start node was modified, but never copied.
376 3) The start node was copied, but this copy occurred at
377 start or some rev *previous* to start, this is
378 effectively the same situation as 1 if the node was
379 never modified or 2 if it was.
381 In the first three cases the path was not deleted in
382 the specified range and we are done, but in the following
383 cases the start node must have been deleted at least once:
385 4) The start node was deleted and replaced by a copy of
386 itself at some rev between start and end. This copy
387 may itself have been replaced with copies of itself.
389 5) The start node was deleted and replaced by a node which
390 it does not share any history with.
392 SVN_ERR(svn_fs_node_id(&curr_node_id, root, path, pool));
393 if (svn_fs_compare_ids(start_node_id, curr_node_id) != -1)
395 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root,
398 (svn_fs_revision_root_revision(copy_root) <= start))
400 /* Case 1,2 or 3, nothing more to do. */
401 *deleted = SVN_INVALID_REVNUM;
407 /* If we get here we know that path exists in rev start and was deleted
408 at least once before rev end. To find the revision path was first
409 deleted we use a binary search. The rules for the determining if
410 the deletion comes before or after a given median revision are
411 described by this matrix:
413 | Most recent copy event that
414 | caused mid node to exist.
415 |-----------------------------------------------------
417 at start and | Copied at | Copied at | Never copied |
418 mid nodes. | rev > start | rev <= start | |
420 -------------------------------------------------------------------|
421 Mid node is | A) Start node | |
422 equivalent to | replaced with | E) Mid node == start node, |
423 start node | an unmodified | look HIGHER. |
427 -------------------------------------------------------------------|
428 Mid node is | B) Start node | |
429 otherwise | replaced with | F) Mid node is a modified |
430 related to | a modified | version of start node, |
431 start node | copy of | look HIGHER. |
434 -------------------------------------------------------------------|
436 unrelated to | C) Start node replaced with unrelated mid node, |
437 start node | look LOWER. |
439 -------------------------------------------------------------------|
441 exist at mid | D) Start node deleted before mid node, |
444 --------------------------------------------------------------------
447 mid_rev = (start + end) / 2;
448 subpool = svn_pool_create(pool);
452 svn_pool_clear(subpool);
454 /* Get revision root and node id for mid_rev at that revision. */
455 SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, subpool));
456 err = svn_fs_node_id(&curr_node_id, root, path, subpool);
460 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
462 /* Case D: Look lower in the range. */
463 svn_error_clear(err);
465 mid_rev = (start + mid_rev) / 2;
468 return svn_error_trace(err);
472 /* Determine the relationship between the start node
473 and the current node. */
474 int cmp = svn_fs_compare_ids(start_node_id, curr_node_id);
475 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root,
479 (svn_fs_revision_root_revision(copy_root) > start)))
481 /* Cases A, B, C: Look at lower revs. */
483 mid_rev = (start + mid_rev) / 2;
485 else if (end - mid_rev == 1)
487 /* Found the node path was deleted. */
493 /* Cases E, F: Look at higher revs. */
495 mid_rev = (start + end) / 2;
500 svn_pool_destroy(subpool);
505 /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
508 check_readability(svn_fs_root_t *root,
510 svn_repos_authz_func_t authz_read_func,
511 void *authz_read_baton,
514 svn_boolean_t readable;
515 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
517 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
518 _("Unreadable path encountered; access denied"));
523 /* The purpose of this function is to discover if fs_path@future_rev
524 * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */
527 check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
530 svn_revnum_t peg_revision,
531 svn_revnum_t future_revision,
535 svn_fs_history_t *history;
536 const char *path = NULL;
537 svn_revnum_t revision;
538 apr_pool_t *lastpool, *currpool;
540 lastpool = svn_pool_create(pool);
541 currpool = svn_pool_create(pool);
543 SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool));
545 SVN_ERR(svn_fs_node_history(&history, root, fs_path, lastpool));
547 /* Since paths that are different according to strcmp may still be
548 equivalent (due to number of consecutive slashes and the fact that
549 "" is the same as "/"), we get the "canonical" path in the first
550 iteration below so that the comparison after the loop will work
558 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool));
563 SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
566 fs_path = apr_pstrdup(pool, path);
568 if (revision <= peg_revision)
571 /* Clear old pool and flip. */
572 svn_pool_clear(lastpool);
578 /* We must have had at least one iteration above where we
579 reassigned fs_path. Else, the path wouldn't have existed at
580 future_revision and svn_fs_history would have thrown. */
581 SVN_ERR_ASSERT(fs_path != NULL);
583 *is_ancestor = (history && strcmp(path, fs_path) == 0);
590 svn_repos__prev_location(svn_revnum_t *appeared_rev,
591 const char **prev_path,
592 svn_revnum_t *prev_rev,
594 svn_revnum_t revision,
598 svn_fs_root_t *root, *copy_root;
599 const char *copy_path, *copy_src_path, *remainder;
600 svn_revnum_t copy_src_rev;
602 /* Initialize return variables. */
604 *appeared_rev = SVN_INVALID_REVNUM;
606 *prev_rev = SVN_INVALID_REVNUM;
610 /* Ask about the most recent copy which affected PATH@REVISION. If
611 there was no such copy, we're done. */
612 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
613 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, path, pool));
617 /* Ultimately, it's not the path of the closest copy's source that
618 we care about -- it's our own path's location in the copy source
619 revision. So we'll tack the relative path that expresses the
620 difference between the copy destination and our path in the copy
621 revision onto the copy source path to determine this information.
623 In other words, if our path is "/branches/my-branch/foo/bar", and
624 we know that the closest relevant copy was a copy of "/trunk" to
625 "/branches/my-branch", then that relative path under the copy
626 destination is "/foo/bar". Tacking that onto the copy source
627 path tells us that our path was located at "/trunk/foo/bar"
630 SVN_ERR(svn_fs_copied_from(©_src_rev, ©_src_path,
631 copy_root, copy_path, pool));
632 remainder = svn_fspath__skip_ancestor(copy_path, path);
634 *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
636 *appeared_rev = svn_fs_revision_root_revision(copy_root);
638 *prev_rev = copy_src_rev;
644 svn_repos_trace_node_locations(svn_fs_t *fs,
645 apr_hash_t **locations,
647 svn_revnum_t peg_revision,
648 const apr_array_header_t *location_revisions_orig,
649 svn_repos_authz_func_t authz_read_func,
650 void *authz_read_baton,
653 apr_array_header_t *location_revisions;
654 svn_revnum_t *revision_ptr, *revision_ptr_end;
657 svn_revnum_t revision;
658 svn_boolean_t is_ancestor;
659 apr_pool_t *lastpool, *currpool;
660 const svn_fs_id_t *id;
662 SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t));
664 /* Ensure that FS_PATH is absolute, because our path-math below will
665 depend on that being the case. */
667 fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL);
669 /* Another sanity check. */
672 svn_fs_root_t *peg_root;
673 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
674 SVN_ERR(check_readability(peg_root, fs_path,
675 authz_read_func, authz_read_baton, pool));
678 *locations = apr_hash_make(pool);
680 /* We flip between two pools in the second loop below. */
681 lastpool = svn_pool_create(pool);
682 currpool = svn_pool_create(pool);
684 /* First - let's sort the array of the revisions from the greatest revision
685 * downward, so it will be easier to search on. */
686 location_revisions = apr_array_copy(pool, location_revisions_orig);
687 qsort(location_revisions->elts, location_revisions->nelts,
688 sizeof(*revision_ptr), svn_sort_compare_revisions);
690 revision_ptr = (svn_revnum_t *)location_revisions->elts;
691 revision_ptr_end = revision_ptr + location_revisions->nelts;
693 /* Ignore revisions R that are younger than the peg_revisions where
694 path@peg_revision is not an ancestor of path@R. */
696 while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision)
698 svn_pool_clear(currpool);
699 SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path,
700 peg_revision, *revision_ptr,
707 revision = is_ancestor ? *revision_ptr : peg_revision;
711 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
712 SVN_ERR(check_readability(root, fs_path, authz_read_func,
713 authz_read_baton, pool));
716 while (revision_ptr < revision_ptr_end)
719 svn_revnum_t appeared_rev, prev_rev;
720 const char *prev_path;
722 /* Find the target of the innermost copy relevant to path@revision.
723 The copy may be of path itself, or of a parent directory. */
724 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
725 fs, revision, path, currpool));
731 svn_boolean_t readable;
732 svn_fs_root_t *tmp_root;
734 SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool));
735 SVN_ERR(authz_read_func(&readable, tmp_root, path,
736 authz_read_baton, currpool));
739 svn_pool_destroy(lastpool);
740 svn_pool_destroy(currpool);
746 /* Assign the current path to all younger revisions until we reach
747 the copy target rev. */
748 while ((revision_ptr < revision_ptr_end)
749 && (*revision_ptr >= appeared_rev))
751 /* *revision_ptr is allocated out of pool, so we can point
752 to in the hash table. */
753 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
754 apr_pstrdup(pool, path));
758 /* Ignore all revs between the copy target rev and the copy
759 source rev (non-inclusive). */
760 while ((revision_ptr < revision_ptr_end)
761 && (*revision_ptr > prev_rev))
768 /* Clear last pool and switch. */
769 svn_pool_clear(lastpool);
775 /* There are no copies relevant to path@revision. So any remaining
776 revisions either predate the creation of path@revision or have
777 the node existing at the same path. We will look up path@lrev
778 for each remaining location-revision and make sure it is related
780 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
781 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
782 while (revision_ptr < revision_ptr_end)
784 svn_node_kind_t kind;
785 const svn_fs_id_t *lrev_id;
787 svn_pool_clear(currpool);
788 SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool));
789 SVN_ERR(svn_fs_check_path(&kind, root, path, currpool));
790 if (kind == svn_node_none)
792 SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
793 if (! svn_fs_check_related(id, lrev_id))
796 /* The node exists at the same path; record that and advance. */
797 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
798 apr_pstrdup(pool, path));
802 /* Ignore any remaining location-revisions; they predate the
803 creation of path@revision. */
805 svn_pool_destroy(lastpool);
806 svn_pool_destroy(currpool);
812 /* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
813 its revision range fits between END_REV and START_REV, possibly
814 cropping the range so that it fits *entirely* in that range. */
816 maybe_crop_and_send_segment(svn_location_segment_t *segment,
817 svn_revnum_t start_rev,
818 svn_revnum_t end_rev,
819 svn_location_segment_receiver_t receiver,
820 void *receiver_baton,
823 /* We only want to transmit this segment if some portion of it
824 is between our END_REV and START_REV. */
825 if (! ((segment->range_start > start_rev)
826 || (segment->range_end < end_rev)))
828 /* Correct our segment range when the range straddles one of
829 our requested revision boundaries. */
830 if (segment->range_start < end_rev)
831 segment->range_start = end_rev;
832 if (segment->range_end > start_rev)
833 segment->range_end = start_rev;
834 SVN_ERR(receiver(segment, receiver_baton, pool));
841 svn_repos_node_location_segments(svn_repos_t *repos,
843 svn_revnum_t peg_revision,
844 svn_revnum_t start_rev,
845 svn_revnum_t end_rev,
846 svn_location_segment_receiver_t receiver,
847 void *receiver_baton,
848 svn_repos_authz_func_t authz_read_func,
849 void *authz_read_baton,
852 svn_fs_t *fs = svn_repos_fs(repos);
853 svn_stringbuf_t *current_path;
854 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
857 /* No PEG_REVISION? We'll use HEAD. */
858 if (! SVN_IS_VALID_REVNUM(peg_revision))
860 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
861 peg_revision = youngest_rev;
864 /* No START_REV? We'll use HEAD (which we may have already fetched). */
865 if (! SVN_IS_VALID_REVNUM(start_rev))
867 if (SVN_IS_VALID_REVNUM(youngest_rev))
868 start_rev = youngest_rev;
870 SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
873 /* No END_REV? We'll use 0. */
874 end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
876 /* Are the revision properly ordered? They better be -- the API
878 SVN_ERR_ASSERT(end_rev <= start_rev);
879 SVN_ERR_ASSERT(start_rev <= peg_revision);
881 /* Ensure that PATH is absolute, because our path-math will depend
882 on that being the case. */
884 path = apr_pstrcat(pool, "/", path, (char *)NULL);
889 svn_fs_root_t *peg_root;
890 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
891 SVN_ERR(check_readability(peg_root, path,
892 authz_read_func, authz_read_baton, pool));
895 /* Okay, let's get searching! */
896 subpool = svn_pool_create(pool);
897 current_rev = peg_revision;
898 current_path = svn_stringbuf_create(path, pool);
899 while (current_rev >= end_rev)
901 svn_revnum_t appeared_rev, prev_rev;
902 const char *cur_path, *prev_path;
903 svn_location_segment_t *segment;
905 svn_pool_clear(subpool);
907 cur_path = apr_pstrmemdup(subpool, current_path->data,
909 segment = apr_pcalloc(subpool, sizeof(*segment));
910 segment->range_end = current_rev;
911 segment->range_start = end_rev;
912 /* segment path should be absolute without leading '/'. */
913 segment->path = cur_path + 1;
915 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
916 fs, current_rev, cur_path, subpool));
918 /* If there are no previous locations for this thing (meaning,
919 it originated at the current path), then we simply need to
920 find its revision of origin to populate our final segment.
921 Otherwise, the APPEARED_REV is the start of current segment's
925 svn_fs_root_t *revroot;
926 SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
927 SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
929 if (segment->range_start < end_rev)
930 segment->range_start = end_rev;
931 current_rev = SVN_INVALID_REVNUM;
935 segment->range_start = appeared_rev;
936 svn_stringbuf_set(current_path, prev_path);
937 current_rev = prev_rev;
940 /* Report our segment, providing it passes authz muster. */
943 svn_boolean_t readable;
944 svn_fs_root_t *cur_rev_root;
946 /* authz_read_func requires path to have a leading slash. */
947 const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
950 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
951 segment->range_end, subpool));
952 SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
953 authz_read_baton, subpool));
958 /* Transmit the segment (if it's within the scope of our concern). */
959 SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
960 receiver, receiver_baton, subpool));
962 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
963 (and didn't ever reach END_REV). */
964 if (! SVN_IS_VALID_REVNUM(current_rev))
967 /* If there's a gap in the history, we need to report as much
968 (if the gap is within the scope of our concern). */
969 if (segment->range_start - current_rev > 1)
971 svn_location_segment_t *gap_segment;
972 gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
973 gap_segment->range_end = segment->range_start - 1;
974 gap_segment->range_start = current_rev + 1;
975 gap_segment->path = NULL;
976 SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
977 receiver, receiver_baton,
981 svn_pool_destroy(subpool);
985 /* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
987 get_path_mergeinfo(apr_hash_t **mergeinfo,
991 apr_pool_t *result_pool,
992 apr_pool_t *scratch_pool)
994 svn_mergeinfo_catalog_t tmp_catalog;
996 apr_array_header_t *paths = apr_array_make(scratch_pool, 1,
997 sizeof(const char *));
999 APR_ARRAY_PUSH(paths, const char *) = path;
1001 SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool));
1002 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1003 because we will filter out unreadable revisions in
1004 find_interesting_revision(), above */
1005 SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths,
1006 svn_mergeinfo_inherited, FALSE, TRUE,
1007 result_pool, scratch_pool));
1009 *mergeinfo = svn_hash_gets(tmp_catalog, path);
1011 *mergeinfo = apr_hash_make(result_pool);
1013 return SVN_NO_ERROR;
1016 static APR_INLINE svn_boolean_t
1017 is_path_in_hash(apr_hash_t *duplicate_path_revs,
1019 svn_revnum_t revision,
1022 const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
1025 ptr = svn_hash_gets(duplicate_path_revs, key);
1029 struct path_revision
1031 svn_revnum_t revnum;
1034 /* Does this path_rev have merges to also be included? */
1035 apr_hash_t *merged_mergeinfo;
1037 /* Is this a merged revision? */
1038 svn_boolean_t merged;
1041 /* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store
1042 the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The
1043 returned *MERGED_MERGEINFO will be NULL if there are no changes. */
1044 static svn_error_t *
1045 get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
1047 struct path_revision *old_path_rev,
1048 apr_pool_t *result_pool,
1049 apr_pool_t *scratch_pool)
1051 apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1053 svn_fs_root_t *root;
1054 apr_hash_t *changed_paths;
1055 const char *path = old_path_rev->path;
1057 /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
1058 if there is a property change. */
1059 SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
1061 SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
1064 svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
1065 if (changed_path && changed_path->prop_mod)
1067 if (svn_fspath__is_root(path, strlen(path)))
1069 *merged_mergeinfo = NULL;
1070 return SVN_NO_ERROR;
1072 path = svn_fspath__dirname(path, scratch_pool);
1075 /* First, find the mergeinfo difference for old_path_rev->revnum, and
1076 old_path_rev->revnum - 1. */
1077 err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path,
1078 old_path_rev->revnum, scratch_pool,
1082 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1084 /* Issue #3896: If invalid mergeinfo is encountered the
1085 best we can do is ignore it and act is if there are
1086 no mergeinfo differences. */
1087 svn_error_clear(err);
1088 *merged_mergeinfo = NULL;
1089 return SVN_NO_ERROR;
1093 return svn_error_trace(err);
1097 err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path,
1098 old_path_rev->revnum - 1, scratch_pool,
1100 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
1101 || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
1103 /* If the path doesn't exist in the previous revision or it does exist
1104 but has invalid mergeinfo (Issue #3896), assume no merges. */
1105 svn_error_clear(err);
1106 *merged_mergeinfo = NULL;
1107 return SVN_NO_ERROR;
1112 /* Then calculate and merge the differences. */
1113 SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
1114 curr_mergeinfo, FALSE, result_pool,
1116 SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
1118 /* Store the result. */
1119 if (apr_hash_count(changed))
1120 *merged_mergeinfo = changed;
1122 *merged_mergeinfo = NULL;
1124 return SVN_NO_ERROR;
1127 static svn_error_t *
1128 find_interesting_revisions(apr_array_header_t *path_revisions,
1133 svn_boolean_t include_merged_revisions,
1134 svn_boolean_t mark_as_merged,
1135 apr_hash_t *duplicate_path_revs,
1136 svn_repos_authz_func_t authz_read_func,
1137 void *authz_read_baton,
1138 apr_pool_t *result_pool,
1139 apr_pool_t *scratch_pool)
1141 apr_pool_t *iterpool, *last_pool;
1142 svn_fs_history_t *history;
1143 svn_fs_root_t *root;
1144 svn_node_kind_t kind;
1146 /* We switch between two pools while looping, since we need information from
1147 the last iteration to be available. */
1148 iterpool = svn_pool_create(scratch_pool);
1149 last_pool = svn_pool_create(scratch_pool);
1151 /* The path had better be a file in this revision. */
1152 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1153 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1154 if (kind != svn_node_file)
1155 return svn_error_createf
1156 (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
1159 /* Open a history object. */
1160 SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1163 struct path_revision *path_rev;
1164 svn_revnum_t tmp_revnum;
1165 const char *tmp_path;
1166 apr_pool_t *tmp_pool;
1168 svn_pool_clear(iterpool);
1170 /* Fetch the history object to walk through. */
1171 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1174 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1175 history, iterpool));
1177 /* Check to see if we already saw this path (and it's ancestors) */
1178 if (include_merged_revisions
1179 && is_path_in_hash(duplicate_path_revs, tmp_path,
1180 tmp_revnum, iterpool))
1183 /* Check authorization. */
1184 if (authz_read_func)
1186 svn_boolean_t readable;
1187 svn_fs_root_t *tmp_root;
1189 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1191 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1192 authz_read_baton, iterpool));
1197 /* We didn't break, so we must really want this path-rev. */
1198 path_rev = apr_palloc(result_pool, sizeof(*path_rev));
1199 path_rev->path = apr_pstrdup(result_pool, tmp_path);
1200 path_rev->revnum = tmp_revnum;
1201 path_rev->merged = mark_as_merged;
1202 APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
1204 if (include_merged_revisions)
1205 SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
1206 path_rev, result_pool, iterpool));
1208 path_rev->merged_mergeinfo = NULL;
1210 /* Add the path/rev pair to the hash, so we can filter out future
1211 occurrences of it. We only care about this if including merged
1212 revisions, 'cause that's the only time we can have duplicates. */
1213 svn_hash_sets(duplicate_path_revs,
1214 apr_psprintf(result_pool, "%s:%ld", path_rev->path,
1216 (void *)0xdeadbeef);
1218 if (path_rev->revnum <= start)
1222 tmp_pool = iterpool;
1223 iterpool = last_pool;
1224 last_pool = tmp_pool;
1227 svn_pool_destroy(iterpool);
1228 svn_pool_destroy(last_pool);
1230 return SVN_NO_ERROR;
1233 /* Comparison function to sort path/revisions in increasing order */
1235 compare_path_revisions(const void *a, const void *b)
1237 struct path_revision *a_pr = *(struct path_revision *const *)a;
1238 struct path_revision *b_pr = *(struct path_revision *const *)b;
1240 if (a_pr->revnum == b_pr->revnum)
1243 return a_pr->revnum < b_pr->revnum ? 1 : -1;
1246 static svn_error_t *
1247 find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
1249 const apr_array_header_t *mainline_path_revisions,
1251 apr_hash_t *duplicate_path_revs,
1252 svn_repos_authz_func_t authz_read_func,
1253 void *authz_read_baton,
1254 apr_pool_t *result_pool,
1255 apr_pool_t *scratch_pool)
1257 const apr_array_header_t *old;
1258 apr_array_header_t *new_merged_path_revs;
1259 apr_pool_t *iterpool, *last_pool;
1260 apr_array_header_t *merged_path_revisions =
1261 apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
1263 old = mainline_path_revisions;
1264 iterpool = svn_pool_create(scratch_pool);
1265 last_pool = svn_pool_create(scratch_pool);
1270 apr_pool_t *temp_pool;
1272 svn_pool_clear(iterpool);
1273 new_merged_path_revs = apr_array_make(iterpool, 0,
1274 sizeof(struct path_revision *));
1276 /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather
1277 path_revisions for any merged revisions, and store those in NEW. */
1278 for (i = 0; i < old->nelts; i++)
1280 apr_pool_t *iterpool2;
1281 apr_hash_index_t *hi;
1282 struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
1283 struct path_revision *);
1284 if (!old_pr->merged_mergeinfo)
1287 iterpool2 = svn_pool_create(iterpool);
1289 /* Determine and trace the merge sources. */
1290 for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
1291 hi = apr_hash_next(hi))
1293 apr_pool_t *iterpool3;
1294 svn_rangelist_t *rangelist;
1298 svn_pool_clear(iterpool2);
1299 iterpool3 = svn_pool_create(iterpool2);
1301 apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist);
1303 for (j = 0; j < rangelist->nelts; j++)
1305 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
1306 svn_merge_range_t *);
1307 svn_node_kind_t kind;
1308 svn_fs_root_t *root;
1310 if (range->end < start)
1313 svn_pool_clear(iterpool3);
1315 SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
1317 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
1318 if (kind != svn_node_file)
1321 /* Search and find revisions to add to the NEW list. */
1322 SVN_ERR(find_interesting_revisions(new_merged_path_revs,
1324 range->start, range->end,
1326 duplicate_path_revs,
1329 result_pool, iterpool3));
1331 svn_pool_destroy(iterpool3);
1333 svn_pool_destroy(iterpool2);
1336 /* Append the newly found path revisions with the old ones. */
1337 merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
1338 new_merged_path_revs);
1340 /* Swap data structures */
1341 old = new_merged_path_revs;
1342 temp_pool = last_pool;
1343 last_pool = iterpool;
1344 iterpool = temp_pool;
1346 while (new_merged_path_revs->nelts > 0);
1348 /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1349 qsort(merged_path_revisions->elts, merged_path_revisions->nelts,
1350 sizeof(struct path_revision *), compare_path_revisions);
1352 /* Copy to the output array. */
1353 *merged_path_revisions_out = apr_array_copy(result_pool,
1354 merged_path_revisions);
1356 svn_pool_destroy(iterpool);
1357 svn_pool_destroy(last_pool);
1359 return SVN_NO_ERROR;
1364 apr_pool_t *iterpool;
1365 apr_pool_t *last_pool;
1366 apr_hash_t *last_props;
1367 const char *last_path;
1368 svn_fs_root_t *last_root;
1371 /* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1373 static svn_error_t *
1374 send_path_revision(struct path_revision *path_rev,
1376 struct send_baton *sb,
1377 svn_file_rev_handler_t handler,
1378 void *handler_baton)
1380 apr_hash_t *rev_props;
1382 apr_array_header_t *prop_diffs;
1383 svn_fs_root_t *root;
1384 svn_txdelta_stream_t *delta_stream;
1385 svn_txdelta_window_handler_t delta_handler = NULL;
1386 void *delta_baton = NULL;
1387 apr_pool_t *tmp_pool; /* For swapping */
1388 svn_boolean_t contents_changed;
1390 svn_pool_clear(sb->iterpool);
1392 /* Get the revision properties. */
1393 SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
1394 path_rev->revnum, sb->iterpool));
1396 /* Open the revision root. */
1397 SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
1400 /* Get the file's properties for this revision and compute the diffs. */
1401 SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
1403 SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1406 /* Check if the contents changed. */
1407 /* Special case: In the first revision, we always provide a delta. */
1409 SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root,
1410 sb->last_path, root, path_rev->path,
1413 contents_changed = TRUE;
1415 /* We have all we need, give to the handler. */
1416 SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
1417 rev_props, path_rev->merged,
1418 contents_changed ? &delta_handler : NULL,
1419 contents_changed ? &delta_baton : NULL,
1420 prop_diffs, sb->iterpool));
1422 /* Compute and send delta if client asked for it.
1423 Note that this was initialized to NULL, so if !contents_changed,
1424 no deltas will be computed. */
1427 /* Get the content delta. */
1428 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
1429 sb->last_root, sb->last_path,
1430 root, path_rev->path,
1433 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1434 delta_handler, delta_baton,
1438 /* Remember root, path and props for next iteration. */
1439 sb->last_root = root;
1440 sb->last_path = path_rev->path;
1441 sb->last_props = props;
1443 /* Swap the pools. */
1444 tmp_pool = sb->iterpool;
1445 sb->iterpool = sb->last_pool;
1446 sb->last_pool = tmp_pool;
1448 return SVN_NO_ERROR;
1451 /* Similar to svn_repos_get_file_revs2() but returns paths while walking
1452 history instead of after collecting all history.
1454 This allows implementing clients to immediately start processing and
1455 stop when they got the information they need. (E.g. all or a specific set
1456 of lines were modified) */
1457 static svn_error_t *
1458 get_file_revs_backwards(svn_repos_t *repos,
1462 svn_repos_authz_func_t authz_read_func,
1463 void *authz_read_baton,
1464 svn_file_rev_handler_t handler,
1465 void *handler_baton,
1466 apr_pool_t *scratch_pool)
1468 apr_pool_t *iterpool, *last_pool;
1469 svn_fs_history_t *history;
1470 svn_fs_root_t *root;
1471 svn_node_kind_t kind;
1472 struct send_baton sb;
1474 /* We switch between two pools while looping and so does the path-rev
1475 handler for actually reported revisions. We do this as we
1476 need just information from last iteration to be available. */
1478 iterpool = svn_pool_create(scratch_pool);
1479 last_pool = svn_pool_create(scratch_pool);
1480 sb.iterpool = svn_pool_create(scratch_pool);
1481 sb.last_pool = svn_pool_create(scratch_pool);
1483 /* We want the first txdelta to be against the empty file. */
1484 sb.last_root = NULL;
1485 sb.last_path = NULL;
1487 /* Create an empty hash table for the first property diff. */
1488 sb.last_props = apr_hash_make(sb.last_pool);
1490 /* The path had better be a file in this revision. */
1491 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1492 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1493 if (kind != svn_node_file)
1494 return svn_error_createf(SVN_ERR_FS_NOT_FILE,
1495 NULL, _("'%s' is not a file in revision %ld"),
1498 /* Open a history object. */
1499 SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1502 struct path_revision *path_rev;
1503 svn_revnum_t tmp_revnum;
1504 const char *tmp_path;
1506 svn_pool_clear(iterpool);
1508 /* Fetch the history object to walk through. */
1509 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1512 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1513 history, iterpool));
1515 /* Check authorization. */
1516 if (authz_read_func)
1518 svn_boolean_t readable;
1519 svn_fs_root_t *tmp_root;
1521 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1523 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1524 authz_read_baton, iterpool));
1529 /* We didn't break, so we must really want this path-rev. */
1530 path_rev = apr_palloc(iterpool, sizeof(*path_rev));
1531 path_rev->path = tmp_path;
1532 path_rev->revnum = tmp_revnum;
1533 path_rev->merged = FALSE;
1535 SVN_ERR(send_path_revision(path_rev, repos, &sb,
1536 handler, handler_baton));
1538 if (path_rev->revnum <= start)
1543 apr_pool_t *tmp_pool = iterpool;
1544 iterpool = last_pool;
1545 last_pool = tmp_pool;
1549 svn_pool_destroy(iterpool);
1550 svn_pool_destroy(last_pool);
1551 svn_pool_destroy(sb.last_pool);
1552 svn_pool_destroy(sb.iterpool);
1554 return SVN_NO_ERROR;
1559 /* We don't yet support sending revisions in reverse order; the caller wait
1560 * until we've traced back through the entire history, and then accept
1561 * them from oldest to youngest. Someday this may change, but in the meantime,
1562 * the general algorithm is thus:
1564 * 1) Trace back through the history of an object, adding each revision
1565 * found to the MAINLINE_PATH_REVISIONS array, marking any which were
1567 * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1568 * merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1569 * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1571 * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1572 * oldest to youngest, interleaving as appropriate. This is implemented
1573 * similar to an insertion sort, but instead of inserting into another
1574 * array, we just call the appropriate handler.
1576 * 2013-02: Added a very simple reverse for mainline only changes. Before this,
1577 * this would return an error (path not found) or just the first
1578 * revision before end.
1581 svn_repos_get_file_revs2(svn_repos_t *repos,
1585 svn_boolean_t include_merged_revisions,
1586 svn_repos_authz_func_t authz_read_func,
1587 void *authz_read_baton,
1588 svn_file_rev_handler_t handler,
1589 void *handler_baton,
1590 apr_pool_t *scratch_pool)
1592 apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
1593 apr_hash_t *duplicate_path_revs;
1594 struct send_baton sb;
1595 int mainline_pos, merged_pos;
1599 if (include_merged_revisions)
1600 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
1602 return svn_error_trace(
1603 get_file_revs_backwards(repos, path,
1612 /* We switch between two pools while looping, since we need information from
1613 the last iteration to be available. */
1614 sb.iterpool = svn_pool_create(scratch_pool);
1615 sb.last_pool = svn_pool_create(scratch_pool);
1617 /* We want the first txdelta to be against the empty file. */
1618 sb.last_root = NULL;
1619 sb.last_path = NULL;
1621 /* Create an empty hash table for the first property diff. */
1622 sb.last_props = apr_hash_make(sb.last_pool);
1625 /* Get the revisions we are interested in. */
1626 duplicate_path_revs = apr_hash_make(scratch_pool);
1627 mainline_path_revisions = apr_array_make(scratch_pool, 100,
1628 sizeof(struct path_revision *));
1629 SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
1630 start, end, include_merged_revisions,
1631 FALSE, duplicate_path_revs,
1632 authz_read_func, authz_read_baton,
1633 scratch_pool, sb.iterpool));
1635 /* If we are including merged revisions, go get those, too. */
1636 if (include_merged_revisions)
1637 SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
1638 mainline_path_revisions, repos,
1639 duplicate_path_revs, authz_read_func,
1641 scratch_pool, sb.iterpool));
1643 merged_path_revisions = apr_array_make(scratch_pool, 0,
1644 sizeof(struct path_revision *));
1646 /* We must have at least one revision to get. */
1647 SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
1649 /* Walk through both mainline and merged revisions, and send them in
1650 reverse chronological order, interleaving as appropriate. */
1651 mainline_pos = mainline_path_revisions->nelts - 1;
1652 merged_pos = merged_path_revisions->nelts - 1;
1653 while (mainline_pos >= 0 && merged_pos >= 0)
1655 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1657 struct path_revision *);
1658 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1660 struct path_revision *);
1662 if (main_pr->revnum <= merged_pr->revnum)
1664 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1670 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1676 /* Send any remaining revisions from the mainline list. */
1677 for (; mainline_pos >= 0; mainline_pos -= 1)
1679 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1681 struct path_revision *);
1682 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
1685 /* Ditto for the merged list. */
1686 for (; merged_pos >= 0; merged_pos -= 1)
1688 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1690 struct path_revision *);
1691 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1695 svn_pool_destroy(sb.last_pool);
1696 svn_pool_destroy(sb.iterpool);
1698 return SVN_NO_ERROR;