1 /* rev_hunt.c --- routines to hunt down particular fs revisions and
4 * ====================================================================
5 * Copyright (c) 2000-2008 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 /* Note: this binary search assumes that the datestamp properties on
40 each revision are in chronological order. That is if revision A >
41 revision B, then A's datestamp is younger then B's datestamp.
43 If someone comes along and sets a bogus datestamp, this routine
46 ### todo: you know, we *could* have svn_fs_change_rev_prop() do
47 some semantic checking when it's asked to change special reserved
48 svn: properties. It could prevent such a problem. */
51 /* helper for svn_repos_dated_revision().
53 Set *TM to the apr_time_t datestamp on revision REV in FS. */
55 get_time(apr_time_t
*tm
,
60 svn_string_t
*date_str
;
62 SVN_ERR(svn_fs_revision_prop(&date_str
, fs
, rev
, SVN_PROP_REVISION_DATE
,
65 return svn_error_createf
66 (SVN_ERR_FS_GENERAL
, NULL
,
67 _("Failed to find time on revision %ld"), rev
);
69 SVN_ERR(svn_time_from_cstring(tm
, date_str
->data
, pool
));
76 svn_repos_dated_revision(svn_revnum_t
*revision
,
81 svn_revnum_t rev_mid
, rev_top
, rev_bot
, rev_latest
;
83 svn_fs_t
*fs
= repos
->fs
;
85 /* Initialize top and bottom values of binary search. */
86 SVN_ERR(svn_fs_youngest_rev(&rev_latest
, fs
, pool
));
90 while (rev_bot
<= rev_top
)
92 rev_mid
= (rev_top
+ rev_bot
) / 2;
93 SVN_ERR(get_time(&this_time
, fs
, rev_mid
, pool
));
95 if (this_time
> tm
)/* we've overshot */
97 apr_time_t previous_time
;
99 if ((rev_mid
- 1) < 0)
105 /* see if time falls between rev_mid and rev_mid-1: */
106 SVN_ERR(get_time(&previous_time
, fs
, rev_mid
- 1, pool
));
107 if (previous_time
<= tm
)
109 *revision
= rev_mid
- 1;
113 rev_top
= rev_mid
- 1;
116 else if (this_time
< tm
) /* we've undershot */
118 apr_time_t next_time
;
120 if ((rev_mid
+ 1) > rev_latest
)
122 *revision
= rev_latest
;
126 /* see if time falls between rev_mid and rev_mid+1: */
127 SVN_ERR(get_time(&next_time
, fs
, rev_mid
+ 1, pool
));
134 rev_bot
= rev_mid
+ 1;
139 *revision
= rev_mid
; /* exact match! */
149 svn_repos_get_committed_info(svn_revnum_t
*committed_rev
,
150 const char **committed_date
,
151 const char **last_author
,
156 svn_fs_t
*fs
= svn_fs_root_fs(root
);
158 /* ### It might be simpler just to declare that revision
159 properties have char * (i.e., UTF-8) values, not arbitrary
160 binary values, hmmm. */
161 svn_string_t
*committed_date_s
, *last_author_s
;
163 /* Get the CR field out of the node's skel. */
164 SVN_ERR(svn_fs_node_created_rev(committed_rev
, root
, path
, pool
));
166 /* Get the date property of this revision. */
167 SVN_ERR(svn_fs_revision_prop(&committed_date_s
, fs
, *committed_rev
,
168 SVN_PROP_REVISION_DATE
, pool
));
170 /* Get the author property of this revision. */
171 SVN_ERR(svn_fs_revision_prop(&last_author_s
, fs
, *committed_rev
,
172 SVN_PROP_REVISION_AUTHOR
, pool
));
174 *committed_date
= committed_date_s
? committed_date_s
->data
: NULL
;
175 *last_author
= last_author_s
? last_author_s
->data
: NULL
;
183 svn_repos_history(svn_fs_t
*fs
,
185 svn_repos_history_func_t history_func
,
189 svn_boolean_t cross_copies
,
192 return svn_repos_history2(fs
, path
, history_func
, history_baton
,
194 start
, end
, cross_copies
, pool
);
200 svn_repos_history2(svn_fs_t
*fs
,
202 svn_repos_history_func_t history_func
,
204 svn_repos_authz_func_t authz_read_func
,
205 void *authz_read_baton
,
208 svn_boolean_t cross_copies
,
211 svn_fs_history_t
*history
;
212 apr_pool_t
*oldpool
= svn_pool_create(pool
);
213 apr_pool_t
*newpool
= svn_pool_create(pool
);
214 const char *history_path
;
215 svn_revnum_t history_rev
;
218 /* Validate the revisions. */
219 if (! SVN_IS_VALID_REVNUM(start
))
220 return svn_error_createf
221 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
222 _("Invalid start revision %ld"), start
);
223 if (! SVN_IS_VALID_REVNUM(end
))
224 return svn_error_createf
225 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
226 _("Invalid end revision %ld"), end
);
228 /* Ensure that the input is ordered. */
231 svn_revnum_t tmprev
= start
;
236 /* Get a revision root for END, and an initial HISTORY baton. */
237 SVN_ERR(svn_fs_revision_root(&root
, fs
, end
, pool
));
241 svn_boolean_t readable
;
242 SVN_ERR(authz_read_func(&readable
, root
, path
,
243 authz_read_baton
, pool
));
245 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE
, NULL
, NULL
);
248 SVN_ERR(svn_fs_node_history(&history
, root
, path
, oldpool
));
250 /* Now, we loop over the history items, calling svn_fs_history_prev(). */
253 /* Note that we have to do some crazy pool work here. We can't
254 get rid of the old history until we use it to get the new, so
255 we alternate back and forth between our subpools. */
259 SVN_ERR(svn_fs_history_prev(&history
, history
, cross_copies
, newpool
));
261 /* Only continue if there is further history to deal with. */
265 /* Fetch the location information for this history step. */
266 SVN_ERR(svn_fs_history_location(&history_path
, &history_rev
,
269 /* If this history item predates our START revision, quit
271 if (history_rev
< start
)
274 /* Is the history item readable? If not, quit. */
277 svn_boolean_t readable
;
278 svn_fs_root_t
*history_root
;
279 SVN_ERR(svn_fs_revision_root(&history_root
, fs
,
280 history_rev
, newpool
));
281 SVN_ERR(authz_read_func(&readable
, history_root
, history_path
,
282 authz_read_baton
, newpool
));
287 /* Call the user-provided callback function. */
288 err
= history_func(history_baton
, history_path
, history_rev
, newpool
);
291 if (err
->apr_err
== SVN_ERR_CEASE_INVOCATION
)
293 svn_error_clear(err
);
302 /* We're done with the old history item, so we can clear its
303 pool, and then toggle our notion of "the old pool". */
304 svn_pool_clear(oldpool
);
309 while (history
); /* shouldn't hit this */
312 svn_pool_destroy(oldpool
);
313 svn_pool_destroy(newpool
);
319 svn_repos_deleted_rev(svn_fs_t
*fs
,
323 svn_revnum_t
*deleted
,
327 svn_fs_root_t
*root
, *copy_root
;
328 const char *copy_path
;
329 svn_revnum_t mid_rev
;
330 const svn_fs_id_t
*start_node_id
, *curr_node_id
;
333 /* Validate the revision range. */
334 if (! SVN_IS_VALID_REVNUM(start
))
335 return svn_error_createf
336 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
337 _("Invalid start revision %ld"), start
);
338 if (! SVN_IS_VALID_REVNUM(end
))
339 return svn_error_createf
340 (SVN_ERR_FS_NO_SUCH_REVISION
, 0,
341 _("Invalid end revision %ld"), end
);
343 /* Ensure that the input is ordered. */
346 svn_revnum_t tmprev
= start
;
351 /* Ensure path exists in fs at start revision. */
352 SVN_ERR(svn_fs_revision_root(&root
, fs
, start
, pool
));
353 err
= svn_fs_node_id(&start_node_id
, root
, path
, pool
);
356 if (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
358 /* Path must exist in fs at start rev. */
359 *deleted
= SVN_INVALID_REVNUM
;
360 svn_error_clear(err
);
366 /* Ensure path was deleted at or before end revision. */
367 SVN_ERR(svn_fs_revision_root(&root
, fs
, end
, pool
));
368 err
= svn_fs_node_id(&curr_node_id
, root
, path
, pool
);
369 if (err
&& err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
371 svn_error_clear(err
);
379 /* path exists in the end node and the end node is equivalent
380 or otherwise equivalent to the start node. This can mean
383 1) The end node *is* simply the start node, uncopied
384 and unmodified in the start to end range.
386 2) The start node was modified, but never copied.
388 3) The start node was copied, but this copy occurred at
389 start or some rev *previous* to start, this is
390 effectively the same situation as 1 if the node was
391 never modified or 2 if it was.
393 In the first three cases the path was not deleted in
394 the specified range and we are done, but in the following
395 cases the start node must have been deleted at least once:
397 4) The start node was deleted and replaced by a copy of
398 itself at some rev between start and end. This copy
399 may itself have been replaced with copies of itself.
401 5) The start node was deleted and replaced by a node which
402 it does not share any history with.
404 SVN_ERR(svn_fs_node_id(&curr_node_id
, root
, path
, pool
));
405 if (svn_fs_compare_ids(start_node_id
, curr_node_id
) != -1)
407 SVN_ERR(svn_fs_closest_copy(©_root
, ©_path
, root
,
410 (svn_fs_revision_root_revision(copy_root
) <= start
))
412 /* Case 1,2 or 3, nothing more to do. */
413 *deleted
= SVN_INVALID_REVNUM
;
419 /* If we get here we know that path exists in rev start and was deleted
420 at least once before rev end. To find the revision path was first
421 deleted we use a binary search. The rules for the determining if
422 the deletion comes before or after a given median revision are
423 described by this matrix:
425 | Most recent copy event that
426 | caused mid node to exist.
427 |-----------------------------------------------------
429 at start and | Copied at | Copied at | Never copied |
430 mid nodes. | rev > start | rev <= start | |
432 -------------------------------------------------------------------|
433 Mid node is | A) Start node | |
434 equivalent to | replaced with | E) Mid node == start node, |
435 start node | an unmodified | look HIGHER. |
439 -------------------------------------------------------------------|
440 Mid node is | B) Start node | |
441 otherwise | replaced with | F) Mid node is a modified |
442 related to | a modified | version of start node, |
443 start node | copy of | look HIGHER. |
446 -------------------------------------------------------------------|
448 unrelated to | C) Start node replaced with unrelated mid node, |
449 start node | look LOWER. |
451 -------------------------------------------------------------------|
453 exist at mid | D) Start node deleted before mid node, |
456 --------------------------------------------------------------------
459 mid_rev
= (start
+ end
) / 2;
460 subpool
= svn_pool_create(pool
);
464 svn_pool_clear(subpool
);
466 /* Get revision root and node id for mid_rev at that revision. */
467 SVN_ERR(svn_fs_revision_root(&root
, fs
, mid_rev
, subpool
));
468 err
= svn_fs_node_id(&curr_node_id
, root
, path
, subpool
);
472 if (err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
474 /* Case D: Look lower in the range. */
475 svn_error_clear(err
);
477 mid_rev
= (start
+ mid_rev
) / 2;
484 /* Determine the relationship between the start node
485 and the current node. */
486 int cmp
= svn_fs_compare_ids(start_node_id
, curr_node_id
);
487 SVN_ERR(svn_fs_closest_copy(©_root
, ©_path
, root
,
491 (svn_fs_revision_root_revision(copy_root
) > start
)))
493 /* Cases A, B, C: Look at lower revs. */
495 mid_rev
= (start
+ mid_rev
) / 2;
497 else if (end
- mid_rev
== 1)
499 /* Found the node path was deleted. */
505 /* Cases E, F: Look at higher revs. */
507 mid_rev
= (start
+ end
) / 2;
512 svn_pool_destroy(subpool
);
517 /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
520 check_readability(svn_fs_root_t
*root
,
522 svn_repos_authz_func_t authz_read_func
,
523 void *authz_read_baton
,
526 svn_boolean_t readable
;
527 SVN_ERR(authz_read_func(&readable
, root
, path
, authz_read_baton
, pool
));
529 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE
, NULL
,
530 _("Unreadable path encountered; access denied"));
535 /* The purpose of this function is to discover if fs_path@future_rev
536 * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */
539 check_ancestry_of_peg_path(svn_boolean_t
*is_ancestor
,
542 svn_revnum_t peg_revision
,
543 svn_revnum_t future_revision
,
547 svn_fs_history_t
*history
;
549 svn_revnum_t revision
;
550 apr_pool_t
*lastpool
, *currpool
;
552 lastpool
= svn_pool_create(pool
);
553 currpool
= svn_pool_create(pool
);
555 SVN_ERR(svn_fs_revision_root(&root
, fs
, future_revision
, pool
));
557 SVN_ERR(svn_fs_node_history(&history
, root
, fs_path
, lastpool
));
559 /* Since paths that are different according to strcmp may still be
560 equivalent (due to number of consecutive slashes and the fact that
561 "" is the same as "/"), we get the "canonical" path in the first
562 iteration below so that the comparison after the loop will work
570 SVN_ERR(svn_fs_history_prev(&history
, history
, TRUE
, currpool
));
575 SVN_ERR(svn_fs_history_location(&path
, &revision
, history
, currpool
));
578 fs_path
= apr_pstrdup(pool
, path
);
580 if (revision
<= peg_revision
)
583 /* Clear old pool and flip. */
584 svn_pool_clear(lastpool
);
590 /* We must have had at least one iteration above where we
591 reassigned fs_path. Else, the path wouldn't have existed at
592 future_revision and svn_fs_history would have thrown. */
593 assert(fs_path
!= NULL
);
595 *is_ancestor
= (history
&& strcmp(path
, fs_path
) == 0);
601 /* Set *PREV_PATH and *PREV_REV to the path and revision which
602 represent the location at which PATH in FS was located immediately
603 prior to REVISION iff there was a copy operation (to PATH or one of
604 its parent directories) between that previous location and
605 PATH@REVISION, and set *APPEARED_REV to the first revision in which
606 PATH@REVISION appeared at PATH as a result of that copy operation.
608 If there was no such copy operation in that portion
609 of PATH's history, set *PREV_PATH to NULL, and set *PREV_REV and
610 *APPEARED_REV to SVN_INVALID_REVNUM. */
612 prev_location(svn_revnum_t
*appeared_rev
,
613 const char **prev_path
,
614 svn_revnum_t
*prev_rev
,
616 svn_revnum_t revision
,
620 svn_fs_root_t
*root
, *copy_root
;
621 const char *copy_path
, *copy_src_path
, *remainder
= "";
622 svn_revnum_t copy_src_rev
;
624 /* Initialize return variables. */
625 *appeared_rev
= *prev_rev
= SVN_INVALID_REVNUM
;
628 /* Ask about the most recent copy which affected PATH@REVISION. If
629 there was no such copy, we're done. */
630 SVN_ERR(svn_fs_revision_root(&root
, fs
, revision
, pool
));
631 SVN_ERR(svn_fs_closest_copy(©_root
, ©_path
, root
, path
, pool
));
635 /* Ultimately, it's not the path of the closest copy's source that
636 we care about -- it's our own path's location in the copy source
637 revision. So we'll tack the relative path that expresses the
638 difference between the copy destination and our path in the copy
639 revision onto the copy source path to determine this information.
641 In other words, if our path is "/branches/my-branch/foo/bar", and
642 we know that the closest relevant copy was a copy of "/trunk" to
643 "/branches/my-branch", then that relative path under the copy
644 destination is "/foo/bar". Tacking that onto the copy source
645 path tells us that our path was located at "/trunk/foo/bar"
648 SVN_ERR(svn_fs_copied_from(©_src_rev
, ©_src_path
,
649 copy_root
, copy_path
, pool
));
650 if (! strcmp(copy_path
, path
) == 0)
651 remainder
= svn_path_is_child(copy_path
, path
, pool
);
652 *prev_path
= svn_path_join(copy_src_path
, remainder
, pool
);
653 *appeared_rev
= svn_fs_revision_root_revision(copy_root
);
654 *prev_rev
= copy_src_rev
;
660 svn_repos_trace_node_locations(svn_fs_t
*fs
,
661 apr_hash_t
**locations
,
663 svn_revnum_t peg_revision
,
664 apr_array_header_t
*location_revisions_orig
,
665 svn_repos_authz_func_t authz_read_func
,
666 void *authz_read_baton
,
669 apr_array_header_t
*location_revisions
;
670 svn_revnum_t
*revision_ptr
, *revision_ptr_end
;
673 svn_revnum_t revision
;
674 svn_boolean_t is_ancestor
;
675 apr_pool_t
*lastpool
, *currpool
;
676 const svn_fs_id_t
*id
;
679 assert(location_revisions_orig
->elt_size
== sizeof(svn_revnum_t
));
681 /* Ensure that FS_PATH is absolute, because our path-math below will
682 depend on that being the case. */
684 fs_path
= apr_pstrcat(pool
, "/", fs_path
, NULL
);
686 /* Another sanity check. */
689 svn_fs_root_t
*peg_root
;
690 SVN_ERR(svn_fs_revision_root(&peg_root
, fs
, peg_revision
, pool
));
691 SVN_ERR(check_readability(peg_root
, fs_path
,
692 authz_read_func
, authz_read_baton
, pool
));
695 *locations
= apr_hash_make(pool
);
697 /* We flip between two pools in the second loop below. */
698 lastpool
= svn_pool_create(pool
);
699 currpool
= svn_pool_create(pool
);
701 /* First - let's sort the array of the revisions from the greatest revision
702 * downward, so it will be easier to search on. */
703 location_revisions
= apr_array_copy(pool
, location_revisions_orig
);
704 qsort(location_revisions
->elts
, location_revisions
->nelts
,
705 sizeof(*revision_ptr
), svn_sort_compare_revisions
);
707 revision_ptr
= (svn_revnum_t
*)location_revisions
->elts
;
708 revision_ptr_end
= revision_ptr
+ location_revisions
->nelts
;
710 /* Ignore revisions R that are younger than the peg_revisions where
711 path@peg_revision is not an ancestor of path@R. */
713 while (revision_ptr
< revision_ptr_end
&& *revision_ptr
> peg_revision
)
715 svn_pool_clear(currpool
);
716 SVN_ERR(check_ancestry_of_peg_path(&is_ancestor
, fs
, fs_path
,
717 peg_revision
, *revision_ptr
,
724 revision
= is_ancestor
? *revision_ptr
: peg_revision
;
728 SVN_ERR(svn_fs_revision_root(&root
, fs
, revision
, pool
));
729 SVN_ERR(check_readability(root
, fs_path
, authz_read_func
,
730 authz_read_baton
, pool
));
733 while (revision_ptr
< revision_ptr_end
)
736 svn_revnum_t appeared_rev
, prev_rev
;
737 const char *prev_path
;
739 /* Find the target of the innermost copy relevant to path@revision.
740 The copy may be of path itself, or of a parent directory. */
741 SVN_ERR(prev_location(&appeared_rev
, &prev_path
, &prev_rev
, fs
,
742 revision
, path
, currpool
));
748 svn_boolean_t readable
;
749 svn_fs_root_t
*tmp_root
;
751 SVN_ERR(svn_fs_revision_root(&tmp_root
, fs
, revision
, currpool
));
752 SVN_ERR(authz_read_func(&readable
, tmp_root
, path
,
753 authz_read_baton
, currpool
));
760 /* Assign the current path to all younger revisions until we reach
761 the copy target rev. */
762 while ((revision_ptr
< revision_ptr_end
)
763 && (*revision_ptr
>= appeared_rev
))
765 /* *revision_ptr is allocated out of pool, so we can point
766 to in the hash table. */
767 apr_hash_set(*locations
, revision_ptr
, sizeof(*revision_ptr
),
768 apr_pstrdup(pool
, path
));
772 /* Ignore all revs between the copy target rev and the copy
773 source rev (non-inclusive). */
774 while ((revision_ptr
< revision_ptr_end
)
775 && (*revision_ptr
> prev_rev
))
782 /* Clear last pool and switch. */
783 svn_pool_clear(lastpool
);
789 /* There are no copies relevant to path@revision. So any remaining
790 revisions either predate the creation of path@revision or have
791 the node existing at the same path. We will look up path@lrev
792 for each remaining location-revision and make sure it is related
794 SVN_ERR(svn_fs_revision_root(&root
, fs
, revision
, currpool
));
795 SVN_ERR(svn_fs_node_id(&id
, root
, path
, pool
));
796 while (revision_ptr
< revision_ptr_end
)
798 svn_node_kind_t kind
;
799 const svn_fs_id_t
*lrev_id
;
801 svn_pool_clear(currpool
);
802 SVN_ERR(svn_fs_revision_root(&root
, fs
, *revision_ptr
, currpool
));
803 SVN_ERR(svn_fs_check_path(&kind
, root
, path
, currpool
));
804 if (kind
== svn_node_none
)
806 SVN_ERR(svn_fs_node_id(&lrev_id
, root
, path
, currpool
));
807 if (! svn_fs_check_related(id
, lrev_id
))
810 /* The node exists at the same path; record that and advance. */
811 apr_hash_set(*locations
, revision_ptr
, sizeof(*revision_ptr
),
812 apr_pstrdup(pool
, path
));
816 /* Ignore any remaining location-revisions; they predate the
817 creation of path@revision. */
819 svn_pool_destroy(lastpool
);
820 svn_pool_destroy(currpool
);
826 /* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
827 its revision range fits between END_REV and START_REV, possibly
828 cropping the range so that it fits *entirely* in that range. */
830 maybe_crop_and_send_segment(svn_location_segment_t
*segment
,
831 svn_revnum_t start_rev
,
832 svn_revnum_t end_rev
,
833 svn_location_segment_receiver_t receiver
,
834 void *receiver_baton
,
837 /* We only want to transmit this segment if some portion of it
838 is between our END_REV and START_REV. */
839 if (! ((segment
->range_start
> start_rev
)
840 || (segment
->range_end
< end_rev
)))
842 /* Correct our segment range when the range straddles one of
843 our requested revision boundaries. */
844 if (segment
->range_start
< end_rev
)
845 segment
->range_start
= end_rev
;
846 if (segment
->range_end
> start_rev
)
847 segment
->range_end
= start_rev
;
848 SVN_ERR(receiver(segment
, receiver_baton
, pool
));
855 svn_repos_node_location_segments(svn_repos_t
*repos
,
857 svn_revnum_t peg_revision
,
858 svn_revnum_t start_rev
,
859 svn_revnum_t end_rev
,
860 svn_location_segment_receiver_t receiver
,
861 void *receiver_baton
,
862 svn_repos_authz_func_t authz_read_func
,
863 void *authz_read_baton
,
866 svn_fs_t
*fs
= svn_repos_fs(repos
);
867 svn_stringbuf_t
*current_path
;
868 svn_revnum_t youngest_rev
= SVN_INVALID_REVNUM
, current_rev
;
871 /* No PEG_REVISION? We'll use HEAD. */
872 if (! SVN_IS_VALID_REVNUM(peg_revision
))
874 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, fs
, pool
));
875 peg_revision
= youngest_rev
;
878 /* No START_REV? We'll use HEAD (which we may have already fetched). */
879 if (! SVN_IS_VALID_REVNUM(start_rev
))
881 if (SVN_IS_VALID_REVNUM(youngest_rev
))
882 start_rev
= youngest_rev
;
884 SVN_ERR(svn_fs_youngest_rev(&start_rev
, fs
, pool
));
887 /* No END_REV? We'll use 0. */
888 end_rev
= SVN_IS_VALID_REVNUM(end_rev
) ? end_rev
: 0;
890 /* Are the revision properly ordered? They better be -- the API
892 assert(end_rev
<= start_rev
);
893 assert(start_rev
<= peg_revision
);
895 /* Ensure that PATH is absolute, because our path-math will depend
896 on that being the case. */
898 path
= apr_pstrcat(pool
, "/", path
, NULL
);
903 svn_fs_root_t
*peg_root
;
904 SVN_ERR(svn_fs_revision_root(&peg_root
, fs
, peg_revision
, pool
));
905 SVN_ERR(check_readability(peg_root
, path
,
906 authz_read_func
, authz_read_baton
, pool
));
909 /* Okay, let's get searching! */
910 subpool
= svn_pool_create(pool
);
911 current_rev
= peg_revision
;
912 current_path
= svn_stringbuf_create(path
, pool
);
913 while (current_rev
>= end_rev
)
915 svn_revnum_t appeared_rev
, prev_rev
;
916 const char *cur_path
, *prev_path
;
917 svn_location_segment_t
*segment
;
919 svn_pool_clear(subpool
);
921 cur_path
= apr_pstrmemdup(subpool
, current_path
->data
,
923 segment
= apr_pcalloc(subpool
, sizeof(*segment
));
924 segment
->range_end
= current_rev
;
925 segment
->range_start
= end_rev
;
926 segment
->path
= cur_path
+ 1;
928 SVN_ERR(prev_location(&appeared_rev
, &prev_path
, &prev_rev
, fs
,
929 current_rev
, cur_path
, subpool
));
931 /* If there are no previous locations for this thing (meaning,
932 it originated at the current path), then we simply need to
933 find its revision of origin to populate our final segment.
934 Otherwise, the APPEARED_REV is the start of current segment's
938 svn_fs_root_t
*revroot
;
939 SVN_ERR(svn_fs_revision_root(&revroot
, fs
, current_rev
, subpool
));
940 SVN_ERR(svn_fs_node_origin_rev(&(segment
->range_start
), revroot
,
942 if (segment
->range_start
< end_rev
)
943 segment
->range_start
= end_rev
;
944 current_rev
= SVN_INVALID_REVNUM
;
948 segment
->range_start
= appeared_rev
;
949 svn_stringbuf_set(current_path
, prev_path
);
950 current_rev
= prev_rev
;
953 /* Report our segment, providing it passes authz muster. */
956 svn_boolean_t readable
;
957 svn_fs_root_t
*cur_rev_root
;
959 SVN_ERR(svn_fs_revision_root(&cur_rev_root
, fs
,
960 segment
->range_end
, subpool
));
961 SVN_ERR(authz_read_func(&readable
, cur_rev_root
, segment
->path
,
962 authz_read_baton
, subpool
));
967 /* Transmit the segment (if it's within the scope of our concern). */
968 SVN_ERR(maybe_crop_and_send_segment(segment
, start_rev
, end_rev
,
969 receiver
, receiver_baton
, subpool
));
971 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
972 (and didn't ever reach END_REV). */
973 if (! SVN_IS_VALID_REVNUM(current_rev
))
976 /* If there's a gap in the history, we need to report as much
977 (if the gap is within the scope of our concern). */
978 if (segment
->range_start
- current_rev
> 1)
980 svn_location_segment_t
*gap_segment
;
981 gap_segment
= apr_pcalloc(subpool
, sizeof(*gap_segment
));
982 gap_segment
->range_end
= segment
->range_start
- 1;
983 gap_segment
->range_start
= current_rev
+ 1;
984 gap_segment
->path
= NULL
;
985 SVN_ERR(maybe_crop_and_send_segment(gap_segment
, start_rev
, end_rev
,
986 receiver
, receiver_baton
,
990 svn_pool_destroy(subpool
);
994 /* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
996 get_path_mergeinfo(apr_hash_t
**mergeinfo
,
1002 svn_mergeinfo_catalog_t tmp_catalog
;
1003 svn_fs_root_t
*root
;
1004 apr_pool_t
*subpool
= svn_pool_create(pool
);
1005 apr_array_header_t
*paths
= apr_array_make(subpool
, 1,
1006 sizeof(const char *));
1008 APR_ARRAY_PUSH(paths
, const char *) = path
;
1010 SVN_ERR(svn_fs_revision_root(&root
, fs
, revnum
, subpool
));
1011 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1012 because we will filter out unreadable revisions in
1013 find_interesting_revision(), above */
1014 SVN_ERR(svn_fs_get_mergeinfo(&tmp_catalog
, root
, paths
,
1015 svn_mergeinfo_inherited
, FALSE
, subpool
));
1017 *mergeinfo
= apr_hash_get(tmp_catalog
, path
, APR_HASH_KEY_STRING
);
1019 *mergeinfo
= svn_mergeinfo_dup(*mergeinfo
, pool
);
1021 *mergeinfo
= apr_hash_make(pool
);
1023 svn_pool_destroy(subpool
);
1025 return SVN_NO_ERROR
;
1028 static APR_INLINE svn_boolean_t
1029 is_path_in_hash(apr_hash_t
*duplicate_path_revs
,
1031 svn_revnum_t revision
,
1034 const char *key
= apr_psprintf(pool
, "%s:%ld", path
, revision
);
1037 ptr
= apr_hash_get(duplicate_path_revs
, key
, APR_HASH_KEY_STRING
);
1041 struct path_revision
1043 svn_revnum_t revnum
;
1046 /* Does this path_rev have merges to also be included? */
1047 apr_hash_t
*merged_mergeinfo
;
1049 /* Is this a merged revision? */
1050 svn_boolean_t merged
;
1053 /* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store
1054 the mergeinfo difference in MERGED_MERGEINFO, allocated in POOL. */
1055 static svn_error_t
*
1056 get_merged_mergeinfo(apr_hash_t
**merged_mergeinfo
,
1058 struct path_revision
*old_path_rev
,
1061 apr_pool_t
*subpool
= svn_pool_create(pool
);
1062 apr_hash_t
*curr_mergeinfo
, *prev_mergeinfo
, *deleted
, *changed
;
1065 /* First, find the mergeinfo difference for old_path_rev->revnum, and
1066 old_path_rev->revnum - 1. */
1067 SVN_ERR(get_path_mergeinfo(&curr_mergeinfo
, repos
->fs
, old_path_rev
->path
,
1068 old_path_rev
->revnum
, subpool
));
1069 err
= get_path_mergeinfo(&prev_mergeinfo
, repos
->fs
, old_path_rev
->path
,
1070 old_path_rev
->revnum
- 1, subpool
);
1071 if (err
&& err
->apr_err
== SVN_ERR_FS_NOT_FOUND
)
1073 /* If the path doesn't exist in the previous revision, assume empty
1075 svn_error_clear(err
);
1076 prev_mergeinfo
= apr_hash_make(subpool
);
1081 /* Then calculate and merge the differences. */
1082 SVN_ERR(svn_mergeinfo_diff(&deleted
, &changed
, prev_mergeinfo
, curr_mergeinfo
,
1084 SVN_ERR(svn_mergeinfo_merge(changed
, deleted
, subpool
));
1086 /* Store the result. */
1087 *merged_mergeinfo
= svn_mergeinfo_dup(changed
, pool
);
1089 svn_pool_destroy(subpool
);
1091 return SVN_NO_ERROR
;
1094 static svn_error_t
*
1095 find_interesting_revisions(apr_array_header_t
*path_revisions
,
1100 svn_boolean_t include_merged_revisions
,
1101 svn_boolean_t mark_as_merged
,
1102 apr_hash_t
*duplicate_path_revs
,
1103 svn_repos_authz_func_t authz_read_func
,
1104 void *authz_read_baton
,
1107 apr_pool_t
*iter_pool
, *last_pool
;
1108 svn_fs_history_t
*history
;
1109 svn_fs_root_t
*root
;
1110 svn_node_kind_t kind
;
1112 /* We switch betwwen two pools while looping, since we need information from
1113 the last iteration to be available. */
1114 iter_pool
= svn_pool_create(pool
);
1115 last_pool
= svn_pool_create(pool
);
1117 /* The path had better be a file in this revision. */
1118 SVN_ERR(svn_fs_revision_root(&root
, repos
->fs
, end
, last_pool
));
1119 SVN_ERR(svn_fs_check_path(&kind
, root
, path
, pool
));
1120 if (kind
!= svn_node_file
)
1121 return svn_error_createf
1122 (SVN_ERR_FS_NOT_FILE
, NULL
, _("'%s' is not a file in revision %ld"),
1125 /* Open a history object. */
1126 SVN_ERR(svn_fs_node_history(&history
, root
, path
, last_pool
));
1130 struct path_revision
*path_rev
= apr_palloc(pool
, sizeof(*path_rev
));
1131 apr_pool_t
*tmp_pool
;
1133 svn_pool_clear(iter_pool
);
1135 /* Fetch the history object to walk through. */
1136 SVN_ERR(svn_fs_history_prev(&history
, history
, TRUE
, iter_pool
));
1139 SVN_ERR(svn_fs_history_location(&path_rev
->path
, &path_rev
->revnum
,
1140 history
, iter_pool
));
1142 /* Check to see if we already saw this path (and it's ancestors) */
1143 if (include_merged_revisions
1144 && is_path_in_hash(duplicate_path_revs
, path_rev
->path
,
1145 path_rev
->revnum
, iter_pool
))
1148 /* Check authorization. */
1149 if (authz_read_func
)
1151 svn_boolean_t readable
;
1152 svn_fs_root_t
*tmp_root
;
1154 SVN_ERR(svn_fs_revision_root(&tmp_root
, repos
->fs
, path_rev
->revnum
,
1156 SVN_ERR(authz_read_func(&readable
, tmp_root
, path_rev
->path
,
1157 authz_read_baton
, iter_pool
));
1162 path_rev
->path
= apr_pstrdup(pool
, path_rev
->path
);
1163 path_rev
->merged
= mark_as_merged
;
1164 APR_ARRAY_PUSH(path_revisions
, struct path_revision
*) = path_rev
;
1166 if (include_merged_revisions
)
1167 SVN_ERR(get_merged_mergeinfo(&path_rev
->merged_mergeinfo
, repos
,
1170 path_rev
->merged_mergeinfo
= NULL
;
1172 /* Add the path/rev pair to the hash, so we can filter out future
1173 occurrences of it. We only care about this if including merged
1174 revisions, 'cause that's the only time we can have duplicates. */
1175 apr_hash_set(duplicate_path_revs
,
1176 apr_psprintf(pool
, "%s:%ld", path_rev
->path
,
1178 APR_HASH_KEY_STRING
, (void *)0xdeadbeef);
1180 if (path_rev
->revnum
<= start
)
1184 tmp_pool
= iter_pool
;
1185 iter_pool
= last_pool
;
1186 last_pool
= tmp_pool
;
1189 svn_pool_destroy(iter_pool
);
1191 return SVN_NO_ERROR
;
1194 /* Comparison function to sort path/revisions in increasing order */
1196 compare_path_revisions(const void *a
, const void *b
)
1198 struct path_revision
*a_pr
= *(struct path_revision
**)a
;
1199 struct path_revision
*b_pr
= *(struct path_revision
**)b
;
1201 if (a_pr
->revnum
== b_pr
->revnum
)
1204 return a_pr
->revnum
< b_pr
->revnum
? 1 : -1;
1207 static svn_error_t
*
1208 find_merged_revisions(apr_array_header_t
**merged_path_revisions_out
,
1209 apr_array_header_t
*mainline_path_revisions
,
1211 apr_hash_t
*duplicate_path_revs
,
1212 svn_repos_authz_func_t authz_read_func
,
1213 void *authz_read_baton
,
1216 apr_array_header_t
*old
, *new;
1217 apr_pool_t
*iter_pool
, *last_pool
;
1218 apr_array_header_t
*merged_path_revisions
= apr_array_make(pool
, 0,
1219 sizeof(struct path_revision
*));
1221 old
= mainline_path_revisions
;
1222 iter_pool
= svn_pool_create(pool
);
1223 last_pool
= svn_pool_create(pool
);
1228 apr_pool_t
*temp_pool
;
1230 svn_pool_clear(iter_pool
);
1231 new = apr_array_make(iter_pool
, 0, sizeof(struct path_revision
*));
1233 /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather
1234 path_revisions for any merged revisions, and store those in NEW. */
1235 for (i
= 0; i
< old
->nelts
; i
++)
1237 apr_hash_index_t
*hi
;
1238 struct path_revision
*old_pr
= APR_ARRAY_IDX(old
, i
,
1239 struct path_revision
*);
1240 if (!old_pr
->merged_mergeinfo
)
1243 /* Determine and trace the merge sources. */
1244 for (hi
= apr_hash_first(iter_pool
, old_pr
->merged_mergeinfo
); hi
;
1245 hi
= apr_hash_next(hi
))
1247 apr_array_header_t
*rangelist
;
1251 apr_hash_this(hi
, (void *) &path
, NULL
, (void *) &rangelist
);
1253 for (j
= 0; j
< rangelist
->nelts
; j
++)
1255 svn_merge_range_t
*range
= APR_ARRAY_IDX(rangelist
, j
,
1256 svn_merge_range_t
*);
1257 svn_node_kind_t kind
;
1258 svn_fs_root_t
*root
;
1260 SVN_ERR(svn_fs_revision_root(&root
, repos
->fs
, range
->end
,
1262 SVN_ERR(svn_fs_check_path(&kind
, root
, path
, iter_pool
));
1263 if (kind
!= svn_node_file
)
1266 /* Search and find revisions to add to the NEW list. */
1267 SVN_ERR(find_interesting_revisions(new, repos
, path
,
1268 range
->start
, range
->end
,
1270 duplicate_path_revs
,
1272 authz_read_baton
, pool
));
1277 /* Append the newly found path revisions with the old ones. */
1278 merged_path_revisions
= apr_array_append(iter_pool
, merged_path_revisions
,
1281 /* Swap data structures */
1283 temp_pool
= last_pool
;
1284 last_pool
= iter_pool
;
1285 iter_pool
= temp_pool
;
1287 while (new->nelts
> 0);
1289 /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1290 qsort(merged_path_revisions
->elts
, merged_path_revisions
->nelts
,
1291 sizeof(struct path_revision
*), compare_path_revisions
);
1293 /* Copy to the output array. */
1294 *merged_path_revisions_out
= apr_array_copy(pool
, merged_path_revisions
);
1296 svn_pool_destroy(iter_pool
);
1297 svn_pool_destroy(last_pool
);
1299 return SVN_NO_ERROR
;
1304 apr_pool_t
*iter_pool
;
1305 apr_pool_t
*last_pool
;
1306 apr_hash_t
*last_props
;
1307 const char *last_path
;
1308 svn_fs_root_t
*last_root
;
1311 /* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1313 static svn_error_t
*
1314 send_path_revision(struct path_revision
*path_rev
,
1316 struct send_baton
*sb
,
1317 svn_file_rev_handler_t handler
,
1318 void *handler_baton
)
1320 apr_hash_t
*rev_props
;
1322 apr_array_header_t
*prop_diffs
;
1323 svn_fs_root_t
*root
;
1324 svn_txdelta_stream_t
*delta_stream
;
1325 svn_txdelta_window_handler_t delta_handler
= NULL
;
1326 void *delta_baton
= NULL
;
1327 apr_pool_t
*tmp_pool
; /* For swapping */
1328 svn_boolean_t contents_changed
;
1330 svn_pool_clear(sb
->iter_pool
);
1332 /* Get the revision properties. */
1333 SVN_ERR(svn_fs_revision_proplist(&rev_props
, repos
->fs
,
1334 path_rev
->revnum
, sb
->iter_pool
));
1336 /* Open the revision root. */
1337 SVN_ERR(svn_fs_revision_root(&root
, repos
->fs
, path_rev
->revnum
,
1340 /* Get the file's properties for this revision and compute the diffs. */
1341 SVN_ERR(svn_fs_node_proplist(&props
, root
, path_rev
->path
,
1343 SVN_ERR(svn_prop_diffs(&prop_diffs
, props
, sb
->last_props
,
1346 /* Check if the contents changed. */
1347 /* Special case: In the first revision, we always provide a delta. */
1349 SVN_ERR(svn_fs_contents_changed(&contents_changed
, sb
->last_root
,
1350 sb
->last_path
, root
, path_rev
->path
,
1353 contents_changed
= TRUE
;
1355 /* We have all we need, give to the handler. */
1356 SVN_ERR(handler(handler_baton
, path_rev
->path
, path_rev
->revnum
,
1357 rev_props
, path_rev
->merged
,
1358 contents_changed
? &delta_handler
: NULL
,
1359 contents_changed
? &delta_baton
: NULL
,
1360 prop_diffs
, sb
->iter_pool
));
1362 /* Compute and send delta if client asked for it.
1363 Note that this was initialized to NULL, so if !contents_changed,
1364 no deltas will be computed. */
1367 /* Get the content delta. */
1368 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream
,
1369 sb
->last_root
, sb
->last_path
,
1370 root
, path_rev
->path
,
1373 SVN_ERR(svn_txdelta_send_txstream(delta_stream
,
1374 delta_handler
, delta_baton
,
1378 /* Remember root, path and props for next iteration. */
1379 sb
->last_root
= root
;
1380 sb
->last_path
= path_rev
->path
;
1381 sb
->last_props
= props
;
1383 /* Swap the pools. */
1384 tmp_pool
= sb
->iter_pool
;
1385 sb
->iter_pool
= sb
->last_pool
;
1386 sb
->last_pool
= tmp_pool
;
1388 return SVN_NO_ERROR
;
1391 /* We don't yet support sending revisions in reverse order; the caller wait
1392 * until we've traced back through the entire history, and then accept
1393 * them from oldest to youngest. Someday this may change, but in the meantime,
1394 * the general algorithm is thus:
1396 * 1) Trace back through the history of an object, adding each revision
1397 * found to the MAINLINE_PATH_REVISIONS array, marking any which were
1399 * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1400 * merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1401 * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1403 * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1404 * youngest to oldest, interleaving as appropriate. This is implemented
1405 * similar to an insertion sort, but instead of inserting into another
1406 * array, we just call the appropriate handler.
1409 svn_repos_get_file_revs2(svn_repos_t
*repos
,
1413 svn_boolean_t include_merged_revisions
,
1414 svn_repos_authz_func_t authz_read_func
,
1415 void *authz_read_baton
,
1416 svn_file_rev_handler_t handler
,
1417 void *handler_baton
,
1420 apr_array_header_t
*mainline_path_revisions
, *merged_path_revisions
;
1421 apr_hash_t
*duplicate_path_revs
;
1422 struct send_baton sb
;
1423 int mainline_pos
, merged_pos
;
1425 /* Get the revisions we are interested in. */
1426 duplicate_path_revs
= apr_hash_make(pool
);
1427 mainline_path_revisions
= apr_array_make(pool
, 0,
1428 sizeof(struct path_revision
*));
1429 SVN_ERR(find_interesting_revisions(mainline_path_revisions
, repos
, path
,
1430 start
, end
, include_merged_revisions
,
1431 FALSE
, duplicate_path_revs
,
1432 authz_read_func
, authz_read_baton
, pool
));
1434 /* If we are including merged revisions, go get those, too. */
1435 if (include_merged_revisions
)
1436 SVN_ERR(find_merged_revisions(&merged_path_revisions
,
1437 mainline_path_revisions
, repos
,
1438 duplicate_path_revs
, authz_read_func
,
1439 authz_read_baton
, pool
));
1441 merged_path_revisions
= apr_array_make(pool
, 0,
1442 sizeof(struct path_revision
*));
1444 /* We must have at least one revision to get. */
1445 assert(mainline_path_revisions
->nelts
> 0);
1447 /* We switch betwwen two pools while looping, since we need information from
1448 the last iteration to be available. */
1449 sb
.iter_pool
= svn_pool_create(pool
);
1450 sb
.last_pool
= svn_pool_create(pool
);
1452 /* We want the first txdelta to be against the empty file. */
1453 sb
.last_root
= NULL
;
1454 sb
.last_path
= NULL
;
1456 /* Create an empty hash table for the first property diff. */
1457 sb
.last_props
= apr_hash_make(sb
.last_pool
);
1459 /* Walk through both mainline and merged revisions, and send them in
1460 reverse chronological order, interleaving as appropriate. */
1461 mainline_pos
= mainline_path_revisions
->nelts
- 1;
1462 merged_pos
= merged_path_revisions
->nelts
- 1;
1463 while (mainline_pos
>= 0 && merged_pos
>= 0)
1465 struct path_revision
*main_pr
= APR_ARRAY_IDX(mainline_path_revisions
,
1467 struct path_revision
*);
1468 struct path_revision
*merged_pr
= APR_ARRAY_IDX(merged_path_revisions
,
1470 struct path_revision
*);
1472 if (main_pr
->revnum
<= merged_pr
->revnum
)
1474 SVN_ERR(send_path_revision(main_pr
, repos
, &sb
, handler
,
1480 SVN_ERR(send_path_revision(merged_pr
, repos
, &sb
, handler
,
1486 /* Send any remaining revisions from the mainline list. */
1487 for (; mainline_pos
>= 0; mainline_pos
-= 1)
1489 struct path_revision
*main_pr
= APR_ARRAY_IDX(mainline_path_revisions
,
1491 struct path_revision
*);
1492 SVN_ERR(send_path_revision(main_pr
, repos
, &sb
, handler
, handler_baton
));
1495 /* Ditto for the merged list. */
1496 for (; merged_pos
>= 0; merged_pos
-= 1)
1498 struct path_revision
*merged_pr
= APR_ARRAY_IDX(merged_path_revisions
,
1500 struct path_revision
*);
1501 SVN_ERR(send_path_revision(merged_pr
, repos
, &sb
, handler
,
1505 svn_pool_destroy(sb
.last_pool
);
1506 svn_pool_destroy(sb
.iter_pool
);
1508 return SVN_NO_ERROR
;
1512 svn_repos_get_file_revs(svn_repos_t
*repos
,
1516 svn_repos_authz_func_t authz_read_func
,
1517 void *authz_read_baton
,
1518 svn_repos_file_rev_handler_t handler
,
1519 void *handler_baton
,
1522 svn_file_rev_handler_t handler2
;
1523 void *handler2_baton
;
1525 svn_compat_wrap_file_rev_handler(&handler2
, &handler2_baton
, handler
,
1526 handler_baton
, pool
);
1528 return svn_repos_get_file_revs2(repos
, path
, start
, end
, FALSE
,
1529 authz_read_func
, authz_read_baton
,
1530 handler2
, handler2_baton
, pool
);