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"
41 #include "private/svn_fs_private.h"
42 #include "private/svn_sorts_private.h"
45 /* Note: this binary search assumes that the datestamp properties on
46 each revision are in chronological order. That is if revision A >
47 revision B, then A's datestamp is younger then B's datestamp.
49 If someone comes along and sets a bogus datestamp, this routine
52 ### todo: you know, we *could* have svn_fs_change_rev_prop() do
53 some semantic checking when it's asked to change special reserved
54 svn: properties. It could prevent such a problem. */
57 /* helper for svn_repos_dated_revision().
59 Set *TM to the apr_time_t datestamp on revision REV in FS. */
61 get_time(apr_time_t *tm,
66 svn_string_t *date_str;
68 SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
71 return svn_error_createf
72 (SVN_ERR_FS_GENERAL, NULL,
73 _("Failed to find time on revision %ld"), rev);
75 return svn_time_from_cstring(tm, date_str->data, pool);
80 svn_repos_dated_revision(svn_revnum_t *revision,
85 svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
87 svn_fs_t *fs = repos->fs;
89 /* Initialize top and bottom values of binary search. */
90 SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool));
94 while (rev_bot <= rev_top)
96 rev_mid = (rev_top + rev_bot) / 2;
97 SVN_ERR(get_time(&this_time, fs, rev_mid, pool));
99 if (this_time > tm)/* we've overshot */
101 apr_time_t previous_time;
103 if ((rev_mid - 1) < 0)
109 /* see if time falls between rev_mid and rev_mid-1: */
110 SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool));
111 if (previous_time <= tm)
113 *revision = rev_mid - 1;
117 rev_top = rev_mid - 1;
120 else if (this_time < tm) /* we've undershot */
122 apr_time_t next_time;
124 if ((rev_mid + 1) > rev_latest)
126 *revision = rev_latest;
130 /* see if time falls between rev_mid and rev_mid+1: */
131 SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
138 rev_bot = rev_mid + 1;
143 *revision = rev_mid; /* exact match! */
153 svn_repos_get_committed_info(svn_revnum_t *committed_rev,
154 const char **committed_date,
155 const char **last_author,
160 apr_hash_t *revprops;
162 svn_fs_t *fs = svn_fs_root_fs(root);
164 /* ### It might be simpler just to declare that revision
165 properties have char * (i.e., UTF-8) values, not arbitrary
166 binary values, hmmm. */
167 svn_string_t *committed_date_s, *last_author_s;
169 /* Get the CR field out of the node's skel. */
170 SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
172 /* Get the revision properties of this revision. */
173 SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
175 /* Extract date and author from these revprops. */
176 committed_date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
177 last_author_s = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
179 *committed_date = committed_date_s ? committed_date_s->data : NULL;
180 *last_author = last_author_s ? last_author_s->data : NULL;
186 svn_repos_history2(svn_fs_t *fs,
188 svn_repos_history_func_t history_func,
190 svn_repos_authz_func_t authz_read_func,
191 void *authz_read_baton,
194 svn_boolean_t cross_copies,
197 svn_fs_history_t *history;
198 apr_pool_t *oldpool = svn_pool_create(pool);
199 apr_pool_t *newpool = svn_pool_create(pool);
200 const char *history_path;
201 svn_revnum_t history_rev;
204 /* Validate the revisions. */
205 if (! SVN_IS_VALID_REVNUM(start))
206 return svn_error_createf
207 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
208 _("Invalid start revision %ld"), start);
209 if (! SVN_IS_VALID_REVNUM(end))
210 return svn_error_createf
211 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
212 _("Invalid end revision %ld"), end);
214 /* Ensure that the input is ordered. */
217 svn_revnum_t tmprev = start;
222 /* Get a revision root for END, and an initial HISTORY baton. */
223 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
227 svn_boolean_t readable;
228 SVN_ERR(authz_read_func(&readable, root, path,
229 authz_read_baton, pool));
231 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
234 SVN_ERR(svn_fs_node_history2(&history, root, path, oldpool, oldpool));
236 /* Now, we loop over the history items, calling svn_fs_history_prev(). */
239 /* Note that we have to do some crazy pool work here. We can't
240 get rid of the old history until we use it to get the new, so
241 we alternate back and forth between our subpools. */
245 SVN_ERR(svn_fs_history_prev2(&history, history, cross_copies, newpool,
248 /* Only continue if there is further history to deal with. */
252 /* Fetch the location information for this history step. */
253 SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
256 /* If this history item predates our START revision, quit
258 if (history_rev < start)
261 /* Is the history item readable? If not, quit. */
264 svn_boolean_t readable;
265 svn_fs_root_t *history_root;
266 SVN_ERR(svn_fs_revision_root(&history_root, fs,
267 history_rev, newpool));
268 SVN_ERR(authz_read_func(&readable, history_root, history_path,
269 authz_read_baton, newpool));
274 /* Call the user-provided callback function. */
275 err = history_func(history_baton, history_path, history_rev, newpool);
278 if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
280 svn_error_clear(err);
285 return svn_error_trace(err);
289 /* We're done with the old history item, so we can clear its
290 pool, and then toggle our notion of "the old pool". */
291 svn_pool_clear(oldpool);
296 while (history); /* shouldn't hit this */
299 svn_pool_destroy(oldpool);
300 svn_pool_destroy(newpool);
306 svn_repos_deleted_rev(svn_fs_t *fs,
310 svn_revnum_t *deleted,
313 apr_pool_t *iterpool;
314 svn_fs_root_t *start_root, *root;
315 svn_revnum_t mid_rev;
316 svn_node_kind_t kind;
317 svn_fs_node_relation_t node_relation;
319 /* Validate the revision range. */
320 if (! SVN_IS_VALID_REVNUM(start))
321 return svn_error_createf
322 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
323 _("Invalid start revision %ld"), start);
324 if (! SVN_IS_VALID_REVNUM(end))
325 return svn_error_createf
326 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
327 _("Invalid end revision %ld"), end);
329 /* Ensure that the input is ordered. */
332 svn_revnum_t tmprev = start;
337 /* Ensure path exists in fs at start revision. */
338 SVN_ERR(svn_fs_revision_root(&start_root, fs, start, pool));
339 SVN_ERR(svn_fs_check_path(&kind, start_root, path, pool));
340 if (kind == svn_node_none)
342 /* Path must exist in fs at start rev. */
343 *deleted = SVN_INVALID_REVNUM;
347 /* Ensure path was deleted at or before end revision. */
348 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
349 SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
350 if (kind != svn_node_none)
352 /* path exists in the end node and the end node is equivalent
353 or otherwise equivalent to the start node. This can mean
356 1) The end node *is* simply the start node, uncopied
357 and unmodified in the start to end range.
359 2) The start node was modified, but never copied.
361 3) The start node was copied, but this copy occurred at
362 start or some rev *previous* to start, this is
363 effectively the same situation as 1 if the node was
364 never modified or 2 if it was.
366 In the first three cases the path was not deleted in
367 the specified range and we are done, but in the following
368 cases the start node must have been deleted at least once:
370 4) The start node was deleted and replaced by a copy of
371 itself at some rev between start and end. This copy
372 may itself have been replaced with copies of itself.
374 5) The start node was deleted and replaced by a node which
375 it does not share any history with.
377 SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path,
379 if (node_relation != svn_fs_node_unrelated)
381 svn_fs_root_t *copy_root;
382 const char *copy_path;
383 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root,
386 (svn_fs_revision_root_revision(copy_root) <= start))
388 /* Case 1,2 or 3, nothing more to do. */
389 *deleted = SVN_INVALID_REVNUM;
395 /* If we get here we know that path exists in rev start and was deleted
396 at least once before rev end. To find the revision path was first
397 deleted we use a binary search. The rules for the determining if
398 the deletion comes before or after a given median revision are
399 described by this matrix:
401 | Most recent copy event that
402 | caused mid node to exist.
403 |-----------------------------------------------------
405 at start and | Copied at | Copied at | Never copied |
406 mid nodes. | rev > start | rev <= start | |
408 -------------------------------------------------------------------|
409 Mid node is | A) Start node | |
410 equivalent to | replaced with | E) Mid node == start node, |
411 start node | an unmodified | look HIGHER. |
415 -------------------------------------------------------------------|
416 Mid node is | B) Start node | |
417 otherwise | replaced with | F) Mid node is a modified |
418 related to | a modified | version of start node, |
419 start node | copy of | look HIGHER. |
422 -------------------------------------------------------------------|
424 unrelated to | C) Start node replaced with unrelated mid node, |
425 start node | look LOWER. |
427 -------------------------------------------------------------------|
429 exist at mid | D) Start node deleted before mid node, |
432 --------------------------------------------------------------------
435 mid_rev = (start + end) / 2;
436 iterpool = svn_pool_create(pool);
440 svn_pool_clear(iterpool);
442 /* Get revision root and node id for mid_rev at that revision. */
443 SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, iterpool));
444 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool));
445 if (kind == svn_node_none)
447 /* Case D: Look lower in the range. */
449 mid_rev = (start + mid_rev) / 2;
453 svn_fs_root_t *copy_root;
454 const char *copy_path;
455 /* Determine the relationship between the start node
456 and the current node. */
457 SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path,
458 root, path, iterpool));
459 if (node_relation != svn_fs_node_unrelated)
460 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root,
462 if (node_relation == svn_fs_node_unrelated ||
464 (svn_fs_revision_root_revision(copy_root) > start)))
466 /* Cases A, B, C: Look at lower revs. */
468 mid_rev = (start + mid_rev) / 2;
470 else if (end - mid_rev == 1)
472 /* Found the node path was deleted. */
478 /* Cases E, F: Look at higher revs. */
480 mid_rev = (start + end) / 2;
485 svn_pool_destroy(iterpool);
490 /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
493 check_readability(svn_fs_root_t *root,
495 svn_repos_authz_func_t authz_read_func,
496 void *authz_read_baton,
499 svn_boolean_t readable;
500 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
502 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
503 _("Unreadable path encountered; access denied"));
508 /* The purpose of this function is to discover if fs_path@future_rev
509 * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */
512 check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
515 svn_revnum_t peg_revision,
516 svn_revnum_t future_revision,
520 svn_fs_history_t *history;
521 const char *path = NULL;
522 svn_revnum_t revision;
523 apr_pool_t *lastpool, *currpool;
525 lastpool = svn_pool_create(pool);
526 currpool = svn_pool_create(pool);
528 SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool));
530 SVN_ERR(svn_fs_node_history2(&history, root, fs_path, lastpool, lastpool));
532 /* Since paths that are different according to strcmp may still be
533 equivalent (due to number of consecutive slashes and the fact that
534 "" is the same as "/"), we get the "canonical" path in the first
535 iteration below so that the comparison after the loop will work
543 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, currpool,
549 SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
552 fs_path = apr_pstrdup(pool, path);
554 if (revision <= peg_revision)
557 /* Clear old pool and flip. */
558 svn_pool_clear(lastpool);
564 /* We must have had at least one iteration above where we
565 reassigned fs_path. Else, the path wouldn't have existed at
566 future_revision and svn_fs_history would have thrown. */
567 SVN_ERR_ASSERT(fs_path != NULL);
569 *is_ancestor = (history && strcmp(path, fs_path) == 0);
576 svn_repos__prev_location(svn_revnum_t *appeared_rev,
577 const char **prev_path,
578 svn_revnum_t *prev_rev,
580 svn_revnum_t revision,
584 svn_fs_root_t *root, *copy_root;
585 const char *copy_path, *copy_src_path, *remainder;
586 svn_revnum_t copy_src_rev;
588 /* Initialize return variables. */
590 *appeared_rev = SVN_INVALID_REVNUM;
592 *prev_rev = SVN_INVALID_REVNUM;
596 /* Ask about the most recent copy which affected PATH@REVISION. If
597 there was no such copy, we're done. */
598 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
599 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, path, pool));
603 /* Ultimately, it's not the path of the closest copy's source that
604 we care about -- it's our own path's location in the copy source
605 revision. So we'll tack the relative path that expresses the
606 difference between the copy destination and our path in the copy
607 revision onto the copy source path to determine this information.
609 In other words, if our path is "/branches/my-branch/foo/bar", and
610 we know that the closest relevant copy was a copy of "/trunk" to
611 "/branches/my-branch", then that relative path under the copy
612 destination is "/foo/bar". Tacking that onto the copy source
613 path tells us that our path was located at "/trunk/foo/bar"
616 SVN_ERR(svn_fs_copied_from(©_src_rev, ©_src_path,
617 copy_root, copy_path, pool));
618 remainder = svn_fspath__skip_ancestor(copy_path, path);
620 *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
622 *appeared_rev = svn_fs_revision_root_revision(copy_root);
624 *prev_rev = copy_src_rev;
630 svn_repos_trace_node_locations(svn_fs_t *fs,
631 apr_hash_t **locations,
633 svn_revnum_t peg_revision,
634 const apr_array_header_t *location_revisions_orig,
635 svn_repos_authz_func_t authz_read_func,
636 void *authz_read_baton,
639 apr_array_header_t *location_revisions;
640 svn_revnum_t *revision_ptr, *revision_ptr_end;
643 svn_revnum_t revision;
644 svn_boolean_t is_ancestor;
645 apr_pool_t *lastpool, *currpool;
647 SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t));
649 /* Ensure that FS_PATH is absolute, because our path-math below will
650 depend on that being the case. */
652 fs_path = apr_pstrcat(pool, "/", fs_path, SVN_VA_NULL);
654 /* Another sanity check. */
657 svn_fs_root_t *peg_root;
658 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
659 SVN_ERR(check_readability(peg_root, fs_path,
660 authz_read_func, authz_read_baton, pool));
663 *locations = apr_hash_make(pool);
665 /* We flip between two pools in the second loop below. */
666 lastpool = svn_pool_create(pool);
667 currpool = svn_pool_create(pool);
669 /* First - let's sort the array of the revisions from the greatest revision
670 * downward, so it will be easier to search on. */
671 location_revisions = apr_array_copy(pool, location_revisions_orig);
672 qsort(location_revisions->elts, location_revisions->nelts,
673 sizeof(*revision_ptr), svn_sort_compare_revisions);
675 revision_ptr = (svn_revnum_t *)location_revisions->elts;
676 revision_ptr_end = revision_ptr + location_revisions->nelts;
678 /* Ignore revisions R that are younger than the peg_revisions where
679 path@peg_revision is not an ancestor of path@R. */
681 while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision)
683 svn_pool_clear(currpool);
684 SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path,
685 peg_revision, *revision_ptr,
692 revision = is_ancestor ? *revision_ptr : peg_revision;
696 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
697 SVN_ERR(check_readability(root, fs_path, authz_read_func,
698 authz_read_baton, pool));
701 while (revision_ptr < revision_ptr_end)
704 svn_revnum_t appeared_rev, prev_rev;
705 const char *prev_path;
707 /* Find the target of the innermost copy relevant to path@revision.
708 The copy may be of path itself, or of a parent directory. */
709 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
710 fs, revision, path, currpool));
714 /* Assign the current path to all younger revisions until we reach
715 the copy target rev. */
716 while ((revision_ptr < revision_ptr_end)
717 && (*revision_ptr >= appeared_rev))
719 /* *revision_ptr is allocated out of pool, so we can point
720 to in the hash table. */
721 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
722 apr_pstrdup(pool, path));
726 /* Ignore all revs between the copy target rev and the copy
727 source rev (non-inclusive). */
728 while ((revision_ptr < revision_ptr_end)
729 && (*revision_ptr > prev_rev))
738 svn_boolean_t readable;
739 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
740 SVN_ERR(authz_read_func(&readable, root, path,
741 authz_read_baton, currpool));
744 svn_pool_destroy(lastpool);
745 svn_pool_destroy(currpool);
750 /* Clear last pool and switch. */
751 svn_pool_clear(lastpool);
757 /* There are no copies relevant to path@revision. So any remaining
758 revisions either predate the creation of path@revision or have
759 the node existing at the same path. We will look up path@lrev
760 for each remaining location-revision and make sure it is related
762 SVN_ERR(svn_fs_revision_root(&root, fs, revision, lastpool));
763 while (revision_ptr < revision_ptr_end)
765 svn_node_kind_t kind;
766 svn_fs_node_relation_t node_relation;
767 svn_fs_root_t *cur_rev_root;
769 svn_pool_clear(currpool);
770 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, *revision_ptr,
772 SVN_ERR(svn_fs_check_path(&kind, cur_rev_root, path, currpool));
773 if (kind == svn_node_none)
775 SVN_ERR(svn_fs_node_relation(&node_relation, root, path,
776 cur_rev_root, path, currpool));
777 if (node_relation == svn_fs_node_unrelated)
780 /* The node exists at the same path; record that and advance. */
781 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
782 apr_pstrdup(pool, path));
786 /* Ignore any remaining location-revisions; they predate the
787 creation of path@revision. */
789 svn_pool_destroy(lastpool);
790 svn_pool_destroy(currpool);
796 /* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
797 its revision range fits between END_REV and START_REV, possibly
798 cropping the range so that it fits *entirely* in that range. */
800 maybe_crop_and_send_segment(svn_location_segment_t *segment,
801 svn_revnum_t start_rev,
802 svn_revnum_t end_rev,
803 svn_location_segment_receiver_t receiver,
804 void *receiver_baton,
807 /* We only want to transmit this segment if some portion of it
808 is between our END_REV and START_REV. */
809 if (! ((segment->range_start > start_rev)
810 || (segment->range_end < end_rev)))
812 /* Correct our segment range when the range straddles one of
813 our requested revision boundaries. */
814 if (segment->range_start < end_rev)
815 segment->range_start = end_rev;
816 if (segment->range_end > start_rev)
817 segment->range_end = start_rev;
818 SVN_ERR(receiver(segment, receiver_baton, pool));
825 svn_repos_node_location_segments(svn_repos_t *repos,
827 svn_revnum_t peg_revision,
828 svn_revnum_t start_rev,
829 svn_revnum_t end_rev,
830 svn_location_segment_receiver_t receiver,
831 void *receiver_baton,
832 svn_repos_authz_func_t authz_read_func,
833 void *authz_read_baton,
836 svn_fs_t *fs = svn_repos_fs(repos);
837 svn_stringbuf_t *current_path;
838 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
841 /* No PEG_REVISION? We'll use HEAD. */
842 if (! SVN_IS_VALID_REVNUM(peg_revision))
844 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
845 peg_revision = youngest_rev;
848 /* No START_REV? We'll use HEAD (which we may have already fetched). */
849 if (! SVN_IS_VALID_REVNUM(start_rev))
851 if (SVN_IS_VALID_REVNUM(youngest_rev))
852 start_rev = youngest_rev;
854 SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
857 /* No END_REV? We'll use 0. */
858 end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
860 /* Are the revision properly ordered? They better be -- the API
862 SVN_ERR_ASSERT(end_rev <= start_rev);
863 SVN_ERR_ASSERT(start_rev <= peg_revision);
865 /* Ensure that PATH is absolute, because our path-math will depend
866 on that being the case. */
868 path = apr_pstrcat(pool, "/", path, SVN_VA_NULL);
873 svn_fs_root_t *peg_root;
874 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
875 SVN_ERR(check_readability(peg_root, path,
876 authz_read_func, authz_read_baton, pool));
879 /* Okay, let's get searching! */
880 subpool = svn_pool_create(pool);
881 current_rev = peg_revision;
882 current_path = svn_stringbuf_create(path, pool);
883 while (current_rev >= end_rev)
885 svn_revnum_t appeared_rev, prev_rev;
886 const char *cur_path, *prev_path;
887 svn_location_segment_t *segment;
889 svn_pool_clear(subpool);
891 cur_path = apr_pstrmemdup(subpool, current_path->data,
893 segment = apr_pcalloc(subpool, sizeof(*segment));
894 segment->range_end = current_rev;
895 segment->range_start = end_rev;
896 /* segment path should be absolute without leading '/'. */
897 segment->path = cur_path + 1;
899 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
900 fs, current_rev, cur_path, subpool));
902 /* If there are no previous locations for this thing (meaning,
903 it originated at the current path), then we simply need to
904 find its revision of origin to populate our final segment.
905 Otherwise, the APPEARED_REV is the start of current segment's
909 svn_fs_root_t *revroot;
910 SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
911 SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
913 if (segment->range_start < end_rev)
914 segment->range_start = end_rev;
915 current_rev = SVN_INVALID_REVNUM;
919 segment->range_start = appeared_rev;
920 svn_stringbuf_set(current_path, prev_path);
921 current_rev = prev_rev;
924 /* Report our segment, providing it passes authz muster. */
927 svn_boolean_t readable;
928 svn_fs_root_t *cur_rev_root;
930 /* authz_read_func requires path to have a leading slash. */
931 const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
934 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
935 segment->range_end, subpool));
936 SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
937 authz_read_baton, subpool));
942 /* Transmit the segment (if it's within the scope of our concern). */
943 SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
944 receiver, receiver_baton, subpool));
946 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
947 (and didn't ever reach END_REV). */
948 if (! SVN_IS_VALID_REVNUM(current_rev))
951 /* If there's a gap in the history, we need to report as much
952 (if the gap is within the scope of our concern). */
953 if (segment->range_start - current_rev > 1)
955 svn_location_segment_t *gap_segment;
956 gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
957 gap_segment->range_end = segment->range_start - 1;
958 gap_segment->range_start = current_rev + 1;
959 gap_segment->path = NULL;
960 SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
961 receiver, receiver_baton,
965 svn_pool_destroy(subpool);
969 static APR_INLINE svn_boolean_t
970 is_path_in_hash(apr_hash_t *duplicate_path_revs,
972 svn_revnum_t revision,
975 const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
978 ptr = svn_hash_gets(duplicate_path_revs, key);
987 /* Does this path_rev have merges to also be included? If so, this is
988 the union of both additions and (negated) deletions of mergeinfo. */
989 apr_hash_t *merged_mergeinfo;
991 /* Is this a merged revision? */
992 svn_boolean_t merged;
995 /* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store
996 the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The
997 difference is the union of both additions and (negated) deletions. The
998 returned *MERGED_MERGEINFO will be NULL if there are no changes. */
1000 get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
1002 struct path_revision *old_path_rev,
1003 apr_pool_t *result_pool,
1004 apr_pool_t *scratch_pool)
1006 apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1008 svn_fs_root_t *root, *prev_root;
1009 apr_hash_t *changed_paths;
1010 const char *path = old_path_rev->path;
1012 /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
1013 if there is a property change. */
1014 SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
1016 SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
1019 svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
1020 if (changed_path && changed_path->prop_mod
1021 && changed_path->mergeinfo_mod != svn_tristate_false)
1023 if (svn_fspath__is_root(path, strlen(path)))
1025 *merged_mergeinfo = NULL;
1026 return SVN_NO_ERROR;
1028 path = svn_fspath__dirname(path, scratch_pool);
1031 /* First, find the mergeinfo difference for old_path_rev->revnum, and
1032 old_path_rev->revnum - 1. */
1033 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1034 because we will filter out unreadable revisions in
1035 find_interesting_revision() */
1036 err = svn_fs__get_mergeinfo_for_path(&curr_mergeinfo,
1037 root, old_path_rev->path,
1038 svn_mergeinfo_inherited, TRUE,
1039 scratch_pool, scratch_pool);
1042 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1044 /* Issue #3896: If invalid mergeinfo is encountered the
1045 best we can do is ignore it and act is if there are
1046 no mergeinfo differences. */
1047 svn_error_clear(err);
1048 *merged_mergeinfo = NULL;
1049 return SVN_NO_ERROR;
1053 return svn_error_trace(err);
1057 SVN_ERR(svn_fs_revision_root(&prev_root, repos->fs, old_path_rev->revnum - 1,
1059 err = svn_fs__get_mergeinfo_for_path(&prev_mergeinfo,
1060 prev_root, old_path_rev->path,
1061 svn_mergeinfo_inherited, TRUE,
1062 scratch_pool, scratch_pool);
1063 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
1064 || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
1066 /* If the path doesn't exist in the previous revision or it does exist
1067 but has invalid mergeinfo (Issue #3896), assume no merges. */
1068 svn_error_clear(err);
1069 *merged_mergeinfo = NULL;
1070 return SVN_NO_ERROR;
1075 /* Then calculate and merge the differences, combining additions and
1076 (negated) deletions as all positive changes in CHANGES. */
1077 SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
1078 curr_mergeinfo, FALSE, result_pool,
1080 SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
1082 /* Store the result. */
1083 if (apr_hash_count(changed))
1084 *merged_mergeinfo = changed;
1086 *merged_mergeinfo = NULL;
1088 return SVN_NO_ERROR;
1091 static svn_error_t *
1092 find_interesting_revisions(apr_array_header_t *path_revisions,
1097 svn_boolean_t include_merged_revisions,
1098 svn_boolean_t mark_as_merged,
1099 apr_hash_t *duplicate_path_revs,
1100 svn_repos_authz_func_t authz_read_func,
1101 void *authz_read_baton,
1102 apr_pool_t *result_pool,
1103 apr_pool_t *scratch_pool)
1105 apr_pool_t *iterpool, *last_pool;
1106 svn_fs_history_t *history;
1107 svn_fs_root_t *root;
1108 svn_node_kind_t kind;
1110 /* We switch between two pools while looping, since we need information from
1111 the last iteration to be available. */
1112 iterpool = svn_pool_create(scratch_pool);
1113 last_pool = svn_pool_create(scratch_pool);
1115 /* The path had better be a file in this revision. */
1116 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1117 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1118 if (kind != svn_node_file)
1119 return svn_error_createf
1120 (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
1123 /* Open a history object. */
1124 SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool,
1128 struct path_revision *path_rev;
1129 svn_revnum_t tmp_revnum;
1130 const char *tmp_path;
1131 apr_pool_t *tmp_pool;
1133 svn_pool_clear(iterpool);
1135 /* Fetch the history object to walk through. */
1136 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
1140 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1141 history, iterpool));
1143 /* Check to see if we already saw this path (and it's ancestors) */
1144 if (include_merged_revisions
1145 && is_path_in_hash(duplicate_path_revs, tmp_path,
1146 tmp_revnum, iterpool))
1149 /* Check authorization. */
1150 if (authz_read_func)
1152 svn_boolean_t readable;
1153 svn_fs_root_t *tmp_root;
1155 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1157 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1158 authz_read_baton, iterpool));
1163 /* We didn't break, so we must really want this path-rev. */
1164 path_rev = apr_palloc(result_pool, sizeof(*path_rev));
1165 path_rev->path = apr_pstrdup(result_pool, tmp_path);
1166 path_rev->revnum = tmp_revnum;
1167 path_rev->merged = mark_as_merged;
1168 APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
1170 if (include_merged_revisions)
1171 SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
1172 path_rev, result_pool, iterpool));
1174 path_rev->merged_mergeinfo = NULL;
1176 /* Add the path/rev pair to the hash, so we can filter out future
1177 occurrences of it. We only care about this if including merged
1178 revisions, 'cause that's the only time we can have duplicates. */
1179 svn_hash_sets(duplicate_path_revs,
1180 apr_psprintf(result_pool, "%s:%ld", path_rev->path,
1182 (void *)0xdeadbeef);
1184 if (path_rev->revnum <= start)
1188 tmp_pool = iterpool;
1189 iterpool = last_pool;
1190 last_pool = tmp_pool;
1193 svn_pool_destroy(iterpool);
1194 svn_pool_destroy(last_pool);
1196 return SVN_NO_ERROR;
1199 /* Comparison function to sort path/revisions in increasing order */
1201 compare_path_revisions(const void *a, const void *b)
1203 struct path_revision *a_pr = *(struct path_revision *const *)a;
1204 struct path_revision *b_pr = *(struct path_revision *const *)b;
1206 if (a_pr->revnum == b_pr->revnum)
1209 return a_pr->revnum < b_pr->revnum ? 1 : -1;
1212 static svn_error_t *
1213 find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
1215 const apr_array_header_t *mainline_path_revisions,
1217 apr_hash_t *duplicate_path_revs,
1218 svn_repos_authz_func_t authz_read_func,
1219 void *authz_read_baton,
1220 apr_pool_t *result_pool,
1221 apr_pool_t *scratch_pool)
1223 const apr_array_header_t *old;
1224 apr_array_header_t *new_merged_path_revs;
1225 apr_pool_t *iterpool, *last_pool;
1226 apr_array_header_t *merged_path_revisions =
1227 apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
1229 old = mainline_path_revisions;
1230 iterpool = svn_pool_create(scratch_pool);
1231 last_pool = svn_pool_create(scratch_pool);
1236 apr_pool_t *temp_pool;
1238 svn_pool_clear(iterpool);
1239 new_merged_path_revs = apr_array_make(iterpool, 0,
1240 sizeof(struct path_revision *));
1242 /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather
1243 path_revisions for any merged revisions, and store those in NEW. */
1244 for (i = 0; i < old->nelts; i++)
1246 apr_pool_t *iterpool2;
1247 apr_hash_index_t *hi;
1248 struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
1249 struct path_revision *);
1250 if (!old_pr->merged_mergeinfo)
1253 iterpool2 = svn_pool_create(iterpool);
1255 /* Determine and trace the merge sources. */
1256 for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
1257 hi = apr_hash_next(hi))
1259 const char *path = apr_hash_this_key(hi);
1260 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
1261 apr_pool_t *iterpool3;
1264 svn_pool_clear(iterpool2);
1265 iterpool3 = svn_pool_create(iterpool2);
1267 for (j = 0; j < rangelist->nelts; j++)
1269 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
1270 svn_merge_range_t *);
1271 svn_node_kind_t kind;
1272 svn_fs_root_t *root;
1274 if (range->end < start)
1277 svn_pool_clear(iterpool3);
1279 SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
1281 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
1282 if (kind != svn_node_file)
1285 /* Search and find revisions to add to the NEW list. */
1286 SVN_ERR(find_interesting_revisions(new_merged_path_revs,
1288 range->start, range->end,
1290 duplicate_path_revs,
1293 result_pool, iterpool3));
1295 svn_pool_destroy(iterpool3);
1297 svn_pool_destroy(iterpool2);
1300 /* Append the newly found path revisions with the old ones. */
1301 merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
1302 new_merged_path_revs);
1304 /* Swap data structures */
1305 old = new_merged_path_revs;
1306 temp_pool = last_pool;
1307 last_pool = iterpool;
1308 iterpool = temp_pool;
1310 while (new_merged_path_revs->nelts > 0);
1312 /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1313 svn_sort__array(merged_path_revisions, compare_path_revisions);
1315 /* Copy to the output array. */
1316 *merged_path_revisions_out = apr_array_copy(result_pool,
1317 merged_path_revisions);
1319 svn_pool_destroy(iterpool);
1320 svn_pool_destroy(last_pool);
1322 return SVN_NO_ERROR;
1327 apr_pool_t *iterpool;
1328 apr_pool_t *last_pool;
1329 apr_hash_t *last_props;
1330 const char *last_path;
1331 svn_fs_root_t *last_root;
1332 svn_boolean_t include_merged_revisions;
1335 /* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1337 static svn_error_t *
1338 send_path_revision(struct path_revision *path_rev,
1340 struct send_baton *sb,
1341 svn_file_rev_handler_t handler,
1342 void *handler_baton)
1344 apr_hash_t *rev_props;
1346 apr_array_header_t *prop_diffs;
1347 svn_fs_root_t *root;
1348 svn_txdelta_stream_t *delta_stream;
1349 svn_txdelta_window_handler_t delta_handler = NULL;
1350 void *delta_baton = NULL;
1351 apr_pool_t *tmp_pool; /* For swapping */
1352 svn_boolean_t contents_changed;
1354 svn_pool_clear(sb->iterpool);
1356 /* Get the revision properties. */
1357 SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
1358 path_rev->revnum, sb->iterpool));
1360 /* Open the revision root. */
1361 SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
1364 /* Get the file's properties for this revision and compute the diffs. */
1365 SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
1367 SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1370 /* Check if the contents *may* have changed. */
1371 if (! sb->last_root)
1373 /* Special case: In the first revision, we always provide a delta. */
1374 contents_changed = TRUE;
1376 else if (sb->include_merged_revisions
1377 && strcmp(sb->last_path, path_rev->path))
1379 /* ### This is a HACK!!!
1380 * Blame -g, in older clients anyways, relies on getting a notification
1381 * whenever the path changes - even if there was no content change.
1383 * TODO: A future release should take an extra parameter and depending
1384 * on that either always send a text delta or only send it if there
1385 * is a difference. */
1386 contents_changed = TRUE;
1390 /* Did the file contents actually change?
1391 * It could e.g. be a property-only change. */
1392 SVN_ERR(svn_fs_contents_different(&contents_changed, sb->last_root,
1393 sb->last_path, root, path_rev->path,
1397 /* We have all we need, give to the handler. */
1398 SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
1399 rev_props, path_rev->merged,
1400 contents_changed ? &delta_handler : NULL,
1401 contents_changed ? &delta_baton : NULL,
1402 prop_diffs, sb->iterpool));
1404 /* Compute and send delta if client asked for it.
1405 Note that this was initialized to NULL, so if !contents_changed,
1406 no deltas will be computed. */
1407 if (delta_handler && delta_handler != svn_delta_noop_window_handler)
1409 /* Get the content delta. */
1410 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
1411 sb->last_root, sb->last_path,
1412 root, path_rev->path,
1415 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1416 delta_handler, delta_baton,
1420 /* Remember root, path and props for next iteration. */
1421 sb->last_root = root;
1422 sb->last_path = path_rev->path;
1423 sb->last_props = props;
1425 /* Swap the pools. */
1426 tmp_pool = sb->iterpool;
1427 sb->iterpool = sb->last_pool;
1428 sb->last_pool = tmp_pool;
1430 return SVN_NO_ERROR;
1433 /* Similar to svn_repos_get_file_revs2() but returns paths while walking
1434 history instead of after collecting all history.
1436 This allows implementing clients to immediately start processing and
1437 stop when they got the information they need. (E.g. all or a specific set
1438 of lines were modified) */
1439 static svn_error_t *
1440 get_file_revs_backwards(svn_repos_t *repos,
1444 svn_repos_authz_func_t authz_read_func,
1445 void *authz_read_baton,
1446 svn_file_rev_handler_t handler,
1447 void *handler_baton,
1448 apr_pool_t *scratch_pool)
1450 apr_pool_t *iterpool, *last_pool;
1451 svn_fs_history_t *history;
1452 svn_fs_root_t *root;
1453 svn_node_kind_t kind;
1454 struct send_baton sb;
1456 /* We switch between two pools while looping and so does the path-rev
1457 handler for actually reported revisions. We do this as we
1458 need just information from last iteration to be available. */
1460 iterpool = svn_pool_create(scratch_pool);
1461 last_pool = svn_pool_create(scratch_pool);
1462 sb.iterpool = svn_pool_create(scratch_pool);
1463 sb.last_pool = svn_pool_create(scratch_pool);
1464 sb.include_merged_revisions = FALSE;
1466 /* We want the first txdelta to be against the empty file. */
1467 sb.last_root = NULL;
1468 sb.last_path = NULL;
1470 /* Create an empty hash table for the first property diff. */
1471 sb.last_props = apr_hash_make(sb.last_pool);
1473 /* The path had better be a file in this revision. */
1474 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1475 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1476 if (kind != svn_node_file)
1477 return svn_error_createf(SVN_ERR_FS_NOT_FILE,
1478 NULL, _("'%s' is not a file in revision %ld"),
1481 /* Open a history object. */
1482 SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool, iterpool));
1485 struct path_revision *path_rev;
1486 svn_revnum_t tmp_revnum;
1487 const char *tmp_path;
1489 svn_pool_clear(iterpool);
1491 /* Fetch the history object to walk through. */
1492 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
1496 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1497 history, iterpool));
1499 /* Check authorization. */
1500 if (authz_read_func)
1502 svn_boolean_t readable;
1503 svn_fs_root_t *tmp_root;
1505 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1507 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1508 authz_read_baton, iterpool));
1513 /* We didn't break, so we must really want this path-rev. */
1514 path_rev = apr_palloc(iterpool, sizeof(*path_rev));
1515 path_rev->path = tmp_path;
1516 path_rev->revnum = tmp_revnum;
1517 path_rev->merged = FALSE;
1519 SVN_ERR(send_path_revision(path_rev, repos, &sb,
1520 handler, handler_baton));
1522 if (path_rev->revnum <= start)
1527 apr_pool_t *tmp_pool = iterpool;
1528 iterpool = last_pool;
1529 last_pool = tmp_pool;
1533 svn_pool_destroy(iterpool);
1534 svn_pool_destroy(last_pool);
1535 svn_pool_destroy(sb.last_pool);
1536 svn_pool_destroy(sb.iterpool);
1538 return SVN_NO_ERROR;
1543 /* We don't yet support sending revisions in reverse order; the caller wait
1544 * until we've traced back through the entire history, and then accept
1545 * them from oldest to youngest. Someday this may change, but in the meantime,
1546 * the general algorithm is thus:
1548 * 1) Trace back through the history of an object, adding each revision
1549 * found to the MAINLINE_PATH_REVISIONS array, marking any which were
1551 * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1552 * merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1553 * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1555 * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1556 * oldest to youngest, interleaving as appropriate. This is implemented
1557 * similar to an insertion sort, but instead of inserting into another
1558 * array, we just call the appropriate handler.
1560 * 2013-02: Added a very simple reverse for mainline only changes. Before this,
1561 * this would return an error (path not found) or just the first
1562 * revision before end.
1565 svn_repos_get_file_revs2(svn_repos_t *repos,
1569 svn_boolean_t include_merged_revisions,
1570 svn_repos_authz_func_t authz_read_func,
1571 void *authz_read_baton,
1572 svn_file_rev_handler_t handler,
1573 void *handler_baton,
1574 apr_pool_t *scratch_pool)
1576 apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
1577 apr_hash_t *duplicate_path_revs;
1578 struct send_baton sb;
1579 int mainline_pos, merged_pos;
1581 if (!SVN_IS_VALID_REVNUM(start)
1582 || !SVN_IS_VALID_REVNUM(end))
1584 svn_revnum_t youngest_rev;
1585 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, repos->fs, scratch_pool));
1587 if (!SVN_IS_VALID_REVNUM(start))
1588 start = youngest_rev;
1589 if (!SVN_IS_VALID_REVNUM(end))
1595 if (include_merged_revisions)
1596 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
1598 return svn_error_trace(
1599 get_file_revs_backwards(repos, path,
1608 /* We switch between two pools while looping, since we need information from
1609 the last iteration to be available. */
1610 sb.iterpool = svn_pool_create(scratch_pool);
1611 sb.last_pool = svn_pool_create(scratch_pool);
1613 /* We want the first txdelta to be against the empty file. */
1614 sb.last_root = NULL;
1615 sb.last_path = NULL;
1617 /* Create an empty hash table for the first property diff. */
1618 sb.last_props = apr_hash_make(sb.last_pool);
1620 /* Inform send_path_revision() whether workarounds / special behavior
1622 sb.include_merged_revisions = include_merged_revisions;
1624 /* Get the revisions we are interested in. */
1625 duplicate_path_revs = apr_hash_make(scratch_pool);
1626 mainline_path_revisions = apr_array_make(scratch_pool, 100,
1627 sizeof(struct path_revision *));
1628 SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
1629 start, end, include_merged_revisions,
1630 FALSE, duplicate_path_revs,
1631 authz_read_func, authz_read_baton,
1632 scratch_pool, sb.iterpool));
1634 /* If we are including merged revisions, go get those, too. */
1635 if (include_merged_revisions)
1636 SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
1637 mainline_path_revisions, repos,
1638 duplicate_path_revs, authz_read_func,
1640 scratch_pool, sb.iterpool));
1642 merged_path_revisions = apr_array_make(scratch_pool, 0,
1643 sizeof(struct path_revision *));
1645 /* We must have at least one revision to get. */
1646 SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
1648 /* Walk through both mainline and merged revisions, and send them in
1649 reverse chronological order, interleaving as appropriate. */
1650 mainline_pos = mainline_path_revisions->nelts - 1;
1651 merged_pos = merged_path_revisions->nelts - 1;
1652 while (mainline_pos >= 0 && merged_pos >= 0)
1654 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1656 struct path_revision *);
1657 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1659 struct path_revision *);
1661 if (main_pr->revnum <= merged_pr->revnum)
1663 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1669 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1675 /* Send any remaining revisions from the mainline list. */
1676 for (; mainline_pos >= 0; mainline_pos -= 1)
1678 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1680 struct path_revision *);
1681 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
1684 /* Ditto for the merged list. */
1685 for (; merged_pos >= 0; merged_pos -= 1)
1687 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1689 struct path_revision *);
1690 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1694 svn_pool_destroy(sb.last_pool);
1695 svn_pool_destroy(sb.iterpool);
1697 return SVN_NO_ERROR;