In the command-line client, forbid
[svn.git] / subversion / libsvn_repos / rev_hunt.c
blob09f89667f48f307b1e073fd62344896c4fdbc8a5
1 /* rev_hunt.c --- routines to hunt down particular fs revisions and
2 * their properties.
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 * ====================================================================
20 #include <string.h>
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"
26 #include "svn_fs.h"
27 #include "svn_repos.h"
28 #include "svn_string.h"
29 #include "svn_time.h"
30 #include "svn_sorts.h"
31 #include "svn_path.h"
32 #include "svn_props.h"
33 #include "svn_mergeinfo.h"
34 #include "repos.h"
36 #include <assert.h>
38 static svn_error_t *
39 find_interesting_revisions(apr_array_header_t *path_revisions,
40 svn_repos_t *repos,
41 const char *path,
42 svn_revnum_t start,
43 svn_revnum_t end,
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,
48 apr_pool_t *pool);
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
56 might not work right.
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. */
66 static svn_error_t *
67 get_time(apr_time_t *tm,
68 svn_fs_t *fs,
69 svn_revnum_t rev,
70 apr_pool_t *pool)
72 svn_string_t *date_str;
74 SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
75 pool));
76 if (! date_str)
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));
83 return SVN_NO_ERROR;
87 svn_error_t *
88 svn_repos_dated_revision(svn_revnum_t *revision,
89 svn_repos_t *repos,
90 apr_time_t tm,
91 apr_pool_t *pool)
93 svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
94 apr_time_t this_time;
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));
99 rev_bot = 0;
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)
113 *revision = 0;
114 break;
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;
122 break;
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;
135 break;
138 /* see if time falls between rev_mid and rev_mid+1: */
139 SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
140 if (next_time > tm)
142 *revision = rev_mid;
143 break;
146 rev_bot = rev_mid + 1;
149 else
151 *revision = rev_mid; /* exact match! */
152 break;
156 return SVN_NO_ERROR;
160 svn_error_t *
161 svn_repos_get_committed_info(svn_revnum_t *committed_rev,
162 const char **committed_date,
163 const char **last_author,
164 svn_fs_root_t *root,
165 const char *path,
166 apr_pool_t *pool)
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;
189 return SVN_NO_ERROR;
193 /* Deprecated. */
194 svn_error_t *
195 svn_repos_history(svn_fs_t *fs,
196 const char *path,
197 svn_repos_history_func_t history_func,
198 void *history_baton,
199 svn_revnum_t start,
200 svn_revnum_t end,
201 svn_boolean_t cross_copies,
202 apr_pool_t *pool)
204 return svn_repos_history2(fs, path, history_func, history_baton,
205 NULL, NULL,
206 start, end, cross_copies, pool);
211 svn_error_t *
212 svn_repos_history2(svn_fs_t *fs,
213 const char *path,
214 svn_repos_history_func_t history_func,
215 void *history_baton,
216 svn_repos_authz_func_t authz_read_func,
217 void *authz_read_baton,
218 svn_revnum_t start,
219 svn_revnum_t end,
220 svn_boolean_t cross_copies,
221 apr_pool_t *pool)
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;
228 svn_fs_root_t *root;
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. */
241 if (start > end)
243 svn_revnum_t tmprev = start;
244 start = end;
245 end = tmprev;
248 /* Get a revision root for END, and an initial HISTORY baton. */
249 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
251 if (authz_read_func)
253 svn_boolean_t readable;
254 SVN_ERR(authz_read_func(&readable, root, path,
255 authz_read_baton, pool));
256 if (! readable)
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. */
268 apr_pool_t *tmppool;
269 svn_error_t *err;
271 SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool));
273 /* Only continue if there is further history to deal with. */
274 if (! history)
275 break;
277 /* Fetch the location information for this history step. */
278 SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
279 history, newpool));
281 /* If this history item predates our START revision, quit
282 here. */
283 if (history_rev < start)
284 break;
286 /* Is the history item readable? If not, quit. */
287 if (authz_read_func)
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));
295 if (! readable)
296 break;
299 /* Call the user-provided callback function. */
300 err = history_func(history_baton, history_path, history_rev, newpool);
301 if (err)
303 if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
305 svn_error_clear(err);
306 goto cleanup;
308 else
310 return 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);
317 tmppool = oldpool;
318 oldpool = newpool;
319 newpool = tmppool;
321 while (history); /* shouldn't hit this */
323 cleanup:
324 svn_pool_destroy(oldpool);
325 svn_pool_destroy(newpool);
326 return SVN_NO_ERROR;
330 svn_error_t *
331 svn_repos_deleted_rev(svn_fs_t *fs,
332 const char *path,
333 svn_revnum_t start,
334 svn_revnum_t end,
335 svn_revnum_t *deleted,
336 apr_pool_t *pool)
338 apr_pool_t *subpool;
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;
343 svn_error_t *err;
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. */
356 if (start > end)
358 svn_revnum_t tmprev = start;
359 start = end;
360 end = tmprev;
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);
366 if (err)
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);
373 return SVN_NO_ERROR;
375 return 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);
385 else if (err)
387 return err;
389 else
391 /* path exists in the end node and the end node is equivalent
392 or otherwise equivalent to the start node. This can mean
393 a few things:
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(&copy_root, &copy_path, root,
420 path, pool));
421 if (!copy_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;
426 return SVN_NO_ERROR;
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 |-----------------------------------------------------
440 Compare path | | | |
441 at start and | Copied at | Copied at | Never copied |
442 mid nodes. | rev > start | rev <= start | |
443 | | | |
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. |
448 | copy of | |
449 | itself, | |
450 | look LOWER. | |
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. |
456 | itself, | |
457 | look LOWER. | |
458 -------------------------------------------------------------------|
459 Mid node is | |
460 unrelated to | C) Start node replaced with unrelated mid node, |
461 start node | look LOWER. |
463 -------------------------------------------------------------------|
464 Path doesn't | |
465 exist at mid | D) Start node deleted before mid node, |
466 node | look LOWER |
468 --------------------------------------------------------------------
471 mid_rev = (start + end) / 2;
472 subpool = svn_pool_create(pool);
474 while (1)
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);
482 if (err)
484 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
486 /* Case D: Look lower in the range. */
487 svn_error_clear(err);
488 end = mid_rev;
489 mid_rev = (start + mid_rev) / 2;
491 else
492 return err;
494 else
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(&copy_root, &copy_path, root,
500 path, subpool));
501 if (cmp == -1 ||
502 (copy_root &&
503 (svn_fs_revision_root_revision(copy_root) > start)))
505 /* Cases A, B, C: Look at lower revs. */
506 end = mid_rev;
507 mid_rev = (start + mid_rev) / 2;
509 else if (end - mid_rev == 1)
511 /* Found the node path was deleted. */
512 *deleted = end;
513 break;
515 else
517 /* Cases E, F: Look at higher revs. */
518 start = mid_rev;
519 mid_rev = (start + end) / 2;
524 svn_pool_destroy(subpool);
525 return SVN_NO_ERROR;
529 /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
530 unreadable. */
531 static svn_error_t *
532 check_readability(svn_fs_root_t *root,
533 const char *path,
534 svn_repos_authz_func_t authz_read_func,
535 void *authz_read_baton,
536 apr_pool_t *pool)
538 svn_boolean_t readable;
539 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
540 if (! readable)
541 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
542 _("Unreadable path encountered; access denied"));
543 return SVN_NO_ERROR;
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. */
550 static svn_error_t *
551 check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
552 svn_fs_t *fs,
553 const char *fs_path,
554 svn_revnum_t peg_revision,
555 svn_revnum_t future_revision,
556 apr_pool_t *pool)
558 svn_fs_root_t *root;
559 svn_fs_history_t *history;
560 const char *path;
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
575 correctly. */
576 fs_path = NULL;
578 while (1)
580 apr_pool_t *tmppool;
582 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool));
584 if (!history)
585 break;
587 SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
589 if (!fs_path)
590 fs_path = apr_pstrdup(pool, path);
592 if (revision <= peg_revision)
593 break;
595 /* Clear old pool and flip. */
596 svn_pool_clear(lastpool);
597 tmppool = lastpool;
598 lastpool = currpool;
599 currpool = tmppool;
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);
609 return SVN_NO_ERROR;
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. */
623 static svn_error_t *
624 prev_location(svn_revnum_t *appeared_rev,
625 const char **prev_path,
626 svn_revnum_t *prev_rev,
627 svn_fs_t *fs,
628 svn_revnum_t revision,
629 const char *path,
630 apr_pool_t *pool)
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;
638 *prev_path = NULL;
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(&copy_root, &copy_path, root, path, pool));
644 if (! copy_root)
645 return SVN_NO_ERROR;
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"
658 before the copy.
660 SVN_ERR(svn_fs_copied_from(&copy_src_rev, &copy_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;
667 return SVN_NO_ERROR;
671 svn_error_t *
672 svn_repos_trace_node_locations(svn_fs_t *fs,
673 apr_hash_t **locations,
674 const char *fs_path,
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,
679 apr_pool_t *pool)
681 apr_array_header_t *location_revisions;
682 svn_revnum_t *revision_ptr, *revision_ptr_end;
683 svn_fs_root_t *root;
684 const char *path;
685 svn_revnum_t revision;
686 svn_boolean_t is_ancestor;
687 apr_pool_t *lastpool, *currpool;
688 const svn_fs_id_t *id;
690 /* Sanity check. */
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. */
695 if (*fs_path != '/')
696 fs_path = apr_pstrcat(pool, "/", fs_path, NULL);
698 /* Another sanity check. */
699 if (authz_read_func)
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. */
724 is_ancestor = FALSE;
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,
730 currpool));
731 if (is_ancestor)
732 break;
733 ++revision_ptr;
736 revision = is_ancestor ? *revision_ptr : peg_revision;
737 path = fs_path;
738 if (authz_read_func)
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)
747 apr_pool_t *tmppool;
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));
755 if (! prev_path)
756 break;
758 if (authz_read_func)
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));
766 if (! readable)
768 return SVN_NO_ERROR;
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));
781 revision_ptr++;
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))
788 revision_ptr++;
790 /* State update. */
791 path = prev_path;
792 revision = prev_rev;
794 /* Clear last pool and switch. */
795 svn_pool_clear(lastpool);
796 tmppool = lastpool;
797 lastpool = currpool;
798 currpool = tmppool;
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
805 to path@revision. */
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)
817 break;
818 SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
819 if (! svn_fs_check_related(id, lrev_id))
820 break;
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));
825 revision_ptr++;
828 /* Ignore any remaining location-revisions; they predate the
829 creation of path@revision. */
831 svn_pool_destroy(lastpool);
832 svn_pool_destroy(currpool);
834 return SVN_NO_ERROR;
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. */
841 static svn_error_t *
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,
847 apr_pool_t *pool)
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));
862 return SVN_NO_ERROR;
866 svn_error_t *
867 svn_repos_node_location_segments(svn_repos_t *repos,
868 const char *path,
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,
876 apr_pool_t *pool)
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;
881 apr_pool_t *subpool;
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;
895 else
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
903 demands it. */
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. */
909 if (*path != '/')
910 path = apr_pstrcat(pool, "/", path, NULL);
912 /* Auth check. */
913 if (authz_read_func)
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,
934 current_path->len);
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
947 range. */
948 if (! prev_path)
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,
953 cur_path, subpool));
954 if (segment->range_start < end_rev)
955 segment->range_start = end_rev;
956 current_rev = SVN_INVALID_REVNUM;
958 else
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. */
966 if (authz_read_func)
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));
975 if (! readable)
976 return SVN_NO_ERROR;
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))
986 break;
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,
999 subpool));
1002 svn_pool_destroy(subpool);
1003 return SVN_NO_ERROR;
1007 struct path_revision
1009 svn_revnum_t revnum;
1010 const char *path;
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,
1021 svn_repos_t *repos,
1022 struct path_revision *old_path_rev,
1023 svn_repos_authz_func_t authz_read_func,
1024 void *authz_read_baton,
1025 apr_pool_t *pool)
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,
1033 old_path_rev->path,
1034 old_path_rev->revnum, subpool));
1035 SVN_ERR(svn_repos__get_path_mergeinfo(&prev_mergeinfo, repos->fs,
1036 old_path_rev->path,
1037 old_path_rev->revnum - 1, subpool));
1038 SVN_ERR(svn_mergeinfo_diff(&deleted, &changed, prev_mergeinfo, curr_mergeinfo,
1039 FALSE, subpool));
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;
1051 const char *path;
1052 int i;
1054 apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist);
1056 for (i = 0; i < rangelist->nelts; i++)
1058 svn_error_t *err;
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);
1070 if (err)
1072 if (err->apr_err == SVN_ERR_FS_NOT_FILE)
1073 svn_error_clear(err);
1074 else
1075 return 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,
1089 svn_repos_t *repos,
1090 const char *path,
1091 svn_revnum_t start,
1092 svn_revnum_t end,
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,
1097 apr_pool_t *pool)
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"),
1115 path, end);
1117 /* Open a history object. */
1118 SVN_ERR(svn_fs_node_history(&history, root, path, last_pool));
1120 while (1)
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));
1128 if (!history)
1129 break;
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,
1138 iter_pool));
1139 SVN_ERR(authz_read_func(&readable, tmp_root, path_rev->path,
1140 authz_read_baton, iter_pool));
1141 if (! readable)
1142 break;
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,
1156 pool));
1158 /* Are looking at a branching revision? If so, break. */
1159 SVN_ERR(svn_fs_revision_root(&merge_root, repos->fs,
1160 path_rev->revnum,
1161 iter_pool));
1162 SVN_ERR(svn_repos__is_branching_copy(&branching_rev, merge_root,
1163 path_rev->path, NULL, pool));
1165 if (branching_rev)
1166 break;
1169 if (path_rev->revnum <= start)
1170 break;
1172 /* Swap pools. */
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;
1184 static int
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)
1197 return 0;
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,
1210 apr_pool_t *pool)
1212 int i;
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;
1244 svn_error_t *
1245 svn_repos_get_file_revs2(svn_repos_t *repos,
1246 const char *path,
1247 svn_revnum_t start,
1248 svn_revnum_t end,
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,
1254 apr_pool_t *pool)
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;
1262 int i;
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,
1273 pool));
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. */
1282 last_root = NULL;
1283 last_path = NULL;
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;
1294 apr_hash_t *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,
1311 iter_pool));
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. */
1319 if (last_root)
1320 SVN_ERR(svn_fs_contents_changed(&contents_changed,
1321 last_root, last_path,
1322 root, path_rev->path, iter_pool));
1323 else
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. */
1336 if (delta_handler)
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,
1342 iter_pool));
1343 /* And send. */
1344 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1345 delta_handler, delta_baton,
1346 iter_pool));
1349 /* Remember root, path and props for next iteration. */
1350 last_root = root;
1351 last_path = path_rev->path;
1352 last_props = props;
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;
1366 svn_error_t *
1367 svn_repos_get_file_revs(svn_repos_t *repos,
1368 const char *path,
1369 svn_revnum_t start,
1370 svn_revnum_t end,
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,
1375 apr_pool_t *pool)
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);