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));
729 /* Assign the current path to all younger revisions until we reach
730 the copy target rev. */
731 while ((revision_ptr < revision_ptr_end)
732 && (*revision_ptr >= appeared_rev))
734 /* *revision_ptr is allocated out of pool, so we can point
735 to in the hash table. */
736 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
737 apr_pstrdup(pool, path));
741 /* Ignore all revs between the copy target rev and the copy
742 source rev (non-inclusive). */
743 while ((revision_ptr < revision_ptr_end)
744 && (*revision_ptr > prev_rev))
753 svn_boolean_t readable;
754 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
755 SVN_ERR(authz_read_func(&readable, root, path,
756 authz_read_baton, currpool));
759 svn_pool_destroy(lastpool);
760 svn_pool_destroy(currpool);
765 /* Clear last pool and switch. */
766 svn_pool_clear(lastpool);
772 /* There are no copies relevant to path@revision. So any remaining
773 revisions either predate the creation of path@revision or have
774 the node existing at the same path. We will look up path@lrev
775 for each remaining location-revision and make sure it is related
777 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
778 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
779 while (revision_ptr < revision_ptr_end)
781 svn_node_kind_t kind;
782 const svn_fs_id_t *lrev_id;
784 svn_pool_clear(currpool);
785 SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool));
786 SVN_ERR(svn_fs_check_path(&kind, root, path, currpool));
787 if (kind == svn_node_none)
789 SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
790 if (! svn_fs_check_related(id, lrev_id))
793 /* The node exists at the same path; record that and advance. */
794 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
795 apr_pstrdup(pool, path));
799 /* Ignore any remaining location-revisions; they predate the
800 creation of path@revision. */
802 svn_pool_destroy(lastpool);
803 svn_pool_destroy(currpool);
809 /* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
810 its revision range fits between END_REV and START_REV, possibly
811 cropping the range so that it fits *entirely* in that range. */
813 maybe_crop_and_send_segment(svn_location_segment_t *segment,
814 svn_revnum_t start_rev,
815 svn_revnum_t end_rev,
816 svn_location_segment_receiver_t receiver,
817 void *receiver_baton,
820 /* We only want to transmit this segment if some portion of it
821 is between our END_REV and START_REV. */
822 if (! ((segment->range_start > start_rev)
823 || (segment->range_end < end_rev)))
825 /* Correct our segment range when the range straddles one of
826 our requested revision boundaries. */
827 if (segment->range_start < end_rev)
828 segment->range_start = end_rev;
829 if (segment->range_end > start_rev)
830 segment->range_end = start_rev;
831 SVN_ERR(receiver(segment, receiver_baton, pool));
838 svn_repos_node_location_segments(svn_repos_t *repos,
840 svn_revnum_t peg_revision,
841 svn_revnum_t start_rev,
842 svn_revnum_t end_rev,
843 svn_location_segment_receiver_t receiver,
844 void *receiver_baton,
845 svn_repos_authz_func_t authz_read_func,
846 void *authz_read_baton,
849 svn_fs_t *fs = svn_repos_fs(repos);
850 svn_stringbuf_t *current_path;
851 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
854 /* No PEG_REVISION? We'll use HEAD. */
855 if (! SVN_IS_VALID_REVNUM(peg_revision))
857 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
858 peg_revision = youngest_rev;
861 /* No START_REV? We'll use HEAD (which we may have already fetched). */
862 if (! SVN_IS_VALID_REVNUM(start_rev))
864 if (SVN_IS_VALID_REVNUM(youngest_rev))
865 start_rev = youngest_rev;
867 SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
870 /* No END_REV? We'll use 0. */
871 end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
873 /* Are the revision properly ordered? They better be -- the API
875 SVN_ERR_ASSERT(end_rev <= start_rev);
876 SVN_ERR_ASSERT(start_rev <= peg_revision);
878 /* Ensure that PATH is absolute, because our path-math will depend
879 on that being the case. */
881 path = apr_pstrcat(pool, "/", path, (char *)NULL);
886 svn_fs_root_t *peg_root;
887 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
888 SVN_ERR(check_readability(peg_root, path,
889 authz_read_func, authz_read_baton, pool));
892 /* Okay, let's get searching! */
893 subpool = svn_pool_create(pool);
894 current_rev = peg_revision;
895 current_path = svn_stringbuf_create(path, pool);
896 while (current_rev >= end_rev)
898 svn_revnum_t appeared_rev, prev_rev;
899 const char *cur_path, *prev_path;
900 svn_location_segment_t *segment;
902 svn_pool_clear(subpool);
904 cur_path = apr_pstrmemdup(subpool, current_path->data,
906 segment = apr_pcalloc(subpool, sizeof(*segment));
907 segment->range_end = current_rev;
908 segment->range_start = end_rev;
909 /* segment path should be absolute without leading '/'. */
910 segment->path = cur_path + 1;
912 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
913 fs, current_rev, cur_path, subpool));
915 /* If there are no previous locations for this thing (meaning,
916 it originated at the current path), then we simply need to
917 find its revision of origin to populate our final segment.
918 Otherwise, the APPEARED_REV is the start of current segment's
922 svn_fs_root_t *revroot;
923 SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
924 SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
926 if (segment->range_start < end_rev)
927 segment->range_start = end_rev;
928 current_rev = SVN_INVALID_REVNUM;
932 segment->range_start = appeared_rev;
933 svn_stringbuf_set(current_path, prev_path);
934 current_rev = prev_rev;
937 /* Report our segment, providing it passes authz muster. */
940 svn_boolean_t readable;
941 svn_fs_root_t *cur_rev_root;
943 /* authz_read_func requires path to have a leading slash. */
944 const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
947 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
948 segment->range_end, subpool));
949 SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
950 authz_read_baton, subpool));
955 /* Transmit the segment (if it's within the scope of our concern). */
956 SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
957 receiver, receiver_baton, subpool));
959 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
960 (and didn't ever reach END_REV). */
961 if (! SVN_IS_VALID_REVNUM(current_rev))
964 /* If there's a gap in the history, we need to report as much
965 (if the gap is within the scope of our concern). */
966 if (segment->range_start - current_rev > 1)
968 svn_location_segment_t *gap_segment;
969 gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
970 gap_segment->range_end = segment->range_start - 1;
971 gap_segment->range_start = current_rev + 1;
972 gap_segment->path = NULL;
973 SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
974 receiver, receiver_baton,
978 svn_pool_destroy(subpool);
982 /* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
984 get_path_mergeinfo(apr_hash_t **mergeinfo,
988 apr_pool_t *result_pool,
989 apr_pool_t *scratch_pool)
991 svn_mergeinfo_catalog_t tmp_catalog;
993 apr_array_header_t *paths = apr_array_make(scratch_pool, 1,
994 sizeof(const char *));
996 APR_ARRAY_PUSH(paths, const char *) = path;
998 SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool));
999 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1000 because we will filter out unreadable revisions in
1001 find_interesting_revision(), above */
1002 SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths,
1003 svn_mergeinfo_inherited, FALSE, TRUE,
1004 result_pool, scratch_pool));
1006 *mergeinfo = svn_hash_gets(tmp_catalog, path);
1008 *mergeinfo = apr_hash_make(result_pool);
1010 return SVN_NO_ERROR;
1013 static APR_INLINE svn_boolean_t
1014 is_path_in_hash(apr_hash_t *duplicate_path_revs,
1016 svn_revnum_t revision,
1019 const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
1022 ptr = svn_hash_gets(duplicate_path_revs, key);
1026 struct path_revision
1028 svn_revnum_t revnum;
1031 /* Does this path_rev have merges to also be included? */
1032 apr_hash_t *merged_mergeinfo;
1034 /* Is this a merged revision? */
1035 svn_boolean_t merged;
1038 /* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store
1039 the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The
1040 returned *MERGED_MERGEINFO will be NULL if there are no changes. */
1041 static svn_error_t *
1042 get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
1044 struct path_revision *old_path_rev,
1045 apr_pool_t *result_pool,
1046 apr_pool_t *scratch_pool)
1048 apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1050 svn_fs_root_t *root;
1051 apr_hash_t *changed_paths;
1052 const char *path = old_path_rev->path;
1054 /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
1055 if there is a property change. */
1056 SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
1058 SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
1061 svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
1062 if (changed_path && changed_path->prop_mod)
1064 if (svn_fspath__is_root(path, strlen(path)))
1066 *merged_mergeinfo = NULL;
1067 return SVN_NO_ERROR;
1069 path = svn_fspath__dirname(path, scratch_pool);
1072 /* First, find the mergeinfo difference for old_path_rev->revnum, and
1073 old_path_rev->revnum - 1. */
1074 err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path,
1075 old_path_rev->revnum, scratch_pool,
1079 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1081 /* Issue #3896: If invalid mergeinfo is encountered the
1082 best we can do is ignore it and act is if there are
1083 no mergeinfo differences. */
1084 svn_error_clear(err);
1085 *merged_mergeinfo = NULL;
1086 return SVN_NO_ERROR;
1090 return svn_error_trace(err);
1094 err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path,
1095 old_path_rev->revnum - 1, scratch_pool,
1097 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
1098 || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
1100 /* If the path doesn't exist in the previous revision or it does exist
1101 but has invalid mergeinfo (Issue #3896), assume no merges. */
1102 svn_error_clear(err);
1103 *merged_mergeinfo = NULL;
1104 return SVN_NO_ERROR;
1109 /* Then calculate and merge the differences. */
1110 SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
1111 curr_mergeinfo, FALSE, result_pool,
1113 SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
1115 /* Store the result. */
1116 if (apr_hash_count(changed))
1117 *merged_mergeinfo = changed;
1119 *merged_mergeinfo = NULL;
1121 return SVN_NO_ERROR;
1124 static svn_error_t *
1125 find_interesting_revisions(apr_array_header_t *path_revisions,
1130 svn_boolean_t include_merged_revisions,
1131 svn_boolean_t mark_as_merged,
1132 apr_hash_t *duplicate_path_revs,
1133 svn_repos_authz_func_t authz_read_func,
1134 void *authz_read_baton,
1135 apr_pool_t *result_pool,
1136 apr_pool_t *scratch_pool)
1138 apr_pool_t *iterpool, *last_pool;
1139 svn_fs_history_t *history;
1140 svn_fs_root_t *root;
1141 svn_node_kind_t kind;
1143 /* We switch between two pools while looping, since we need information from
1144 the last iteration to be available. */
1145 iterpool = svn_pool_create(scratch_pool);
1146 last_pool = svn_pool_create(scratch_pool);
1148 /* The path had better be a file in this revision. */
1149 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1150 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1151 if (kind != svn_node_file)
1152 return svn_error_createf
1153 (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
1156 /* Open a history object. */
1157 SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1160 struct path_revision *path_rev;
1161 svn_revnum_t tmp_revnum;
1162 const char *tmp_path;
1163 apr_pool_t *tmp_pool;
1165 svn_pool_clear(iterpool);
1167 /* Fetch the history object to walk through. */
1168 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1171 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1172 history, iterpool));
1174 /* Check to see if we already saw this path (and it's ancestors) */
1175 if (include_merged_revisions
1176 && is_path_in_hash(duplicate_path_revs, tmp_path,
1177 tmp_revnum, iterpool))
1180 /* Check authorization. */
1181 if (authz_read_func)
1183 svn_boolean_t readable;
1184 svn_fs_root_t *tmp_root;
1186 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1188 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1189 authz_read_baton, iterpool));
1194 /* We didn't break, so we must really want this path-rev. */
1195 path_rev = apr_palloc(result_pool, sizeof(*path_rev));
1196 path_rev->path = apr_pstrdup(result_pool, tmp_path);
1197 path_rev->revnum = tmp_revnum;
1198 path_rev->merged = mark_as_merged;
1199 APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
1201 if (include_merged_revisions)
1202 SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
1203 path_rev, result_pool, iterpool));
1205 path_rev->merged_mergeinfo = NULL;
1207 /* Add the path/rev pair to the hash, so we can filter out future
1208 occurrences of it. We only care about this if including merged
1209 revisions, 'cause that's the only time we can have duplicates. */
1210 svn_hash_sets(duplicate_path_revs,
1211 apr_psprintf(result_pool, "%s:%ld", path_rev->path,
1213 (void *)0xdeadbeef);
1215 if (path_rev->revnum <= start)
1219 tmp_pool = iterpool;
1220 iterpool = last_pool;
1221 last_pool = tmp_pool;
1224 svn_pool_destroy(iterpool);
1225 svn_pool_destroy(last_pool);
1227 return SVN_NO_ERROR;
1230 /* Comparison function to sort path/revisions in increasing order */
1232 compare_path_revisions(const void *a, const void *b)
1234 struct path_revision *a_pr = *(struct path_revision *const *)a;
1235 struct path_revision *b_pr = *(struct path_revision *const *)b;
1237 if (a_pr->revnum == b_pr->revnum)
1240 return a_pr->revnum < b_pr->revnum ? 1 : -1;
1243 static svn_error_t *
1244 find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
1246 const apr_array_header_t *mainline_path_revisions,
1248 apr_hash_t *duplicate_path_revs,
1249 svn_repos_authz_func_t authz_read_func,
1250 void *authz_read_baton,
1251 apr_pool_t *result_pool,
1252 apr_pool_t *scratch_pool)
1254 const apr_array_header_t *old;
1255 apr_array_header_t *new_merged_path_revs;
1256 apr_pool_t *iterpool, *last_pool;
1257 apr_array_header_t *merged_path_revisions =
1258 apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
1260 old = mainline_path_revisions;
1261 iterpool = svn_pool_create(scratch_pool);
1262 last_pool = svn_pool_create(scratch_pool);
1267 apr_pool_t *temp_pool;
1269 svn_pool_clear(iterpool);
1270 new_merged_path_revs = apr_array_make(iterpool, 0,
1271 sizeof(struct path_revision *));
1273 /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather
1274 path_revisions for any merged revisions, and store those in NEW. */
1275 for (i = 0; i < old->nelts; i++)
1277 apr_pool_t *iterpool2;
1278 apr_hash_index_t *hi;
1279 struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
1280 struct path_revision *);
1281 if (!old_pr->merged_mergeinfo)
1284 iterpool2 = svn_pool_create(iterpool);
1286 /* Determine and trace the merge sources. */
1287 for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
1288 hi = apr_hash_next(hi))
1290 apr_pool_t *iterpool3;
1291 svn_rangelist_t *rangelist;
1295 svn_pool_clear(iterpool2);
1296 iterpool3 = svn_pool_create(iterpool2);
1298 apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist);
1300 for (j = 0; j < rangelist->nelts; j++)
1302 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
1303 svn_merge_range_t *);
1304 svn_node_kind_t kind;
1305 svn_fs_root_t *root;
1307 if (range->end < start)
1310 svn_pool_clear(iterpool3);
1312 SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
1314 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
1315 if (kind != svn_node_file)
1318 /* Search and find revisions to add to the NEW list. */
1319 SVN_ERR(find_interesting_revisions(new_merged_path_revs,
1321 range->start, range->end,
1323 duplicate_path_revs,
1326 result_pool, iterpool3));
1328 svn_pool_destroy(iterpool3);
1330 svn_pool_destroy(iterpool2);
1333 /* Append the newly found path revisions with the old ones. */
1334 merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
1335 new_merged_path_revs);
1337 /* Swap data structures */
1338 old = new_merged_path_revs;
1339 temp_pool = last_pool;
1340 last_pool = iterpool;
1341 iterpool = temp_pool;
1343 while (new_merged_path_revs->nelts > 0);
1345 /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1346 qsort(merged_path_revisions->elts, merged_path_revisions->nelts,
1347 sizeof(struct path_revision *), compare_path_revisions);
1349 /* Copy to the output array. */
1350 *merged_path_revisions_out = apr_array_copy(result_pool,
1351 merged_path_revisions);
1353 svn_pool_destroy(iterpool);
1354 svn_pool_destroy(last_pool);
1356 return SVN_NO_ERROR;
1361 apr_pool_t *iterpool;
1362 apr_pool_t *last_pool;
1363 apr_hash_t *last_props;
1364 const char *last_path;
1365 svn_fs_root_t *last_root;
1368 /* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1370 static svn_error_t *
1371 send_path_revision(struct path_revision *path_rev,
1373 struct send_baton *sb,
1374 svn_file_rev_handler_t handler,
1375 void *handler_baton)
1377 apr_hash_t *rev_props;
1379 apr_array_header_t *prop_diffs;
1380 svn_fs_root_t *root;
1381 svn_txdelta_stream_t *delta_stream;
1382 svn_txdelta_window_handler_t delta_handler = NULL;
1383 void *delta_baton = NULL;
1384 apr_pool_t *tmp_pool; /* For swapping */
1385 svn_boolean_t contents_changed;
1387 svn_pool_clear(sb->iterpool);
1389 /* Get the revision properties. */
1390 SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
1391 path_rev->revnum, sb->iterpool));
1393 /* Open the revision root. */
1394 SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
1397 /* Get the file's properties for this revision and compute the diffs. */
1398 SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
1400 SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1403 /* Check if the contents changed. */
1404 /* Special case: In the first revision, we always provide a delta. */
1406 SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root,
1407 sb->last_path, root, path_rev->path,
1410 contents_changed = TRUE;
1412 /* We have all we need, give to the handler. */
1413 SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
1414 rev_props, path_rev->merged,
1415 contents_changed ? &delta_handler : NULL,
1416 contents_changed ? &delta_baton : NULL,
1417 prop_diffs, sb->iterpool));
1419 /* Compute and send delta if client asked for it.
1420 Note that this was initialized to NULL, so if !contents_changed,
1421 no deltas will be computed. */
1424 /* Get the content delta. */
1425 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
1426 sb->last_root, sb->last_path,
1427 root, path_rev->path,
1430 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1431 delta_handler, delta_baton,
1435 /* Remember root, path and props for next iteration. */
1436 sb->last_root = root;
1437 sb->last_path = path_rev->path;
1438 sb->last_props = props;
1440 /* Swap the pools. */
1441 tmp_pool = sb->iterpool;
1442 sb->iterpool = sb->last_pool;
1443 sb->last_pool = tmp_pool;
1445 return SVN_NO_ERROR;
1448 /* Similar to svn_repos_get_file_revs2() but returns paths while walking
1449 history instead of after collecting all history.
1451 This allows implementing clients to immediately start processing and
1452 stop when they got the information they need. (E.g. all or a specific set
1453 of lines were modified) */
1454 static svn_error_t *
1455 get_file_revs_backwards(svn_repos_t *repos,
1459 svn_repos_authz_func_t authz_read_func,
1460 void *authz_read_baton,
1461 svn_file_rev_handler_t handler,
1462 void *handler_baton,
1463 apr_pool_t *scratch_pool)
1465 apr_pool_t *iterpool, *last_pool;
1466 svn_fs_history_t *history;
1467 svn_fs_root_t *root;
1468 svn_node_kind_t kind;
1469 struct send_baton sb;
1471 /* We switch between two pools while looping and so does the path-rev
1472 handler for actually reported revisions. We do this as we
1473 need just information from last iteration to be available. */
1475 iterpool = svn_pool_create(scratch_pool);
1476 last_pool = svn_pool_create(scratch_pool);
1477 sb.iterpool = svn_pool_create(scratch_pool);
1478 sb.last_pool = svn_pool_create(scratch_pool);
1480 /* We want the first txdelta to be against the empty file. */
1481 sb.last_root = NULL;
1482 sb.last_path = NULL;
1484 /* Create an empty hash table for the first property diff. */
1485 sb.last_props = apr_hash_make(sb.last_pool);
1487 /* The path had better be a file in this revision. */
1488 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1489 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1490 if (kind != svn_node_file)
1491 return svn_error_createf(SVN_ERR_FS_NOT_FILE,
1492 NULL, _("'%s' is not a file in revision %ld"),
1495 /* Open a history object. */
1496 SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1499 struct path_revision *path_rev;
1500 svn_revnum_t tmp_revnum;
1501 const char *tmp_path;
1503 svn_pool_clear(iterpool);
1505 /* Fetch the history object to walk through. */
1506 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1509 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1510 history, iterpool));
1512 /* Check authorization. */
1513 if (authz_read_func)
1515 svn_boolean_t readable;
1516 svn_fs_root_t *tmp_root;
1518 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1520 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1521 authz_read_baton, iterpool));
1526 /* We didn't break, so we must really want this path-rev. */
1527 path_rev = apr_palloc(iterpool, sizeof(*path_rev));
1528 path_rev->path = tmp_path;
1529 path_rev->revnum = tmp_revnum;
1530 path_rev->merged = FALSE;
1532 SVN_ERR(send_path_revision(path_rev, repos, &sb,
1533 handler, handler_baton));
1535 if (path_rev->revnum <= start)
1540 apr_pool_t *tmp_pool = iterpool;
1541 iterpool = last_pool;
1542 last_pool = tmp_pool;
1546 svn_pool_destroy(iterpool);
1547 svn_pool_destroy(last_pool);
1548 svn_pool_destroy(sb.last_pool);
1549 svn_pool_destroy(sb.iterpool);
1551 return SVN_NO_ERROR;
1556 /* We don't yet support sending revisions in reverse order; the caller wait
1557 * until we've traced back through the entire history, and then accept
1558 * them from oldest to youngest. Someday this may change, but in the meantime,
1559 * the general algorithm is thus:
1561 * 1) Trace back through the history of an object, adding each revision
1562 * found to the MAINLINE_PATH_REVISIONS array, marking any which were
1564 * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1565 * merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1566 * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1568 * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1569 * oldest to youngest, interleaving as appropriate. This is implemented
1570 * similar to an insertion sort, but instead of inserting into another
1571 * array, we just call the appropriate handler.
1573 * 2013-02: Added a very simple reverse for mainline only changes. Before this,
1574 * this would return an error (path not found) or just the first
1575 * revision before end.
1578 svn_repos_get_file_revs2(svn_repos_t *repos,
1582 svn_boolean_t include_merged_revisions,
1583 svn_repos_authz_func_t authz_read_func,
1584 void *authz_read_baton,
1585 svn_file_rev_handler_t handler,
1586 void *handler_baton,
1587 apr_pool_t *scratch_pool)
1589 apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
1590 apr_hash_t *duplicate_path_revs;
1591 struct send_baton sb;
1592 int mainline_pos, merged_pos;
1596 if (include_merged_revisions)
1597 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
1599 return svn_error_trace(
1600 get_file_revs_backwards(repos, path,
1609 /* We switch between two pools while looping, since we need information from
1610 the last iteration to be available. */
1611 sb.iterpool = svn_pool_create(scratch_pool);
1612 sb.last_pool = svn_pool_create(scratch_pool);
1614 /* We want the first txdelta to be against the empty file. */
1615 sb.last_root = NULL;
1616 sb.last_path = NULL;
1618 /* Create an empty hash table for the first property diff. */
1619 sb.last_props = apr_hash_make(sb.last_pool);
1622 /* Get the revisions we are interested in. */
1623 duplicate_path_revs = apr_hash_make(scratch_pool);
1624 mainline_path_revisions = apr_array_make(scratch_pool, 100,
1625 sizeof(struct path_revision *));
1626 SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
1627 start, end, include_merged_revisions,
1628 FALSE, duplicate_path_revs,
1629 authz_read_func, authz_read_baton,
1630 scratch_pool, sb.iterpool));
1632 /* If we are including merged revisions, go get those, too. */
1633 if (include_merged_revisions)
1634 SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
1635 mainline_path_revisions, repos,
1636 duplicate_path_revs, authz_read_func,
1638 scratch_pool, sb.iterpool));
1640 merged_path_revisions = apr_array_make(scratch_pool, 0,
1641 sizeof(struct path_revision *));
1643 /* We must have at least one revision to get. */
1644 SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
1646 /* Walk through both mainline and merged revisions, and send them in
1647 reverse chronological order, interleaving as appropriate. */
1648 mainline_pos = mainline_path_revisions->nelts - 1;
1649 merged_pos = merged_path_revisions->nelts - 1;
1650 while (mainline_pos >= 0 && merged_pos >= 0)
1652 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1654 struct path_revision *);
1655 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1657 struct path_revision *);
1659 if (main_pr->revnum <= merged_pr->revnum)
1661 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1667 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1673 /* Send any remaining revisions from the mainline list. */
1674 for (; mainline_pos >= 0; mainline_pos -= 1)
1676 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1678 struct path_revision *);
1679 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
1682 /* Ditto for the merged list. */
1683 for (; merged_pos >= 0; merged_pos -= 1)
1685 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1687 struct path_revision *);
1688 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1692 svn_pool_destroy(sb.last_pool);
1693 svn_pool_destroy(sb.iterpool);
1695 return SVN_NO_ERROR;