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 than 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_prop2(&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));
91 SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
95 while (rev_bot <= rev_top)
97 rev_mid = (rev_top + rev_bot) / 2;
98 SVN_ERR(get_time(&this_time, fs, rev_mid, pool));
100 if (this_time > tm)/* we've overshot */
102 apr_time_t previous_time;
104 if ((rev_mid - 1) < 0)
110 /* see if time falls between rev_mid and rev_mid-1: */
111 SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool));
112 if (previous_time <= tm)
114 *revision = rev_mid - 1;
118 rev_top = rev_mid - 1;
121 else if (this_time < tm) /* we've undershot */
123 apr_time_t next_time;
125 if ((rev_mid + 1) > rev_latest)
127 *revision = rev_latest;
131 /* see if time falls between rev_mid and rev_mid+1: */
132 SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
139 rev_bot = rev_mid + 1;
144 *revision = rev_mid; /* exact match! */
154 svn_repos_get_committed_info(svn_revnum_t *committed_rev,
155 const char **committed_date,
156 const char **last_author,
161 apr_hash_t *revprops;
163 svn_fs_t *fs = svn_fs_root_fs(root);
165 /* ### It might be simpler just to declare that revision
166 properties have char * (i.e., UTF-8) values, not arbitrary
167 binary values, hmmm. */
168 svn_string_t *committed_date_s, *last_author_s;
170 /* Get the CR field out of the node's skel. */
171 SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
173 /* Get the revision properties of this revision. */
174 SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, *committed_rev, TRUE,
177 /* Extract date and author from these revprops. */
178 committed_date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
179 last_author_s = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
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_history2(&history, root, path, oldpool, 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_prev2(&history, history, cross_copies, newpool,
250 /* Only continue if there is further history to deal with. */
254 /* Fetch the location information for this history step. */
255 SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
258 /* If this history item predates our START revision, quit
260 if (history_rev < start)
263 /* Is the history item readable? If not, quit. */
266 svn_boolean_t readable;
267 svn_fs_root_t *history_root;
268 SVN_ERR(svn_fs_revision_root(&history_root, fs,
269 history_rev, newpool));
270 SVN_ERR(authz_read_func(&readable, history_root, history_path,
271 authz_read_baton, newpool));
276 /* Call the user-provided callback function. */
277 err = history_func(history_baton, history_path, history_rev, newpool);
280 if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
282 svn_error_clear(err);
287 return svn_error_trace(err);
291 /* We're done with the old history item, so we can clear its
292 pool, and then toggle our notion of "the old pool". */
293 svn_pool_clear(oldpool);
298 while (history); /* shouldn't hit this */
301 svn_pool_destroy(oldpool);
302 svn_pool_destroy(newpool);
308 svn_repos_deleted_rev(svn_fs_t *fs,
312 svn_revnum_t *deleted,
315 apr_pool_t *iterpool;
316 svn_fs_root_t *start_root, *root;
317 svn_revnum_t mid_rev;
318 svn_node_kind_t kind;
319 svn_fs_node_relation_t node_relation;
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(&start_root, fs, start, pool));
341 SVN_ERR(svn_fs_check_path(&kind, start_root, path, pool));
342 if (kind == svn_node_none)
344 /* Path must exist in fs at start rev. */
345 *deleted = SVN_INVALID_REVNUM;
349 /* Ensure path was deleted at or before end revision. */
350 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
351 SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
352 if (kind != svn_node_none)
354 /* path exists in the end node and the end node is equivalent
355 or otherwise equivalent to the start node. This can mean
358 1) The end node *is* simply the start node, uncopied
359 and unmodified in the start to end range.
361 2) The start node was modified, but never copied.
363 3) The start node was copied, but this copy occurred at
364 start or some rev *previous* to start, this is
365 effectively the same situation as 1 if the node was
366 never modified or 2 if it was.
368 In the first three cases the path was not deleted in
369 the specified range and we are done, but in the following
370 cases the start node must have been deleted at least once:
372 4) The start node was deleted and replaced by a copy of
373 itself at some rev between start and end. This copy
374 may itself have been replaced with copies of itself.
376 5) The start node was deleted and replaced by a node which
377 it does not share any history with.
379 SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path,
381 if (node_relation != svn_fs_node_unrelated)
383 svn_fs_root_t *copy_root;
384 const char *copy_path;
385 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root,
388 (svn_fs_revision_root_revision(copy_root) <= start))
390 /* Case 1,2 or 3, nothing more to do. */
391 *deleted = SVN_INVALID_REVNUM;
397 /* If we get here we know that path exists in rev start and was deleted
398 at least once before rev end. To find the revision path was first
399 deleted we use a binary search. The rules for the determining if
400 the deletion comes before or after a given median revision are
401 described by this matrix:
403 | Most recent copy event that
404 | caused mid node to exist.
405 |-----------------------------------------------------
407 at start and | Copied at | Copied at | Never copied |
408 mid nodes. | rev > start | rev <= start | |
410 -------------------------------------------------------------------|
411 Mid node is | A) Start node | |
412 equivalent to | replaced with | E) Mid node == start node, |
413 start node | an unmodified | look HIGHER. |
417 -------------------------------------------------------------------|
418 Mid node is | B) Start node | |
419 otherwise | replaced with | F) Mid node is a modified |
420 related to | a modified | version of start node, |
421 start node | copy of | look HIGHER. |
424 -------------------------------------------------------------------|
426 unrelated to | C) Start node replaced with unrelated mid node, |
427 start node | look LOWER. |
429 -------------------------------------------------------------------|
431 exist at mid | D) Start node deleted before mid node, |
434 --------------------------------------------------------------------
437 mid_rev = (start + end) / 2;
438 iterpool = svn_pool_create(pool);
442 svn_pool_clear(iterpool);
444 /* Get revision root and node id for mid_rev at that revision. */
445 SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, iterpool));
446 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool));
447 if (kind == svn_node_none)
449 /* Case D: Look lower in the range. */
451 mid_rev = (start + mid_rev) / 2;
455 svn_fs_root_t *copy_root;
456 const char *copy_path;
457 /* Determine the relationship between the start node
458 and the current node. */
459 SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path,
460 root, path, iterpool));
461 if (node_relation != svn_fs_node_unrelated)
462 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root,
464 if (node_relation == svn_fs_node_unrelated ||
466 (svn_fs_revision_root_revision(copy_root) > start)))
468 /* Cases A, B, C: Look at lower revs. */
470 mid_rev = (start + mid_rev) / 2;
472 else if (end - mid_rev == 1)
474 /* Found the node path was deleted. */
480 /* Cases E, F: Look at higher revs. */
482 mid_rev = (start + end) / 2;
487 svn_pool_destroy(iterpool);
492 /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
495 check_readability(svn_fs_root_t *root,
497 svn_repos_authz_func_t authz_read_func,
498 void *authz_read_baton,
501 svn_boolean_t readable;
502 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
504 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
505 _("Unreadable path encountered; access denied"));
510 /* The purpose of this function is to discover if fs_path@future_rev
511 * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */
514 check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
517 svn_revnum_t peg_revision,
518 svn_revnum_t future_revision,
522 svn_fs_history_t *history;
523 const char *path = NULL;
524 svn_revnum_t revision;
525 apr_pool_t *lastpool, *currpool;
527 lastpool = svn_pool_create(pool);
528 currpool = svn_pool_create(pool);
530 SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool));
532 SVN_ERR(svn_fs_node_history2(&history, root, fs_path, lastpool, lastpool));
534 /* Since paths that are different according to strcmp may still be
535 equivalent (due to number of consecutive slashes and the fact that
536 "" is the same as "/"), we get the "canonical" path in the first
537 iteration below so that the comparison after the loop will work
545 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, currpool,
551 SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
554 fs_path = apr_pstrdup(pool, path);
556 if (revision <= peg_revision)
559 /* Clear old pool and flip. */
560 svn_pool_clear(lastpool);
566 /* We must have had at least one iteration above where we
567 reassigned fs_path. Else, the path wouldn't have existed at
568 future_revision and svn_fs_history would have thrown. */
569 SVN_ERR_ASSERT(fs_path != NULL);
571 *is_ancestor = (history && strcmp(path, fs_path) == 0);
578 svn_repos__prev_location(svn_revnum_t *appeared_rev,
579 const char **prev_path,
580 svn_revnum_t *prev_rev,
582 svn_revnum_t revision,
586 svn_fs_root_t *root, *copy_root;
587 const char *copy_path, *copy_src_path, *remainder;
588 svn_revnum_t copy_src_rev;
590 /* Initialize return variables. */
592 *appeared_rev = SVN_INVALID_REVNUM;
594 *prev_rev = SVN_INVALID_REVNUM;
598 /* Ask about the most recent copy which affected PATH@REVISION. If
599 there was no such copy, we're done. */
600 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
601 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, path, pool));
605 /* Ultimately, it's not the path of the closest copy's source that
606 we care about -- it's our own path's location in the copy source
607 revision. So we'll tack the relative path that expresses the
608 difference between the copy destination and our path in the copy
609 revision onto the copy source path to determine this information.
611 In other words, if our path is "/branches/my-branch/foo/bar", and
612 we know that the closest relevant copy was a copy of "/trunk" to
613 "/branches/my-branch", then that relative path under the copy
614 destination is "/foo/bar". Tacking that onto the copy source
615 path tells us that our path was located at "/trunk/foo/bar"
618 SVN_ERR(svn_fs_copied_from(©_src_rev, ©_src_path,
619 copy_root, copy_path, pool));
620 remainder = svn_fspath__skip_ancestor(copy_path, path);
622 *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
624 *appeared_rev = svn_fs_revision_root_revision(copy_root);
626 *prev_rev = copy_src_rev;
632 svn_repos_trace_node_locations(svn_fs_t *fs,
633 apr_hash_t **locations,
635 svn_revnum_t peg_revision,
636 const apr_array_header_t *location_revisions_orig,
637 svn_repos_authz_func_t authz_read_func,
638 void *authz_read_baton,
641 apr_array_header_t *location_revisions;
642 svn_revnum_t *revision_ptr, *revision_ptr_end;
645 svn_revnum_t revision;
646 svn_boolean_t is_ancestor;
647 apr_pool_t *lastpool, *currpool;
649 SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t));
651 /* Ensure that FS_PATH is absolute, because our path-math below will
652 depend on that being the case. */
654 fs_path = apr_pstrcat(pool, "/", fs_path, SVN_VA_NULL);
656 /* Another sanity check. */
659 svn_fs_root_t *peg_root;
660 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
661 SVN_ERR(check_readability(peg_root, fs_path,
662 authz_read_func, authz_read_baton, pool));
665 *locations = apr_hash_make(pool);
667 /* We flip between two pools in the second loop below. */
668 lastpool = svn_pool_create(pool);
669 currpool = svn_pool_create(pool);
671 /* First - let's sort the array of the revisions from the greatest revision
672 * downward, so it will be easier to search on. */
673 location_revisions = apr_array_copy(pool, location_revisions_orig);
674 svn_sort__array(location_revisions, svn_sort_compare_revisions);
676 revision_ptr = (svn_revnum_t *)location_revisions->elts;
677 revision_ptr_end = revision_ptr + location_revisions->nelts;
679 /* Ignore revisions R that are younger than the peg_revisions where
680 path@peg_revision is not an ancestor of path@R. */
682 while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision)
684 svn_pool_clear(currpool);
685 SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path,
686 peg_revision, *revision_ptr,
693 revision = is_ancestor ? *revision_ptr : peg_revision;
697 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
698 SVN_ERR(check_readability(root, fs_path, authz_read_func,
699 authz_read_baton, pool));
702 while (revision_ptr < revision_ptr_end)
705 svn_revnum_t appeared_rev, prev_rev;
706 const char *prev_path;
708 /* Find the target of the innermost copy relevant to path@revision.
709 The copy may be of path itself, or of a parent directory. */
710 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
711 fs, revision, path, currpool));
715 /* Assign the current path to all younger revisions until we reach
716 the copy target rev. */
717 while ((revision_ptr < revision_ptr_end)
718 && (*revision_ptr >= appeared_rev))
720 /* *revision_ptr is allocated out of pool, so we can point
721 to in the hash table. */
722 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
723 apr_pstrdup(pool, path));
727 /* Ignore all revs between the copy target rev and the copy
728 source rev (non-inclusive). */
729 while ((revision_ptr < revision_ptr_end)
730 && (*revision_ptr > prev_rev))
739 svn_boolean_t readable;
740 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
741 SVN_ERR(authz_read_func(&readable, root, path,
742 authz_read_baton, currpool));
745 svn_pool_destroy(lastpool);
746 svn_pool_destroy(currpool);
751 /* Clear last pool and switch. */
752 svn_pool_clear(lastpool);
758 /* There are no copies relevant to path@revision. So any remaining
759 revisions either predate the creation of path@revision or have
760 the node existing at the same path. We will look up path@lrev
761 for each remaining location-revision and make sure it is related
763 SVN_ERR(svn_fs_revision_root(&root, fs, revision, lastpool));
764 while (revision_ptr < revision_ptr_end)
766 svn_node_kind_t kind;
767 svn_fs_node_relation_t node_relation;
768 svn_fs_root_t *cur_rev_root;
770 svn_pool_clear(currpool);
771 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, *revision_ptr,
773 SVN_ERR(svn_fs_check_path(&kind, cur_rev_root, path, currpool));
774 if (kind == svn_node_none)
776 SVN_ERR(svn_fs_node_relation(&node_relation, root, path,
777 cur_rev_root, path, currpool));
778 if (node_relation == svn_fs_node_unrelated)
781 /* The node exists at the same path; record that and advance. */
782 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
783 apr_pstrdup(pool, path));
787 /* Ignore any remaining location-revisions; they predate the
788 creation of path@revision. */
790 svn_pool_destroy(lastpool);
791 svn_pool_destroy(currpool);
797 /* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
798 its revision range fits between END_REV and START_REV, possibly
799 cropping the range so that it fits *entirely* in that range. */
801 maybe_crop_and_send_segment(svn_location_segment_t *segment,
802 svn_revnum_t start_rev,
803 svn_revnum_t end_rev,
804 svn_location_segment_receiver_t receiver,
805 void *receiver_baton,
808 /* We only want to transmit this segment if some portion of it
809 is between our END_REV and START_REV. */
810 if (! ((segment->range_start > start_rev)
811 || (segment->range_end < end_rev)))
813 /* Correct our segment range when the range straddles one of
814 our requested revision boundaries. */
815 if (segment->range_start < end_rev)
816 segment->range_start = end_rev;
817 if (segment->range_end > start_rev)
818 segment->range_end = start_rev;
819 SVN_ERR(receiver(segment, receiver_baton, pool));
826 svn_repos_node_location_segments(svn_repos_t *repos,
828 svn_revnum_t peg_revision,
829 svn_revnum_t start_rev,
830 svn_revnum_t end_rev,
831 svn_location_segment_receiver_t receiver,
832 void *receiver_baton,
833 svn_repos_authz_func_t authz_read_func,
834 void *authz_read_baton,
837 svn_fs_t *fs = svn_repos_fs(repos);
838 svn_stringbuf_t *current_path;
839 svn_revnum_t youngest_rev, current_rev;
842 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
844 /* No PEG_REVISION? We'll use HEAD. */
845 if (! SVN_IS_VALID_REVNUM(peg_revision))
846 peg_revision = youngest_rev;
848 if (peg_revision > youngest_rev)
849 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
850 _("No such revision %ld"), peg_revision);
852 /* No START_REV? We'll use peg rev. */
853 if (! SVN_IS_VALID_REVNUM(start_rev))
854 start_rev = peg_revision;
855 else if (start_rev > peg_revision)
856 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
857 _("No such revision %ld"), start_rev);
859 /* No END_REV? We'll use 0. */
860 if (! SVN_IS_VALID_REVNUM(end_rev))
862 else if (end_rev > start_rev)
863 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
864 _("No such revision %ld"), end_rev);
866 /* Are the revision properly ordered? They better be -- the API
868 SVN_ERR_ASSERT(end_rev <= start_rev);
869 SVN_ERR_ASSERT(start_rev <= peg_revision);
871 /* Ensure that PATH is absolute, because our path-math will depend
872 on that being the case. */
874 path = apr_pstrcat(pool, "/", path, SVN_VA_NULL);
879 svn_fs_root_t *peg_root;
880 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
881 SVN_ERR(check_readability(peg_root, path,
882 authz_read_func, authz_read_baton, pool));
885 /* Okay, let's get searching! */
886 subpool = svn_pool_create(pool);
887 current_rev = peg_revision;
888 current_path = svn_stringbuf_create(path, pool);
889 while (current_rev >= end_rev)
891 svn_revnum_t appeared_rev, prev_rev;
892 const char *cur_path, *prev_path;
893 svn_location_segment_t *segment;
895 svn_pool_clear(subpool);
897 cur_path = apr_pstrmemdup(subpool, current_path->data,
899 segment = apr_pcalloc(subpool, sizeof(*segment));
900 segment->range_end = current_rev;
901 segment->range_start = end_rev;
902 /* segment path should be absolute without leading '/'. */
903 segment->path = cur_path + 1;
905 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
906 fs, current_rev, cur_path, subpool));
908 /* If there are no previous locations for this thing (meaning,
909 it originated at the current path), then we simply need to
910 find its revision of origin to populate our final segment.
911 Otherwise, the APPEARED_REV is the start of current segment's
915 svn_fs_root_t *revroot;
916 SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
917 SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
919 if (segment->range_start < end_rev)
920 segment->range_start = end_rev;
921 current_rev = SVN_INVALID_REVNUM;
925 segment->range_start = appeared_rev;
926 svn_stringbuf_set(current_path, prev_path);
927 current_rev = prev_rev;
930 /* Report our segment, providing it passes authz muster. */
933 svn_boolean_t readable;
934 svn_fs_root_t *cur_rev_root;
936 /* authz_read_func requires path to have a leading slash. */
937 const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
940 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
941 segment->range_end, subpool));
942 SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
943 authz_read_baton, subpool));
948 /* Transmit the segment (if it's within the scope of our concern). */
949 SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
950 receiver, receiver_baton, subpool));
952 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
953 (and didn't ever reach END_REV). */
954 if (! SVN_IS_VALID_REVNUM(current_rev))
957 /* If there's a gap in the history, we need to report as much
958 (if the gap is within the scope of our concern). */
959 if (segment->range_start - current_rev > 1)
961 svn_location_segment_t *gap_segment;
962 gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
963 gap_segment->range_end = segment->range_start - 1;
964 gap_segment->range_start = current_rev + 1;
965 gap_segment->path = NULL;
966 SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
967 receiver, receiver_baton,
971 svn_pool_destroy(subpool);
975 static APR_INLINE svn_boolean_t
976 is_path_in_hash(apr_hash_t *duplicate_path_revs,
978 svn_revnum_t revision,
981 const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
984 ptr = svn_hash_gets(duplicate_path_revs, key);
993 /* Does this path_rev have merges to also be included? If so, this is
994 the union of both additions and (negated) deletions of mergeinfo. */
995 apr_hash_t *merged_mergeinfo;
997 /* Is this a merged revision? */
998 svn_boolean_t merged;
1001 /* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store
1002 the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The
1003 difference is the union of both additions and (negated) deletions. The
1004 returned *MERGED_MERGEINFO will be NULL if there are no changes. */
1005 static svn_error_t *
1006 get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
1008 struct path_revision *old_path_rev,
1009 apr_pool_t *result_pool,
1010 apr_pool_t *scratch_pool)
1012 apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1014 svn_fs_root_t *root, *prev_root;
1015 const char *start_path = old_path_rev->path;
1016 const char *path = NULL;
1018 svn_fs_path_change_iterator_t *iterator;
1019 svn_fs_path_change3_t *change;
1021 /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
1022 if there is a property change. */
1023 SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
1025 SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
1026 SVN_ERR(svn_fs_path_change_get(&change, iterator));
1028 /* Find the changed PATH closest to START_PATH which may have a mergeinfo
1032 if ( change->prop_mod
1033 && change->mergeinfo_mod != svn_tristate_false
1034 && svn_fspath__skip_ancestor(change->path.data, start_path))
1036 if (!path || svn_fspath__skip_ancestor(path, change->path.data))
1037 path = apr_pstrmemdup(scratch_pool, change->path.data,
1041 SVN_ERR(svn_fs_path_change_get(&change, iterator));
1046 *merged_mergeinfo = NULL;
1047 return SVN_NO_ERROR;
1050 /* First, find the mergeinfo difference for old_path_rev->revnum, and
1051 old_path_rev->revnum - 1. */
1052 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1053 because we will filter out unreadable revisions in
1054 find_interesting_revision() */
1055 err = svn_fs__get_mergeinfo_for_path(&curr_mergeinfo,
1056 root, old_path_rev->path,
1057 svn_mergeinfo_inherited, TRUE,
1058 scratch_pool, scratch_pool);
1061 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1063 /* Issue #3896: If invalid mergeinfo is encountered the
1064 best we can do is ignore it and act is if there are
1065 no mergeinfo differences. */
1066 svn_error_clear(err);
1067 *merged_mergeinfo = NULL;
1068 return SVN_NO_ERROR;
1072 return svn_error_trace(err);
1076 SVN_ERR(svn_fs_revision_root(&prev_root, repos->fs, old_path_rev->revnum - 1,
1078 err = svn_fs__get_mergeinfo_for_path(&prev_mergeinfo,
1079 prev_root, old_path_rev->path,
1080 svn_mergeinfo_inherited, TRUE,
1081 scratch_pool, scratch_pool);
1082 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
1083 || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
1085 /* If the path doesn't exist in the previous revision or it does exist
1086 but has invalid mergeinfo (Issue #3896), assume no merges. */
1087 svn_error_clear(err);
1088 *merged_mergeinfo = NULL;
1089 return SVN_NO_ERROR;
1094 /* Then calculate and merge the differences, combining additions and
1095 (negated) deletions as all positive changes in CHANGES. */
1096 SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
1097 curr_mergeinfo, FALSE, result_pool,
1099 SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
1101 /* Store the result. */
1102 if (apr_hash_count(changed))
1103 *merged_mergeinfo = changed;
1105 *merged_mergeinfo = NULL;
1107 return SVN_NO_ERROR;
1110 static svn_error_t *
1111 find_interesting_revisions(apr_array_header_t *path_revisions,
1116 svn_boolean_t include_merged_revisions,
1117 svn_boolean_t mark_as_merged,
1118 apr_hash_t *duplicate_path_revs,
1119 svn_repos_authz_func_t authz_read_func,
1120 void *authz_read_baton,
1121 apr_pool_t *result_pool,
1122 apr_pool_t *scratch_pool)
1124 apr_pool_t *iterpool, *last_pool;
1125 svn_fs_history_t *history;
1126 svn_fs_root_t *root;
1127 svn_node_kind_t kind;
1129 /* We switch between two pools while looping, since we need information from
1130 the last iteration to be available. */
1131 iterpool = svn_pool_create(scratch_pool);
1132 last_pool = svn_pool_create(scratch_pool);
1134 /* The path had better be a file in this revision. */
1135 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1136 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1137 if (kind != svn_node_file)
1138 return svn_error_createf
1139 (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
1142 /* Open a history object. */
1143 SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool,
1147 struct path_revision *path_rev;
1148 svn_revnum_t tmp_revnum;
1149 const char *tmp_path;
1150 apr_pool_t *tmp_pool;
1152 svn_pool_clear(iterpool);
1154 /* Fetch the history object to walk through. */
1155 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
1159 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1160 history, iterpool));
1162 /* Check to see if we already saw this path (and it's ancestors) */
1163 if (include_merged_revisions
1164 && is_path_in_hash(duplicate_path_revs, tmp_path,
1165 tmp_revnum, iterpool))
1168 /* Check authorization. */
1169 if (authz_read_func)
1171 svn_boolean_t readable;
1172 svn_fs_root_t *tmp_root;
1174 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1176 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1177 authz_read_baton, iterpool));
1182 /* We didn't break, so we must really want this path-rev. */
1183 path_rev = apr_palloc(result_pool, sizeof(*path_rev));
1184 path_rev->path = apr_pstrdup(result_pool, tmp_path);
1185 path_rev->revnum = tmp_revnum;
1186 path_rev->merged = mark_as_merged;
1187 APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
1189 if (include_merged_revisions)
1190 SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
1191 path_rev, result_pool, iterpool));
1193 path_rev->merged_mergeinfo = NULL;
1195 /* Add the path/rev pair to the hash, so we can filter out future
1196 occurrences of it. We only care about this if including merged
1197 revisions, 'cause that's the only time we can have duplicates. */
1198 svn_hash_sets(duplicate_path_revs,
1199 apr_psprintf(result_pool, "%s:%ld", path_rev->path,
1201 (void *)0xdeadbeef);
1203 if (path_rev->revnum <= start)
1207 tmp_pool = iterpool;
1208 iterpool = last_pool;
1209 last_pool = tmp_pool;
1212 svn_pool_destroy(iterpool);
1213 svn_pool_destroy(last_pool);
1215 return SVN_NO_ERROR;
1218 /* Comparison function to sort path/revisions in increasing order */
1220 compare_path_revisions(const void *a, const void *b)
1222 struct path_revision *a_pr = *(struct path_revision *const *)a;
1223 struct path_revision *b_pr = *(struct path_revision *const *)b;
1225 if (a_pr->revnum == b_pr->revnum)
1228 return a_pr->revnum < b_pr->revnum ? 1 : -1;
1231 static svn_error_t *
1232 find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
1234 const apr_array_header_t *mainline_path_revisions,
1236 apr_hash_t *duplicate_path_revs,
1237 svn_repos_authz_func_t authz_read_func,
1238 void *authz_read_baton,
1239 apr_pool_t *result_pool,
1240 apr_pool_t *scratch_pool)
1242 const apr_array_header_t *old;
1243 apr_array_header_t *new_merged_path_revs;
1244 apr_pool_t *iterpool, *last_pool;
1245 apr_array_header_t *merged_path_revisions =
1246 apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
1248 old = mainline_path_revisions;
1249 iterpool = svn_pool_create(scratch_pool);
1250 last_pool = svn_pool_create(scratch_pool);
1255 apr_pool_t *temp_pool;
1257 svn_pool_clear(iterpool);
1258 new_merged_path_revs = apr_array_make(iterpool, 0,
1259 sizeof(struct path_revision *));
1261 /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather
1262 path_revisions for any merged revisions, and store those in NEW. */
1263 for (i = 0; i < old->nelts; i++)
1265 apr_pool_t *iterpool2;
1266 apr_hash_index_t *hi;
1267 struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
1268 struct path_revision *);
1269 if (!old_pr->merged_mergeinfo)
1272 iterpool2 = svn_pool_create(iterpool);
1274 /* Determine and trace the merge sources. */
1275 for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
1276 hi = apr_hash_next(hi))
1278 const char *path = apr_hash_this_key(hi);
1279 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
1280 apr_pool_t *iterpool3;
1283 svn_pool_clear(iterpool2);
1284 iterpool3 = svn_pool_create(iterpool2);
1286 for (j = 0; j < rangelist->nelts; j++)
1288 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
1289 svn_merge_range_t *);
1290 svn_node_kind_t kind;
1291 svn_fs_root_t *root;
1293 if (range->end < start)
1296 svn_pool_clear(iterpool3);
1298 SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
1300 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
1301 if (kind != svn_node_file)
1304 /* Search and find revisions to add to the NEW list. */
1305 SVN_ERR(find_interesting_revisions(new_merged_path_revs,
1307 range->start, range->end,
1309 duplicate_path_revs,
1312 result_pool, iterpool3));
1314 svn_pool_destroy(iterpool3);
1316 svn_pool_destroy(iterpool2);
1319 /* Append the newly found path revisions with the old ones. */
1320 merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
1321 new_merged_path_revs);
1323 /* Swap data structures */
1324 old = new_merged_path_revs;
1325 temp_pool = last_pool;
1326 last_pool = iterpool;
1327 iterpool = temp_pool;
1329 while (new_merged_path_revs->nelts > 0);
1331 /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1332 svn_sort__array(merged_path_revisions, compare_path_revisions);
1334 /* Copy to the output array. */
1335 *merged_path_revisions_out = apr_array_copy(result_pool,
1336 merged_path_revisions);
1338 svn_pool_destroy(iterpool);
1339 svn_pool_destroy(last_pool);
1341 return SVN_NO_ERROR;
1346 apr_pool_t *iterpool;
1347 apr_pool_t *last_pool;
1348 apr_hash_t *last_props;
1349 const char *last_path;
1350 svn_fs_root_t *last_root;
1351 svn_boolean_t include_merged_revisions;
1354 /* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1356 static svn_error_t *
1357 send_path_revision(struct path_revision *path_rev,
1359 struct send_baton *sb,
1360 svn_file_rev_handler_t handler,
1361 void *handler_baton)
1363 apr_hash_t *rev_props;
1365 apr_array_header_t *prop_diffs;
1366 svn_fs_root_t *root;
1367 svn_txdelta_stream_t *delta_stream;
1368 svn_txdelta_window_handler_t delta_handler = NULL;
1369 void *delta_baton = NULL;
1370 apr_pool_t *tmp_pool; /* For swapping */
1371 svn_boolean_t contents_changed;
1372 svn_boolean_t props_changed;
1374 svn_pool_clear(sb->iterpool);
1376 /* Get the revision properties. */
1377 SVN_ERR(svn_fs_revision_proplist2(&rev_props, repos->fs,
1378 path_rev->revnum, FALSE,
1379 sb->iterpool, sb->iterpool));
1381 /* Open the revision root. */
1382 SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
1385 /* Check if the props *may* have changed. */
1388 /* We don't use svn_fs_props_different() because it's more
1390 SVN_ERR(svn_fs_props_changed(&props_changed,
1391 sb->last_root, sb->last_path,
1392 root, path_rev->path, sb->iterpool));
1396 props_changed = TRUE;
1399 /* Calculate actual difference between last and current properties. */
1402 /* Get the file's properties for this revision and compute the diffs. */
1403 SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
1405 SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1410 /* Properties didn't change: copy LAST_PROPS to current POOL. */
1411 props = svn_prop_hash_dup(sb->last_props, sb->iterpool);
1412 prop_diffs = apr_array_make(sb->iterpool, 0, sizeof(svn_prop_t));
1415 /* Check if the contents *may* have changed. */
1416 if (! sb->last_root)
1418 /* Special case: In the first revision, we always provide a delta. */
1419 contents_changed = TRUE;
1421 else if (sb->include_merged_revisions
1422 && strcmp(sb->last_path, path_rev->path))
1424 /* ### This is a HACK!!!
1425 * Blame -g, in older clients anyways, relies on getting a notification
1426 * whenever the path changes - even if there was no content change.
1428 * TODO: A future release should take an extra parameter and depending
1429 * on that either always send a text delta or only send it if there
1430 * is a difference. */
1431 contents_changed = TRUE;
1435 /* Did the file contents actually change?
1436 * It could e.g. be a property-only change. */
1437 SVN_ERR(svn_fs_contents_different(&contents_changed, sb->last_root,
1438 sb->last_path, root, path_rev->path,
1442 /* We have all we need, give to the handler. */
1443 SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
1444 rev_props, path_rev->merged,
1445 contents_changed ? &delta_handler : NULL,
1446 contents_changed ? &delta_baton : NULL,
1447 prop_diffs, sb->iterpool));
1449 /* Compute and send delta if client asked for it.
1450 Note that this was initialized to NULL, so if !contents_changed,
1451 no deltas will be computed. */
1452 if (delta_handler && delta_handler != svn_delta_noop_window_handler)
1454 /* Get the content delta. */
1455 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
1456 sb->last_root, sb->last_path,
1457 root, path_rev->path,
1460 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1461 delta_handler, delta_baton,
1465 /* Remember root, path and props for next iteration. */
1466 sb->last_root = root;
1467 sb->last_path = path_rev->path;
1468 sb->last_props = props;
1470 /* Swap the pools. */
1471 tmp_pool = sb->iterpool;
1472 sb->iterpool = sb->last_pool;
1473 sb->last_pool = tmp_pool;
1475 return SVN_NO_ERROR;
1478 /* Similar to svn_repos_get_file_revs2() but returns paths while walking
1479 history instead of after collecting all history.
1481 This allows implementing clients to immediately start processing and
1482 stop when they got the information they need. (E.g. all or a specific set
1483 of lines were modified) */
1484 static svn_error_t *
1485 get_file_revs_backwards(svn_repos_t *repos,
1489 svn_repos_authz_func_t authz_read_func,
1490 void *authz_read_baton,
1491 svn_file_rev_handler_t handler,
1492 void *handler_baton,
1493 apr_pool_t *scratch_pool)
1495 apr_pool_t *iterpool, *last_pool;
1496 svn_fs_history_t *history;
1497 svn_fs_root_t *root;
1498 svn_node_kind_t kind;
1499 struct send_baton sb;
1501 /* We switch between two pools while looping and so does the path-rev
1502 handler for actually reported revisions. We do this as we
1503 need just information from last iteration to be available. */
1505 iterpool = svn_pool_create(scratch_pool);
1506 last_pool = svn_pool_create(scratch_pool);
1507 sb.iterpool = svn_pool_create(scratch_pool);
1508 sb.last_pool = svn_pool_create(scratch_pool);
1509 sb.include_merged_revisions = FALSE;
1511 /* We want the first txdelta to be against the empty file. */
1512 sb.last_root = NULL;
1513 sb.last_path = NULL;
1515 /* Create an empty hash table for the first property diff. */
1516 sb.last_props = apr_hash_make(sb.last_pool);
1518 /* The path had better be a file in this revision. */
1519 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1520 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1521 if (kind != svn_node_file)
1522 return svn_error_createf(SVN_ERR_FS_NOT_FILE,
1523 NULL, _("'%s' is not a file in revision %ld"),
1526 /* Open a history object. */
1527 SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool, iterpool));
1530 struct path_revision *path_rev;
1531 svn_revnum_t tmp_revnum;
1532 const char *tmp_path;
1534 svn_pool_clear(iterpool);
1536 /* Fetch the history object to walk through. */
1537 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
1541 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1542 history, iterpool));
1544 /* Check authorization. */
1545 if (authz_read_func)
1547 svn_boolean_t readable;
1548 svn_fs_root_t *tmp_root;
1550 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1552 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1553 authz_read_baton, iterpool));
1558 /* We didn't break, so we must really want this path-rev. */
1559 path_rev = apr_palloc(iterpool, sizeof(*path_rev));
1560 path_rev->path = tmp_path;
1561 path_rev->revnum = tmp_revnum;
1562 path_rev->merged = FALSE;
1564 SVN_ERR(send_path_revision(path_rev, repos, &sb,
1565 handler, handler_baton));
1567 if (path_rev->revnum <= start)
1572 apr_pool_t *tmp_pool = iterpool;
1573 iterpool = last_pool;
1574 last_pool = tmp_pool;
1578 svn_pool_destroy(iterpool);
1579 svn_pool_destroy(last_pool);
1580 svn_pool_destroy(sb.last_pool);
1581 svn_pool_destroy(sb.iterpool);
1583 return SVN_NO_ERROR;
1588 /* We don't yet support sending revisions in reverse order; the caller wait
1589 * until we've traced back through the entire history, and then accept
1590 * them from oldest to youngest. Someday this may change, but in the meantime,
1591 * the general algorithm is thus:
1593 * 1) Trace back through the history of an object, adding each revision
1594 * found to the MAINLINE_PATH_REVISIONS array, marking any which were
1596 * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1597 * merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1598 * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1600 * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1601 * oldest to youngest, interleaving as appropriate. This is implemented
1602 * similar to an insertion sort, but instead of inserting into another
1603 * array, we just call the appropriate handler.
1605 * 2013-02: Added a very simple reverse for mainline only changes. Before this,
1606 * this would return an error (path not found) or just the first
1607 * revision before end.
1610 svn_repos_get_file_revs2(svn_repos_t *repos,
1614 svn_boolean_t include_merged_revisions,
1615 svn_repos_authz_func_t authz_read_func,
1616 void *authz_read_baton,
1617 svn_file_rev_handler_t handler,
1618 void *handler_baton,
1619 apr_pool_t *scratch_pool)
1621 apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
1622 apr_hash_t *duplicate_path_revs;
1623 struct send_baton sb;
1624 int mainline_pos, merged_pos;
1626 if (!SVN_IS_VALID_REVNUM(start)
1627 || !SVN_IS_VALID_REVNUM(end))
1629 svn_revnum_t youngest_rev;
1630 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, repos->fs, scratch_pool));
1632 if (!SVN_IS_VALID_REVNUM(start))
1633 start = youngest_rev;
1634 if (!SVN_IS_VALID_REVNUM(end))
1638 /* Make sure we catch up on the latest revprop changes. This is the only
1639 * time we will refresh the revprop data in this query. */
1640 SVN_ERR(svn_fs_refresh_revision_props(repos->fs, scratch_pool));
1644 if (include_merged_revisions)
1645 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
1647 return svn_error_trace(
1648 get_file_revs_backwards(repos, path,
1657 /* We switch between two pools while looping, since we need information from
1658 the last iteration to be available. */
1659 sb.iterpool = svn_pool_create(scratch_pool);
1660 sb.last_pool = svn_pool_create(scratch_pool);
1662 /* We want the first txdelta to be against the empty file. */
1663 sb.last_root = NULL;
1664 sb.last_path = NULL;
1666 /* Create an empty hash table for the first property diff. */
1667 sb.last_props = apr_hash_make(sb.last_pool);
1669 /* Inform send_path_revision() whether workarounds / special behavior
1671 sb.include_merged_revisions = include_merged_revisions;
1673 /* Get the revisions we are interested in. */
1674 duplicate_path_revs = apr_hash_make(scratch_pool);
1675 mainline_path_revisions = apr_array_make(scratch_pool, 100,
1676 sizeof(struct path_revision *));
1677 SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
1678 start, end, include_merged_revisions,
1679 FALSE, duplicate_path_revs,
1680 authz_read_func, authz_read_baton,
1681 scratch_pool, sb.iterpool));
1683 /* If we are including merged revisions, go get those, too. */
1684 if (include_merged_revisions)
1685 SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
1686 mainline_path_revisions, repos,
1687 duplicate_path_revs, authz_read_func,
1689 scratch_pool, sb.iterpool));
1691 merged_path_revisions = apr_array_make(scratch_pool, 0,
1692 sizeof(struct path_revision *));
1694 /* We must have at least one revision to get. */
1695 SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
1697 /* Walk through both mainline and merged revisions, and send them in
1698 reverse chronological order, interleaving as appropriate. */
1699 mainline_pos = mainline_path_revisions->nelts - 1;
1700 merged_pos = merged_path_revisions->nelts - 1;
1701 while (mainline_pos >= 0 && merged_pos >= 0)
1703 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1705 struct path_revision *);
1706 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1708 struct path_revision *);
1710 if (main_pr->revnum <= merged_pr->revnum)
1712 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1718 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1724 /* Send any remaining revisions from the mainline list. */
1725 for (; mainline_pos >= 0; mainline_pos -= 1)
1727 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1729 struct path_revision *);
1730 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
1733 /* Ditto for the merged list. */
1734 for (; merged_pos >= 0; merged_pos -= 1)
1736 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1738 struct path_revision *);
1739 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1743 svn_pool_destroy(sb.last_pool);
1744 svn_pool_destroy(sb.iterpool);
1746 return SVN_NO_ERROR;