1 /* rev_hunt.c --- routines to hunt down particular fs revisions and
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include "svn_compat.h"
22 #include "svn_private_config.h"
23 #include "svn_pools.h"
24 #include "svn_error.h"
25 #include "svn_error_codes.h"
27 #include "svn_repos.h"
28 #include "svn_string.h"
30 #include "svn_sorts.h"
32 #include "svn_props.h"
33 #include "svn_mergeinfo.h"
39 find_interesting_revisions(apr_array_header_t
*path_revisions
,
44 svn_boolean_t include_merged_revisions
,
45 svn_boolean_t mark_as_merged
,
46 svn_repos_authz_func_t authz_read_func
,
47 void *authz_read_baton
,
51 /* Note: this binary search assumes that the datestamp properties on
52 each revision are in chronological order. That is if revision A >
53 revision B, then A's datestamp is younger then B's datestamp.
55 If someone comes along and sets a bogus datestamp, this routine
58 ### todo: you know, we *could* have svn_fs_change_rev_prop() do
59 some semantic checking when it's asked to change special reserved
60 svn: properties. It could prevent such a problem. */
63 /* helper for svn_repos_dated_revision().
65 Set *TM to the apr_time_t datestamp on revision REV in FS. */
67 get_time(apr_time_t
*tm
,
72 svn_string_t
*date_str
;
74 SVN_ERR(svn_fs_revision_prop(&date_str
, fs
, rev
, SVN_PROP_REVISION_DATE
,
77 return svn_error_createf
78 (SVN_ERR_FS_GENERAL
, NULL
,
79 _("Failed to find time on revision %ld"), rev
);
81 SVN_ERR(svn_time_from_cstring(tm
, date_str
->data
, pool
));
88 svn_repos_dated_revision(svn_revnum_t
*revision
,
93 svn_revnum_t rev_mid
, rev_top
, rev_bot
, rev_latest
;
95 svn_fs_t
*fs
= repos
->fs
;
97 /* Initialize top and bottom values of binary search. */
98 SVN_ERR(svn_fs_youngest_rev(&rev_latest
, fs
, pool
));
100 rev_top
= rev_latest
;
102 while (rev_bot
<= rev_top
)
104 rev_mid
= (rev_top
+ rev_bot
) / 2;
105 SVN_ERR(get_time(&this_time
, fs
, rev_mid
, pool
));
107 if (this_time
> tm
)/* we've overshot */
109 apr_time_t previous_time
;
111 if ((rev_mid
- 1) < 0)
117 /* see if time falls between rev_mid and rev_mid-1: */
118 SVN_ERR(get_time(&previous_time
, fs
, rev_mid
- 1, pool
));
119 if (previous_time
<= tm
)
121 *revision
= rev_mid
- 1;
125 rev_top
= rev_mid
- 1;
128 else if (this_time
< tm
) /* we've undershot */
130 apr_time_t next_time
;
132 if ((rev_mid
+ 1) > rev_latest
)
134 *revision
= rev_latest
;
138 /* see if time falls between rev_mid and rev_mid+1: */
139 SVN_ERR(get_time(&next_time
, fs
, rev_mid
+ 1, pool
));
146 rev_bot
= rev_mid
+ 1;
151 *revision
= rev_mid
; /* exact match! */
161 svn_repos_get_committed_info(svn_revnum_t
*committed_rev
,
162 const char **committed_date
,
163 const char **last_author
,
168 svn_fs_t
*fs
= svn_fs_root_fs(root
);
170 /* ### It might be simpler just to declare that revision
171 properties have char * (i.e., UTF-8) values, not arbitrary
172 binary values, hmmm. */
173 svn_string_t
*committed_date_s
, *last_author_s
;
175 /* Get the CR field out of the node's skel. */
176 SVN_ERR(svn_fs_node_created_rev(committed_rev
, root
, path
, pool
));
178 /* Get the date property of this revision. */
179 SVN_ERR(svn_fs_revision_prop(&committed_date_s
, fs
, *committed_rev
,
180 SVN_PROP_REVISION_DATE
, pool
));
182 /* Get the author property of this revision. */
183 SVN_ERR(svn_fs_revision_prop(&last_author_s
, fs
, *committed_rev
,
184 SVN_PROP_REVISION_AUTHOR
, pool
));
186 *committed_date
= committed_date_s
? committed_date_s
->data
: NULL
;
187 *last_author
= last_author_s
? last_author_s
->data
: NULL
;
195 svn_repos_history(svn_fs_t
*fs
,
197 svn_repos_history_func_t history_func
,
201 svn_boolean_t cross_copies
,
204 return svn_repos_history2(fs
, path
, history_func
, history_baton
,
206 start
, end
, cross_copies
, pool
);
212 svn_repos_history2(svn_fs_t
*fs
,
214 svn_repos_history_func_t history_func
,
216 svn_repos_authz_func_t authz_read_func
,
217 void *authz_read_baton
,
220 svn_boolean_t cross_copies
,
223 svn_fs_history_t
*history
;
224 apr_pool_t
*oldpool
= svn_pool_create(pool
);
225 apr_pool_t
*newpool
= svn_pool_create(pool
);
226 const char *history_path
;
227 svn_revnum_t history_rev
;
230 /* Validate the revisions. */
231 if (! SVN_IS_VALID_REVNUM(start
))
232 return svn_error_createf
233 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
234 _("Invalid start revision %ld"), start
);
235 if (! SVN_IS_VALID_REVNUM(end
))
236 return svn_error_createf
237 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
238 _("Invalid end revision %ld"), end
);
240 /* Ensure that the input is ordered. */
243 svn_revnum_t tmprev
= start
;
248 /* Get a revision root for END, and an initial HISTORY baton. */
249 SVN_ERR(svn_fs_revision_root(&root
, fs
, end
, pool
));
253 svn_boolean_t readable
;
254 SVN_ERR(authz_read_func(&readable
, root
, path
,
255 authz_read_baton
, pool
));
257 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE
, NULL
, NULL
);
260 SVN_ERR(svn_fs_node_history(&history
, root
, path
, oldpool
));
262 /* Now, we loop over the history items, calling svn_fs_history_prev(). */
265 /* Note that we have to do some crazy pool work here. We can't
266 get rid of the old history until we use it to get the new, so
267 we alternate back and forth between our subpools. */
271 SVN_ERR(svn_fs_history_prev(&history
, history
, cross_copies
, newpool
));
273 /* Only continue if there is further history to deal with. */
277 /* Fetch the location information for this history step. */
278 SVN_ERR(svn_fs_history_location(&history_path
, &history_rev
,
281 /* If this history item predates our START revision, quit
283 if (history_rev
< start
)
286 /* Is the history item readable? If not, quit. */
289 svn_boolean_t readable
;
290 svn_fs_root_t
*history_root
;
291 SVN_ERR(svn_fs_revision_root(&history_root
, fs
,
292 history_rev
, newpool
));
293 SVN_ERR(authz_read_func(&readable
, history_root
, history_path
,
294 authz_read_baton
, newpool
));
299 /* Call the user-provided callback function. */
300 err
= history_func(history_baton
, history_path
, history_rev
, newpool
);
303 if (err
->apr_err
== SVN_ERR_CEASE_INVOCATION
)
305 svn_error_clear(err
);
314 /* We're done with the old history item, so we can clear its
315 pool, and then toggle our notion of "the old pool". */
316 svn_pool_clear(oldpool
);
321 while (history
); /* shouldn't hit this */
324 svn_pool_destroy(oldpool
);
325 svn_pool_destroy(newpool
);
331 svn_repos_deleted_rev(svn_fs_t
*fs
,
335 svn_revnum_t
*deleted
,
339 svn_fs_root_t
*root
, *copy_root
;
340 const char *copy_path
;
341 svn_revnum_t mid_rev
;
342 const svn_fs_id_t
*start_node_id
, *curr_node_id
;
345 /* Validate the revision range. */
346 if (! SVN_IS_VALID_REVNUM(start
))
347 return svn_error_createf
348 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
349 _("Invalid start revision %ld"), start
);
350 if (! SVN_IS_VALID_REVNUM(end
))
351 return svn_error_createf
352 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
353 _("Invalid end revision %ld"), end
);
355 /* Ensure that the input is ordered. */
358 svn_revnum_t tmprev
= start
;
363 /* Ensure path exists in fs at start revision. */
364 SVN_ERR(svn_fs_revision_root(&root
, fs
, start
, pool
));
365 err
= svn_fs_node_id(&start_node_id
, root
, path
, pool
);
368 if (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
370 /* Path must exist in fs at start rev. */
371 *deleted
= SVN_INVALID_REVNUM
;
372 svn_error_clear(err
);
378 /* Ensure path was deleted at or before end revision. */
379 SVN_ERR(svn_fs_revision_root(&root
, fs
, end
, pool
));
380 err
= svn_fs_node_id(&curr_node_id
, root
, path
, pool
);
381 if (err
&& err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
383 svn_error_clear(err
);
391 /* path exists in the end node and the end node is equivalent
392 or otherwise equivalent to the start node. This can mean
395 1) The end node *is* simply the start node, uncopied
396 and unmodified in the start to end range.
398 2) The start node was modified, but never copied.
400 3) The start node was copied, but this copy occurred at
401 start or some rev *previous* to start, this is
402 effectively the same situation as 1 if the node was
403 never modified or 2 if it was.
405 In the first three cases the path was not deleted in
406 the specified range and we are done, but in the following
407 cases the start node must have been deleted at least once:
409 4) The start node was deleted and replaced by a copy of
410 itself at some rev between start and end. This copy
411 may itself have been replaced with copies of itself.
413 5) The start node was deleted and replaced by a node which
414 it does not share any history with.
416 SVN_ERR(svn_fs_node_id(&curr_node_id
, root
, path
, pool
));
417 if (svn_fs_compare_ids(start_node_id
, curr_node_id
) != -1)
419 SVN_ERR(svn_fs_closest_copy(©_root
, ©_path
, root
,
422 (svn_fs_revision_root_revision(copy_root
) <= start
))
424 /* Case 1,2 or 3, nothing more to do. */
425 *deleted
= SVN_INVALID_REVNUM
;
431 /* If we get here we know that path exists in rev start and was deleted
432 at least once before rev end. To find the revision path was first
433 deleted we use a binary search. The rules for the determining if
434 the deletion comes before or after a given median revision are
435 described by this matrix:
437 | Most recent copy event that
438 | caused mid node to exist.
439 |-----------------------------------------------------
441 at start and | Copied at | Copied at | Never copied |
442 mid nodes. | rev > start | rev <= start | |
444 -------------------------------------------------------------------|
445 Mid node is | A) Start node | |
446 equivalent to | replaced with | E) Mid node == start node, |
447 start node | an unmodified | look HIGHER. |
451 -------------------------------------------------------------------|
452 Mid node is | B) Start node | |
453 otherwise | replaced with | F) Mid node is a modified |
454 related to | a modified | version of start node, |
455 start node | copy of | look HIGHER. |
458 -------------------------------------------------------------------|
460 unrelated to | C) Start node replaced with unrelated mid node, |
461 start node | look LOWER. |
463 -------------------------------------------------------------------|
465 exist at mid | D) Start node deleted before mid node, |
468 --------------------------------------------------------------------
471 mid_rev
= (start
+ end
) / 2;
472 subpool
= svn_pool_create(pool
);
476 svn_pool_clear(subpool
);
478 /* Get revision root and node id for mid_rev at that revision. */
479 SVN_ERR(svn_fs_revision_root(&root
, fs
, mid_rev
, subpool
));
480 err
= svn_fs_node_id(&curr_node_id
, root
, path
, subpool
);
484 if (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
486 /* Case D: Look lower in the range. */
487 svn_error_clear(err
);
489 mid_rev
= (start
+ mid_rev
) / 2;
496 /* Determine the relationship between the start node
497 and the current node. */
498 int cmp
= svn_fs_compare_ids(start_node_id
, curr_node_id
);
499 SVN_ERR(svn_fs_closest_copy(©_root
, ©_path
, root
,
503 (svn_fs_revision_root_revision(copy_root
) > start
)))
505 /* Cases A, B, C: Look at lower revs. */
507 mid_rev
= (start
+ mid_rev
) / 2;
509 else if (end
- mid_rev
== 1)
511 /* Found the node path was deleted. */
517 /* Cases E, F: Look at higher revs. */
519 mid_rev
= (start
+ end
) / 2;
524 svn_pool_destroy(subpool
);
529 /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
532 check_readability(svn_fs_root_t
*root
,
534 svn_repos_authz_func_t authz_read_func
,
535 void *authz_read_baton
,
538 svn_boolean_t readable
;
539 SVN_ERR(authz_read_func(&readable
, root
, path
, authz_read_baton
, pool
));
541 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE
, NULL
,
542 _("Unreadable path encountered; access denied"));
547 /* The purpose of this function is to discover if fs_path@future_rev
548 * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */
551 check_ancestry_of_peg_path(svn_boolean_t
*is_ancestor
,
554 svn_revnum_t peg_revision
,
555 svn_revnum_t future_revision
,
559 svn_fs_history_t
*history
;
561 svn_revnum_t revision
;
562 apr_pool_t
*lastpool
, *currpool
;
564 lastpool
= svn_pool_create(pool
);
565 currpool
= svn_pool_create(pool
);
567 SVN_ERR(svn_fs_revision_root(&root
, fs
, future_revision
, pool
));
569 SVN_ERR(svn_fs_node_history(&history
, root
, fs_path
, lastpool
));
571 /* Since paths that are different according to strcmp may still be
572 equivalent (due to number of consecutive slashes and the fact that
573 "" is the same as "/"), we get the "canonical" path in the first
574 iteration below so that the comparison after the loop will work
582 SVN_ERR(svn_fs_history_prev(&history
, history
, TRUE
, currpool
));
587 SVN_ERR(svn_fs_history_location(&path
, &revision
, history
, currpool
));
590 fs_path
= apr_pstrdup(pool
, path
);
592 if (revision
<= peg_revision
)
595 /* Clear old pool and flip. */
596 svn_pool_clear(lastpool
);
602 /* We must have had at least one iteration above where we
603 reassigned fs_path. Else, the path wouldn't have existed at
604 future_revision and svn_fs_history would have thrown. */
605 assert(fs_path
!= NULL
);
607 *is_ancestor
= (history
&& strcmp(path
, fs_path
) == 0);
613 /* Set *PREV_PATH and *PREV_REV to the path and revision which
614 represent the location at which PATH in FS was located immediately
615 prior to REVISION iff there was a copy operation (to PATH or one of
616 its parent directories) between that previous location and
617 PATH@REVISION, and set *APPEARED_REV to the first revision in which
618 PATH@REVISION appeared at PATH as a result of that copy operation.
620 If there was no such copy operation in that portion
621 of PATH's history, set *PREV_PATH to NULL, and set *PREV_REV and
622 *APPEARED_REV to SVN_INVALID_REVNUM. */
624 prev_location(svn_revnum_t
*appeared_rev
,
625 const char **prev_path
,
626 svn_revnum_t
*prev_rev
,
628 svn_revnum_t revision
,
632 svn_fs_root_t
*root
, *copy_root
;
633 const char *copy_path
, *copy_src_path
, *remainder
= "";
634 svn_revnum_t copy_src_rev
;
636 /* Initialize return variables. */
637 *appeared_rev
= *prev_rev
= SVN_INVALID_REVNUM
;
640 /* Ask about the most recent copy which affected PATH@REVISION. If
641 there was no such copy, we're done. */
642 SVN_ERR(svn_fs_revision_root(&root
, fs
, revision
, pool
));
643 SVN_ERR(svn_fs_closest_copy(©_root
, ©_path
, root
, path
, pool
));
647 /* Ultimately, it's not the path of the closest copy's source that
648 we care about -- it's our own path's location in the copy source
649 revision. So we'll tack the relative path that expresses the
650 difference between the copy destination and our path in the copy
651 revision onto the copy source path to determine this information.
653 In other words, if our path is "/branches/my-branch/foo/bar", and
654 we know that the closest relevant copy was a copy of "/trunk" to
655 "/branches/my-branch", then that relative path under the copy
656 destination is "/foo/bar". Tacking that onto the copy source
657 path tells us that our path was located at "/trunk/foo/bar"
660 SVN_ERR(svn_fs_copied_from(©_src_rev
, ©_src_path
,
661 copy_root
, copy_path
, pool
));
662 if (! strcmp(copy_path
, path
) == 0)
663 remainder
= svn_path_is_child(copy_path
, path
, pool
);
664 *prev_path
= svn_path_join(copy_src_path
, remainder
, pool
);
665 *appeared_rev
= svn_fs_revision_root_revision(copy_root
);
666 *prev_rev
= copy_src_rev
;
672 svn_repos_trace_node_locations(svn_fs_t
*fs
,
673 apr_hash_t
**locations
,
675 svn_revnum_t peg_revision
,
676 apr_array_header_t
*location_revisions_orig
,
677 svn_repos_authz_func_t authz_read_func
,
678 void *authz_read_baton
,
681 apr_array_header_t
*location_revisions
;
682 svn_revnum_t
*revision_ptr
, *revision_ptr_end
;
685 svn_revnum_t revision
;
686 svn_boolean_t is_ancestor
;
687 apr_pool_t
*lastpool
, *currpool
;
688 const svn_fs_id_t
*id
;
691 assert(location_revisions_orig
->elt_size
== sizeof(svn_revnum_t
));
693 /* Ensure that FS_PATH is absolute, because our path-math below will
694 depend on that being the case. */
696 fs_path
= apr_pstrcat(pool
, "/", fs_path
, NULL
);
698 /* Another sanity check. */
701 svn_fs_root_t
*peg_root
;
702 SVN_ERR(svn_fs_revision_root(&peg_root
, fs
, peg_revision
, pool
));
703 SVN_ERR(check_readability(peg_root
, fs_path
,
704 authz_read_func
, authz_read_baton
, pool
));
707 *locations
= apr_hash_make(pool
);
709 /* We flip between two pools in the second loop below. */
710 lastpool
= svn_pool_create(pool
);
711 currpool
= svn_pool_create(pool
);
713 /* First - let's sort the array of the revisions from the greatest revision
714 * downward, so it will be easier to search on. */
715 location_revisions
= apr_array_copy(pool
, location_revisions_orig
);
716 qsort(location_revisions
->elts
, location_revisions
->nelts
,
717 sizeof(*revision_ptr
), svn_sort_compare_revisions
);
719 revision_ptr
= (svn_revnum_t
*)location_revisions
->elts
;
720 revision_ptr_end
= revision_ptr
+ location_revisions
->nelts
;
722 /* Ignore revisions R that are younger than the peg_revisions where
723 path@peg_revision is not an ancestor of path@R. */
725 while (revision_ptr
< revision_ptr_end
&& *revision_ptr
> peg_revision
)
727 svn_pool_clear(currpool
);
728 SVN_ERR(check_ancestry_of_peg_path(&is_ancestor
, fs
, fs_path
,
729 peg_revision
, *revision_ptr
,
736 revision
= is_ancestor
? *revision_ptr
: peg_revision
;
740 SVN_ERR(svn_fs_revision_root(&root
, fs
, revision
, pool
));
741 SVN_ERR(check_readability(root
, fs_path
, authz_read_func
,
742 authz_read_baton
, pool
));
745 while (revision_ptr
< revision_ptr_end
)
748 svn_revnum_t appeared_rev
, prev_rev
;
749 const char *prev_path
;
751 /* Find the target of the innermost copy relevant to path@revision.
752 The copy may be of path itself, or of a parent directory. */
753 SVN_ERR(prev_location(&appeared_rev
, &prev_path
, &prev_rev
, fs
,
754 revision
, path
, currpool
));
760 svn_boolean_t readable
;
761 svn_fs_root_t
*tmp_root
;
763 SVN_ERR(svn_fs_revision_root(&tmp_root
, fs
, revision
, currpool
));
764 SVN_ERR(authz_read_func(&readable
, tmp_root
, path
,
765 authz_read_baton
, currpool
));
772 /* Assign the current path to all younger revisions until we reach
773 the copy target rev. */
774 while ((revision_ptr
< revision_ptr_end
)
775 && (*revision_ptr
>= appeared_rev
))
777 /* *revision_ptr is allocated out of pool, so we can point
778 to in the hash table. */
779 apr_hash_set(*locations
, revision_ptr
, sizeof(*revision_ptr
),
780 apr_pstrdup(pool
, path
));
784 /* Ignore all revs between the copy target rev and the copy
785 source rev (non-inclusive). */
786 while ((revision_ptr
< revision_ptr_end
)
787 && (*revision_ptr
> prev_rev
))
794 /* Clear last pool and switch. */
795 svn_pool_clear(lastpool
);
801 /* There are no copies relevant to path@revision. So any remaining
802 revisions either predate the creation of path@revision or have
803 the node existing at the same path. We will look up path@lrev
804 for each remaining location-revision and make sure it is related
806 SVN_ERR(svn_fs_revision_root(&root
, fs
, revision
, currpool
));
807 SVN_ERR(svn_fs_node_id(&id
, root
, path
, pool
));
808 while (revision_ptr
< revision_ptr_end
)
810 svn_node_kind_t kind
;
811 const svn_fs_id_t
*lrev_id
;
813 svn_pool_clear(currpool
);
814 SVN_ERR(svn_fs_revision_root(&root
, fs
, *revision_ptr
, currpool
));
815 SVN_ERR(svn_fs_check_path(&kind
, root
, path
, currpool
));
816 if (kind
== svn_node_none
)
818 SVN_ERR(svn_fs_node_id(&lrev_id
, root
, path
, currpool
));
819 if (! svn_fs_check_related(id
, lrev_id
))
822 /* The node exists at the same path; record that and advance. */
823 apr_hash_set(*locations
, revision_ptr
, sizeof(*revision_ptr
),
824 apr_pstrdup(pool
, path
));
828 /* Ignore any remaining location-revisions; they predate the
829 creation of path@revision. */
831 svn_pool_destroy(lastpool
);
832 svn_pool_destroy(currpool
);
838 /* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
839 its revision range fits between END_REV and START_REV, possibly
840 cropping the range so that it fits *entirely* in that range. */
842 maybe_crop_and_send_segment(svn_location_segment_t
*segment
,
843 svn_revnum_t start_rev
,
844 svn_revnum_t end_rev
,
845 svn_location_segment_receiver_t receiver
,
846 void *receiver_baton
,
849 /* We only want to transmit this segment if some portion of it
850 is between our END_REV and START_REV. */
851 if (! ((segment
->range_start
> start_rev
)
852 || (segment
->range_end
< end_rev
)))
854 /* Correct our segment range when the range straddles one of
855 our requested revision boundaries. */
856 if (segment
->range_start
< end_rev
)
857 segment
->range_start
= end_rev
;
858 if (segment
->range_end
> start_rev
)
859 segment
->range_end
= start_rev
;
860 SVN_ERR(receiver(segment
, receiver_baton
, pool
));
867 svn_repos_node_location_segments(svn_repos_t
*repos
,
869 svn_revnum_t peg_revision
,
870 svn_revnum_t start_rev
,
871 svn_revnum_t end_rev
,
872 svn_location_segment_receiver_t receiver
,
873 void *receiver_baton
,
874 svn_repos_authz_func_t authz_read_func
,
875 void *authz_read_baton
,
878 svn_fs_t
*fs
= svn_repos_fs(repos
);
879 svn_stringbuf_t
*current_path
;
880 svn_revnum_t youngest_rev
= SVN_INVALID_REVNUM
, current_rev
;
883 /* No PEG_REVISION? We'll use HEAD. */
884 if (! SVN_IS_VALID_REVNUM(peg_revision
))
886 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, fs
, pool
));
887 peg_revision
= youngest_rev
;
890 /* No START_REV? We'll use HEAD (which we may have already fetched). */
891 if (! SVN_IS_VALID_REVNUM(start_rev
))
893 if (SVN_IS_VALID_REVNUM(youngest_rev
))
894 start_rev
= youngest_rev
;
896 SVN_ERR(svn_fs_youngest_rev(&start_rev
, fs
, pool
));
899 /* No END_REV? We'll use 0. */
900 end_rev
= SVN_IS_VALID_REVNUM(end_rev
) ? end_rev
: 0;
902 /* Are the revision properly ordered? They better be -- the API
904 assert(end_rev
<= start_rev
);
905 assert(start_rev
<= peg_revision
);
907 /* Ensure that PATH is absolute, because our path-math will depend
908 on that being the case. */
910 path
= apr_pstrcat(pool
, "/", path
, NULL
);
915 svn_fs_root_t
*peg_root
;
916 SVN_ERR(svn_fs_revision_root(&peg_root
, fs
, peg_revision
, pool
));
917 SVN_ERR(check_readability(peg_root
, path
,
918 authz_read_func
, authz_read_baton
, pool
));
921 /* Okay, let's get searching! */
922 subpool
= svn_pool_create(pool
);
923 current_rev
= peg_revision
;
924 current_path
= svn_stringbuf_create(path
, pool
);
925 while (current_rev
>= end_rev
)
927 svn_revnum_t appeared_rev
, prev_rev
;
928 const char *cur_path
, *prev_path
;
929 svn_location_segment_t
*segment
;
931 svn_pool_clear(subpool
);
933 cur_path
= apr_pstrmemdup(subpool
, current_path
->data
,
935 segment
= apr_pcalloc(subpool
, sizeof(*segment
));
936 segment
->range_end
= current_rev
;
937 segment
->range_start
= end_rev
;
938 segment
->path
= cur_path
+ 1;
940 SVN_ERR(prev_location(&appeared_rev
, &prev_path
, &prev_rev
, fs
,
941 current_rev
, cur_path
, subpool
));
943 /* If there are no previous locations for this thing (meaning,
944 it originated at the current path), then we simply need to
945 find its revision of origin to populate our final segment.
946 Otherwise, the APPEARED_REV is the start of current segment's
950 svn_fs_root_t
*revroot
;
951 SVN_ERR(svn_fs_revision_root(&revroot
, fs
, current_rev
, subpool
));
952 SVN_ERR(svn_fs_node_origin_rev(&(segment
->range_start
), revroot
,
954 if (segment
->range_start
< end_rev
)
955 segment
->range_start
= end_rev
;
956 current_rev
= SVN_INVALID_REVNUM
;
960 segment
->range_start
= appeared_rev
;
961 svn_stringbuf_set(current_path
, prev_path
);
962 current_rev
= prev_rev
;
965 /* Report our segment, providing it passes authz muster. */
968 svn_boolean_t readable
;
969 svn_fs_root_t
*cur_rev_root
;
971 SVN_ERR(svn_fs_revision_root(&cur_rev_root
, fs
,
972 segment
->range_end
, subpool
));
973 SVN_ERR(authz_read_func(&readable
, cur_rev_root
, segment
->path
,
974 authz_read_baton
, subpool
));
979 /* Trasmit the segment (if its within the scope of our concern). */
980 SVN_ERR(maybe_crop_and_send_segment(segment
, start_rev
, end_rev
,
981 receiver
, receiver_baton
, subpool
));
983 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
984 (and didn't ever reach END_REV). */
985 if (! SVN_IS_VALID_REVNUM(current_rev
))
988 /* If there's a gap in the history, we need to report as much
989 (if the gap is within the scope of our concern). */
990 if (segment
->range_start
- current_rev
> 1)
992 svn_location_segment_t
*gap_segment
;
993 gap_segment
= apr_pcalloc(subpool
, sizeof(*gap_segment
));
994 gap_segment
->range_end
= segment
->range_start
- 1;
995 gap_segment
->range_start
= current_rev
+ 1;
996 gap_segment
->path
= NULL
;
997 SVN_ERR(maybe_crop_and_send_segment(gap_segment
, start_rev
, end_rev
,
998 receiver
, receiver_baton
,
1002 svn_pool_destroy(subpool
);
1003 return SVN_NO_ERROR
;
1007 struct path_revision
1009 svn_revnum_t revnum
;
1012 /* Merged revision flag. This is set if the path/revision pair is the
1013 result of a merge. */
1014 svn_boolean_t merged_revision
;
1017 /* Check to see if OLD_PATH_REV->PATH was changed as the result of a merge,
1018 and if so, add the merged revision/path pairs to REVISION_PATHS. */
1019 static svn_error_t
*
1020 get_merged_path_revisions(apr_array_header_t
*path_revisions
,
1022 struct path_revision
*old_path_rev
,
1023 svn_repos_authz_func_t authz_read_func
,
1024 void *authz_read_baton
,
1027 apr_pool_t
*subpool
= svn_pool_create(pool
);
1028 apr_hash_t
*curr_mergeinfo
, *prev_mergeinfo
, *deleted
, *changed
;
1029 apr_hash_index_t
*hi
;
1031 /* First, figure out if old_path_rev is a merging revision or not. */
1032 SVN_ERR(svn_repos__get_path_mergeinfo(&curr_mergeinfo
, repos
->fs
,
1034 old_path_rev
->revnum
, subpool
));
1035 SVN_ERR(svn_repos__get_path_mergeinfo(&prev_mergeinfo
, repos
->fs
,
1037 old_path_rev
->revnum
- 1, subpool
));
1038 SVN_ERR(svn_mergeinfo_diff(&deleted
, &changed
, prev_mergeinfo
, curr_mergeinfo
,
1040 SVN_ERR(svn_mergeinfo_merge(changed
, deleted
, subpool
));
1041 if (apr_hash_count(changed
) == 0)
1043 svn_pool_destroy(subpool
);
1044 return SVN_NO_ERROR
;
1047 /* Determine the source of the merge. */
1048 for (hi
= apr_hash_first(pool
, changed
); hi
; hi
= apr_hash_next(hi
))
1050 apr_array_header_t
*rangelist
;
1054 apr_hash_this(hi
, (void *) &path
, NULL
, (void *) &rangelist
);
1056 for (i
= 0; i
< rangelist
->nelts
; i
++)
1059 svn_merge_range_t
*range
= APR_ARRAY_IDX(rangelist
, i
,
1060 svn_merge_range_t
*);
1062 /* Search and find revisions to add to the PATH_REVISIONS list. */
1063 /* TODO: A trace through this area of the code reveals that we are
1064 searching the same path/revision range pairs multiple times. Is
1065 it possible to shortcircuit subsequent searches somehow? */
1066 err
= find_interesting_revisions(path_revisions
, repos
, path
,
1067 range
->start
, range
->end
,
1068 TRUE
, TRUE
, authz_read_func
,
1069 authz_read_baton
, pool
);
1072 if (err
->apr_err
== SVN_ERR_FS_NOT_FILE
)
1073 svn_error_clear(err
);
1081 svn_pool_destroy(subpool
);
1083 return SVN_NO_ERROR
;
1087 static svn_error_t
*
1088 find_interesting_revisions(apr_array_header_t
*path_revisions
,
1093 svn_boolean_t include_merged_revisions
,
1094 svn_boolean_t mark_as_merged
,
1095 svn_repos_authz_func_t authz_read_func
,
1096 void *authz_read_baton
,
1099 apr_pool_t
*iter_pool
, *last_pool
;
1100 svn_fs_history_t
*history
;
1101 svn_fs_root_t
*root
;
1102 svn_node_kind_t kind
;
1104 /* We switch betwwen two pools while looping, since we need information from
1105 the last iteration to be available. */
1106 iter_pool
= svn_pool_create(pool
);
1107 last_pool
= svn_pool_create(pool
);
1109 /* The path had better be a file in this revision. */
1110 SVN_ERR(svn_fs_revision_root(&root
, repos
->fs
, end
, last_pool
));
1111 SVN_ERR(svn_fs_check_path(&kind
, root
, path
, pool
));
1112 if (kind
!= svn_node_file
)
1113 return svn_error_createf
1114 (SVN_ERR_FS_NOT_FILE
, NULL
, _("'%s' is not a file in revision %ld"),
1117 /* Open a history object. */
1118 SVN_ERR(svn_fs_node_history(&history
, root
, path
, last_pool
));
1122 struct path_revision
*path_rev
= apr_palloc(pool
, sizeof(*path_rev
));
1123 apr_pool_t
*tmp_pool
;
1125 svn_pool_clear(iter_pool
);
1127 SVN_ERR(svn_fs_history_prev(&history
, history
, TRUE
, iter_pool
));
1130 SVN_ERR(svn_fs_history_location(&path_rev
->path
, &path_rev
->revnum
,
1131 history
, iter_pool
));
1132 if (authz_read_func
)
1134 svn_boolean_t readable
;
1135 svn_fs_root_t
*tmp_root
;
1137 SVN_ERR(svn_fs_revision_root(&tmp_root
, repos
->fs
, path_rev
->revnum
,
1139 SVN_ERR(authz_read_func(&readable
, tmp_root
, path_rev
->path
,
1140 authz_read_baton
, iter_pool
));
1145 path_rev
->path
= apr_pstrdup(pool
, path_rev
->path
);
1146 path_rev
->merged_revision
= mark_as_merged
;
1147 APR_ARRAY_PUSH(path_revisions
, struct path_revision
*) = path_rev
;
1149 if (include_merged_revisions
)
1151 svn_boolean_t branching_rev
;
1152 svn_fs_root_t
*merge_root
;
1154 SVN_ERR(get_merged_path_revisions(path_revisions
, repos
, path_rev
,
1155 authz_read_func
, authz_read_baton
,
1158 /* Are looking at a branching revision? If so, break. */
1159 SVN_ERR(svn_fs_revision_root(&merge_root
, repos
->fs
,
1162 SVN_ERR(svn_repos__is_branching_copy(&branching_rev
, merge_root
,
1163 path_rev
->path
, NULL
, pool
));
1169 if (path_rev
->revnum
<= start
)
1173 tmp_pool
= iter_pool
;
1174 iter_pool
= last_pool
;
1175 last_pool
= tmp_pool
;
1178 svn_pool_destroy(iter_pool
);
1180 return SVN_NO_ERROR
;
1185 compare_path_revision_revs(const void *a
, const void *b
)
1187 const struct path_revision
*a_path_rev
= *(const struct path_revision
**)a
;
1188 const struct path_revision
*b_path_rev
= *(const struct path_revision
**)b
;
1190 if (a_path_rev
->revnum
== b_path_rev
->revnum
)
1192 int strcmp_result
= strcmp(a_path_rev
->path
, b_path_rev
->path
);
1194 if (strcmp_result
== 0)
1196 if (a_path_rev
->merged_revision
== b_path_rev
->merged_revision
)
1199 return a_path_rev
->merged_revision
== TRUE
? 1 : -1;
1202 return strcmp_result
;
1205 return a_path_rev
->revnum
< b_path_rev
->revnum
? 1 : -1;
1208 static svn_error_t
*
1209 sort_and_scrub_revisions(apr_array_header_t
**path_revisions
,
1213 struct path_revision previous_path_rev
= { 0, NULL
, FALSE
};
1214 apr_array_header_t
*out_path_revisions
= apr_array_make(pool
, 0,
1215 sizeof(struct path_revision
*));
1217 /* Sort the path_revision pairs by revnum in descending order, then path. */
1218 qsort((*path_revisions
)->elts
, (*path_revisions
)->nelts
,
1219 (*path_revisions
)->elt_size
, compare_path_revision_revs
);
1221 /* Filter out duplicat path/revision pairs. Because we ensured that pairs
1222 without the merged_revision flag set are ordered after pair with it set,
1223 the following scrubbing process will prefer path/revision pairs from the
1224 mainline of history, and not the result of a merge. */
1225 for (i
= 0; i
< (*path_revisions
)->nelts
; i
++)
1227 struct path_revision
*path_rev
= APR_ARRAY_IDX(*path_revisions
, i
,
1228 struct path_revision
*);
1230 if ( (previous_path_rev
.revnum
!= path_rev
->revnum
)
1231 || (strcmp(previous_path_rev
.path
, path_rev
->path
) != 0) )
1233 APR_ARRAY_PUSH(out_path_revisions
, struct path_revision
*) = path_rev
;
1236 previous_path_rev
= *path_rev
;
1239 *path_revisions
= out_path_revisions
;
1241 return SVN_NO_ERROR
;
1245 svn_repos_get_file_revs2(svn_repos_t
*repos
,
1249 svn_boolean_t include_merged_revisions
,
1250 svn_repos_authz_func_t authz_read_func
,
1251 void *authz_read_baton
,
1252 svn_file_rev_handler_t handler
,
1253 void *handler_baton
,
1256 apr_pool_t
*iter_pool
, *last_pool
;
1257 apr_array_header_t
*path_revisions
= apr_array_make(pool
, 0,
1258 sizeof(struct path_revision
*));
1259 apr_hash_t
*last_props
;
1260 svn_fs_root_t
*last_root
;
1261 const char *last_path
;
1264 /* We switch betwwen two pools while looping, since we need information from
1265 the last iteration to be available. */
1266 iter_pool
= svn_pool_create(pool
);
1267 last_pool
= svn_pool_create(pool
);
1269 /* Get the revisions we are interested in. */
1270 SVN_ERR(find_interesting_revisions(path_revisions
, repos
, path
, start
, end
,
1271 include_merged_revisions
, FALSE
,
1272 authz_read_func
, authz_read_baton
,
1275 if (include_merged_revisions
)
1276 SVN_ERR(sort_and_scrub_revisions(&path_revisions
, pool
));
1278 /* We must have at least one revision to get. */
1279 assert(path_revisions
->nelts
> 0);
1281 /* We want the first txdelta to be against the empty file. */
1285 /* Create an empty hash table for the first property diff. */
1286 last_props
= apr_hash_make(last_pool
);
1288 /* Walk through the revisions in chronological order. */
1289 for (i
= path_revisions
->nelts
; i
> 0; --i
)
1291 struct path_revision
*path_rev
= APR_ARRAY_IDX(path_revisions
, i
- 1,
1292 struct path_revision
*);
1293 apr_hash_t
*rev_props
;
1295 apr_array_header_t
*prop_diffs
;
1296 svn_fs_root_t
*root
;
1297 svn_txdelta_stream_t
*delta_stream
;
1298 svn_txdelta_window_handler_t delta_handler
= NULL
;
1299 void *delta_baton
= NULL
;
1300 apr_pool_t
*tmp_pool
; /* For swapping */
1301 svn_boolean_t contents_changed
;
1303 svn_pool_clear(iter_pool
);
1305 /* Get the revision properties. */
1306 SVN_ERR(svn_fs_revision_proplist(&rev_props
, repos
->fs
,
1307 path_rev
->revnum
, iter_pool
));
1309 /* Open the revision root. */
1310 SVN_ERR(svn_fs_revision_root(&root
, repos
->fs
, path_rev
->revnum
,
1313 /* Get the file's properties for this revision and compute the diffs. */
1314 SVN_ERR(svn_fs_node_proplist(&props
, root
, path_rev
->path
, iter_pool
));
1315 SVN_ERR(svn_prop_diffs(&prop_diffs
, props
, last_props
, iter_pool
));
1317 /* Check if the contents changed. */
1318 /* Special case: In the first revision, we always provide a delta. */
1320 SVN_ERR(svn_fs_contents_changed(&contents_changed
,
1321 last_root
, last_path
,
1322 root
, path_rev
->path
, iter_pool
));
1324 contents_changed
= TRUE
;
1326 /* We have all we need, give to the handler. */
1327 SVN_ERR(handler(handler_baton
, path_rev
->path
, path_rev
->revnum
,
1328 rev_props
, path_rev
->merged_revision
,
1329 contents_changed
? &delta_handler
: NULL
,
1330 contents_changed
? &delta_baton
: NULL
,
1331 prop_diffs
, iter_pool
));
1333 /* Compute and send delta if client asked for it.
1334 Note that this was initialized to NULL, so if !contents_changed,
1335 no deltas will be computed. */
1338 /* Get the content delta. */
1339 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream
,
1340 last_root
, last_path
,
1341 root
, path_rev
->path
,
1344 SVN_ERR(svn_txdelta_send_txstream(delta_stream
,
1345 delta_handler
, delta_baton
,
1349 /* Remember root, path and props for next iteration. */
1351 last_path
= path_rev
->path
;
1354 /* Swap the pools. */
1355 tmp_pool
= iter_pool
;
1356 iter_pool
= last_pool
;
1357 last_pool
= tmp_pool
;
1360 svn_pool_destroy(last_pool
);
1361 svn_pool_destroy(iter_pool
);
1363 return SVN_NO_ERROR
;
1367 svn_repos_get_file_revs(svn_repos_t
*repos
,
1371 svn_repos_authz_func_t authz_read_func
,
1372 void *authz_read_baton
,
1373 svn_repos_file_rev_handler_t handler
,
1374 void *handler_baton
,
1377 svn_file_rev_handler_t handler2
;
1378 void *handler2_baton
;
1380 svn_compat_wrap_file_rev_handler(&handler2
, &handler2_baton
, handler
,
1381 handler_baton
, pool
);
1383 return svn_repos_get_file_revs2(repos
, path
, start
, end
, FALSE
,
1384 authz_read_func
, authz_read_baton
,
1385 handler2
, handler2_baton
, pool
);