Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_repos / rev_hunt.c
blob69dc28c815ae5ca39fa29939bcd4aa1e58dfbc77
1 /* rev_hunt.c --- routines to hunt down particular fs revisions and
2 * their properties.
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 * ====================================================================
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>
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
44 might not work right.
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. */
54 static svn_error_t *
55 get_time(apr_time_t *tm,
56 svn_fs_t *fs,
57 svn_revnum_t rev,
58 apr_pool_t *pool)
60 svn_string_t *date_str;
62 SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
63 pool));
64 if (! date_str)
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));
71 return SVN_NO_ERROR;
75 svn_error_t *
76 svn_repos_dated_revision(svn_revnum_t *revision,
77 svn_repos_t *repos,
78 apr_time_t tm,
79 apr_pool_t *pool)
81 svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
82 apr_time_t this_time;
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));
87 rev_bot = 0;
88 rev_top = rev_latest;
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)
101 *revision = 0;
102 break;
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;
110 break;
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;
123 break;
126 /* see if time falls between rev_mid and rev_mid+1: */
127 SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
128 if (next_time > tm)
130 *revision = rev_mid;
131 break;
134 rev_bot = rev_mid + 1;
137 else
139 *revision = rev_mid; /* exact match! */
140 break;
144 return SVN_NO_ERROR;
148 svn_error_t *
149 svn_repos_get_committed_info(svn_revnum_t *committed_rev,
150 const char **committed_date,
151 const char **last_author,
152 svn_fs_root_t *root,
153 const char *path,
154 apr_pool_t *pool)
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;
177 return SVN_NO_ERROR;
181 /* Deprecated. */
182 svn_error_t *
183 svn_repos_history(svn_fs_t *fs,
184 const char *path,
185 svn_repos_history_func_t history_func,
186 void *history_baton,
187 svn_revnum_t start,
188 svn_revnum_t end,
189 svn_boolean_t cross_copies,
190 apr_pool_t *pool)
192 return svn_repos_history2(fs, path, history_func, history_baton,
193 NULL, NULL,
194 start, end, cross_copies, pool);
199 svn_error_t *
200 svn_repos_history2(svn_fs_t *fs,
201 const char *path,
202 svn_repos_history_func_t history_func,
203 void *history_baton,
204 svn_repos_authz_func_t authz_read_func,
205 void *authz_read_baton,
206 svn_revnum_t start,
207 svn_revnum_t end,
208 svn_boolean_t cross_copies,
209 apr_pool_t *pool)
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;
216 svn_fs_root_t *root;
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. */
229 if (start > end)
231 svn_revnum_t tmprev = start;
232 start = end;
233 end = tmprev;
236 /* Get a revision root for END, and an initial HISTORY baton. */
237 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
239 if (authz_read_func)
241 svn_boolean_t readable;
242 SVN_ERR(authz_read_func(&readable, root, path,
243 authz_read_baton, pool));
244 if (! readable)
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. */
256 apr_pool_t *tmppool;
257 svn_error_t *err;
259 SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool));
261 /* Only continue if there is further history to deal with. */
262 if (! history)
263 break;
265 /* Fetch the location information for this history step. */
266 SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
267 history, newpool));
269 /* If this history item predates our START revision, quit
270 here. */
271 if (history_rev < start)
272 break;
274 /* Is the history item readable? If not, quit. */
275 if (authz_read_func)
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));
283 if (! readable)
284 break;
287 /* Call the user-provided callback function. */
288 err = history_func(history_baton, history_path, history_rev, newpool);
289 if (err)
291 if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
293 svn_error_clear(err);
294 goto cleanup;
296 else
298 return 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);
305 tmppool = oldpool;
306 oldpool = newpool;
307 newpool = tmppool;
309 while (history); /* shouldn't hit this */
311 cleanup:
312 svn_pool_destroy(oldpool);
313 svn_pool_destroy(newpool);
314 return SVN_NO_ERROR;
318 svn_error_t *
319 svn_repos_deleted_rev(svn_fs_t *fs,
320 const char *path,
321 svn_revnum_t start,
322 svn_revnum_t end,
323 svn_revnum_t *deleted,
324 apr_pool_t *pool)
326 apr_pool_t *subpool;
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;
331 svn_error_t *err;
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. */
344 if (start > end)
346 svn_revnum_t tmprev = start;
347 start = end;
348 end = tmprev;
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);
354 if (err)
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);
361 return SVN_NO_ERROR;
363 return 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);
373 else if (err)
375 return err;
377 else
379 /* path exists in the end node and the end node is equivalent
380 or otherwise equivalent to the start node. This can mean
381 a few things:
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(&copy_root, &copy_path, root,
408 path, pool));
409 if (!copy_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;
414 return SVN_NO_ERROR;
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 |-----------------------------------------------------
428 Compare path | | | |
429 at start and | Copied at | Copied at | Never copied |
430 mid nodes. | rev > start | rev <= start | |
431 | | | |
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. |
436 | copy of | |
437 | itself, | |
438 | look LOWER. | |
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. |
444 | itself, | |
445 | look LOWER. | |
446 -------------------------------------------------------------------|
447 Mid node is | |
448 unrelated to | C) Start node replaced with unrelated mid node, |
449 start node | look LOWER. |
451 -------------------------------------------------------------------|
452 Path doesn't | |
453 exist at mid | D) Start node deleted before mid node, |
454 node | look LOWER |
456 --------------------------------------------------------------------
459 mid_rev = (start + end) / 2;
460 subpool = svn_pool_create(pool);
462 while (1)
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);
470 if (err)
472 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
474 /* Case D: Look lower in the range. */
475 svn_error_clear(err);
476 end = mid_rev;
477 mid_rev = (start + mid_rev) / 2;
479 else
480 return err;
482 else
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(&copy_root, &copy_path, root,
488 path, subpool));
489 if (cmp == -1 ||
490 (copy_root &&
491 (svn_fs_revision_root_revision(copy_root) > start)))
493 /* Cases A, B, C: Look at lower revs. */
494 end = mid_rev;
495 mid_rev = (start + mid_rev) / 2;
497 else if (end - mid_rev == 1)
499 /* Found the node path was deleted. */
500 *deleted = end;
501 break;
503 else
505 /* Cases E, F: Look at higher revs. */
506 start = mid_rev;
507 mid_rev = (start + end) / 2;
512 svn_pool_destroy(subpool);
513 return SVN_NO_ERROR;
517 /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
518 unreadable. */
519 static svn_error_t *
520 check_readability(svn_fs_root_t *root,
521 const char *path,
522 svn_repos_authz_func_t authz_read_func,
523 void *authz_read_baton,
524 apr_pool_t *pool)
526 svn_boolean_t readable;
527 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
528 if (! readable)
529 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
530 _("Unreadable path encountered; access denied"));
531 return SVN_NO_ERROR;
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. */
538 static svn_error_t *
539 check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
540 svn_fs_t *fs,
541 const char *fs_path,
542 svn_revnum_t peg_revision,
543 svn_revnum_t future_revision,
544 apr_pool_t *pool)
546 svn_fs_root_t *root;
547 svn_fs_history_t *history;
548 const char *path;
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
563 correctly. */
564 fs_path = NULL;
566 while (1)
568 apr_pool_t *tmppool;
570 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool));
572 if (!history)
573 break;
575 SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
577 if (!fs_path)
578 fs_path = apr_pstrdup(pool, path);
580 if (revision <= peg_revision)
581 break;
583 /* Clear old pool and flip. */
584 svn_pool_clear(lastpool);
585 tmppool = lastpool;
586 lastpool = currpool;
587 currpool = tmppool;
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);
597 return SVN_NO_ERROR;
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. */
611 static svn_error_t *
612 prev_location(svn_revnum_t *appeared_rev,
613 const char **prev_path,
614 svn_revnum_t *prev_rev,
615 svn_fs_t *fs,
616 svn_revnum_t revision,
617 const char *path,
618 apr_pool_t *pool)
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;
626 *prev_path = NULL;
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(&copy_root, &copy_path, root, path, pool));
632 if (! copy_root)
633 return SVN_NO_ERROR;
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"
646 before the copy.
648 SVN_ERR(svn_fs_copied_from(&copy_src_rev, &copy_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;
655 return SVN_NO_ERROR;
659 svn_error_t *
660 svn_repos_trace_node_locations(svn_fs_t *fs,
661 apr_hash_t **locations,
662 const char *fs_path,
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,
667 apr_pool_t *pool)
669 apr_array_header_t *location_revisions;
670 svn_revnum_t *revision_ptr, *revision_ptr_end;
671 svn_fs_root_t *root;
672 const char *path;
673 svn_revnum_t revision;
674 svn_boolean_t is_ancestor;
675 apr_pool_t *lastpool, *currpool;
676 const svn_fs_id_t *id;
678 /* Sanity check. */
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. */
683 if (*fs_path != '/')
684 fs_path = apr_pstrcat(pool, "/", fs_path, NULL);
686 /* Another sanity check. */
687 if (authz_read_func)
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. */
712 is_ancestor = FALSE;
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,
718 currpool));
719 if (is_ancestor)
720 break;
721 ++revision_ptr;
724 revision = is_ancestor ? *revision_ptr : peg_revision;
725 path = fs_path;
726 if (authz_read_func)
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)
735 apr_pool_t *tmppool;
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));
743 if (! prev_path)
744 break;
746 if (authz_read_func)
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));
754 if (! readable)
756 return SVN_NO_ERROR;
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));
769 revision_ptr++;
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))
776 revision_ptr++;
778 /* State update. */
779 path = prev_path;
780 revision = prev_rev;
782 /* Clear last pool and switch. */
783 svn_pool_clear(lastpool);
784 tmppool = lastpool;
785 lastpool = currpool;
786 currpool = tmppool;
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
793 to path@revision. */
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)
805 break;
806 SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
807 if (! svn_fs_check_related(id, lrev_id))
808 break;
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));
813 revision_ptr++;
816 /* Ignore any remaining location-revisions; they predate the
817 creation of path@revision. */
819 svn_pool_destroy(lastpool);
820 svn_pool_destroy(currpool);
822 return SVN_NO_ERROR;
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. */
829 static svn_error_t *
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,
835 apr_pool_t *pool)
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));
850 return SVN_NO_ERROR;
854 svn_error_t *
855 svn_repos_node_location_segments(svn_repos_t *repos,
856 const char *path,
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,
864 apr_pool_t *pool)
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;
869 apr_pool_t *subpool;
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;
883 else
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
891 demands it. */
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. */
897 if (*path != '/')
898 path = apr_pstrcat(pool, "/", path, NULL);
900 /* Auth check. */
901 if (authz_read_func)
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,
922 current_path->len);
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
935 range. */
936 if (! prev_path)
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,
941 cur_path, subpool));
942 if (segment->range_start < end_rev)
943 segment->range_start = end_rev;
944 current_rev = SVN_INVALID_REVNUM;
946 else
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. */
954 if (authz_read_func)
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));
963 if (! readable)
964 return SVN_NO_ERROR;
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))
974 break;
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,
987 subpool));
990 svn_pool_destroy(subpool);
991 return SVN_NO_ERROR;
994 /* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
995 static svn_error_t *
996 get_path_mergeinfo(apr_hash_t **mergeinfo,
997 svn_fs_t *fs,
998 const char *path,
999 svn_revnum_t revnum,
1000 apr_pool_t *pool)
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);
1018 if (*mergeinfo)
1019 *mergeinfo = svn_mergeinfo_dup(*mergeinfo, pool);
1020 else
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,
1030 const char *path,
1031 svn_revnum_t revision,
1032 apr_pool_t *pool)
1034 const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
1035 void *ptr;
1037 ptr = apr_hash_get(duplicate_path_revs, key, APR_HASH_KEY_STRING);
1038 return ptr != NULL;
1041 struct path_revision
1043 svn_revnum_t revnum;
1044 const char *path;
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,
1057 svn_repos_t *repos,
1058 struct path_revision *old_path_rev,
1059 apr_pool_t *pool)
1061 apr_pool_t *subpool = svn_pool_create(pool);
1062 apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1063 svn_error_t *err;
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
1074 mergeinfo. */
1075 svn_error_clear(err);
1076 prev_mergeinfo = apr_hash_make(subpool);
1078 else
1079 SVN_ERR(err);
1081 /* Then calculate and merge the differences. */
1082 SVN_ERR(svn_mergeinfo_diff(&deleted, &changed, prev_mergeinfo, curr_mergeinfo,
1083 FALSE, subpool));
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,
1096 svn_repos_t *repos,
1097 const char *path,
1098 svn_revnum_t start,
1099 svn_revnum_t end,
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,
1105 apr_pool_t *pool)
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"),
1123 path, end);
1125 /* Open a history object. */
1126 SVN_ERR(svn_fs_node_history(&history, root, path, last_pool));
1128 while (1)
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));
1137 if (!history)
1138 break;
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))
1146 break;
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,
1155 iter_pool));
1156 SVN_ERR(authz_read_func(&readable, tmp_root, path_rev->path,
1157 authz_read_baton, iter_pool));
1158 if (! readable)
1159 break;
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,
1168 path_rev, pool));
1169 else
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,
1177 path_rev->revnum),
1178 APR_HASH_KEY_STRING, (void *)0xdeadbeef);
1180 if (path_rev->revnum <= start)
1181 break;
1183 /* Swap pools. */
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 */
1195 static int
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)
1202 return 0;
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,
1210 svn_repos_t *repos,
1211 apr_hash_t *duplicate_path_revs,
1212 svn_repos_authz_func_t authz_read_func,
1213 void *authz_read_baton,
1214 apr_pool_t *pool)
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);
1227 int i;
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)
1241 continue;
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;
1248 const char *path;
1249 int j;
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,
1261 iter_pool));
1262 SVN_ERR(svn_fs_check_path(&kind, root, path, iter_pool));
1263 if (kind != svn_node_file)
1264 continue;
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,
1269 TRUE, TRUE,
1270 duplicate_path_revs,
1271 authz_read_func,
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,
1279 new);
1281 /* Swap data structures */
1282 old = new;
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;
1302 struct send_baton
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
1312 SB. */
1313 static svn_error_t *
1314 send_path_revision(struct path_revision *path_rev,
1315 svn_repos_t *repos,
1316 struct send_baton *sb,
1317 svn_file_rev_handler_t handler,
1318 void *handler_baton)
1320 apr_hash_t *rev_props;
1321 apr_hash_t *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,
1338 sb->iter_pool));
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,
1342 sb->iter_pool));
1343 SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1344 sb->iter_pool));
1346 /* Check if the contents changed. */
1347 /* Special case: In the first revision, we always provide a delta. */
1348 if (sb->last_root)
1349 SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root,
1350 sb->last_path, root, path_rev->path,
1351 sb->iter_pool));
1352 else
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. */
1365 if (delta_handler)
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,
1371 sb->iter_pool));
1372 /* And send. */
1373 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1374 delta_handler, delta_baton,
1375 sb->iter_pool));
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
1398 * merges.
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
1402 * times.
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.
1408 svn_error_t *
1409 svn_repos_get_file_revs2(svn_repos_t *repos,
1410 const char *path,
1411 svn_revnum_t start,
1412 svn_revnum_t end,
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,
1418 apr_pool_t *pool)
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));
1440 else
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,
1466 mainline_pos,
1467 struct path_revision *);
1468 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1469 merged_pos,
1470 struct path_revision *);
1472 if (main_pr->revnum <= merged_pr->revnum)
1474 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1475 handler_baton));
1476 mainline_pos -= 1;
1478 else
1480 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1481 handler_baton));
1482 merged_pos -= 1;
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,
1490 mainline_pos,
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,
1499 merged_pos,
1500 struct path_revision *);
1501 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1502 handler_baton));
1505 svn_pool_destroy(sb.last_pool);
1506 svn_pool_destroy(sb.iter_pool);
1508 return SVN_NO_ERROR;
1511 svn_error_t *
1512 svn_repos_get_file_revs(svn_repos_t *repos,
1513 const char *path,
1514 svn_revnum_t start,
1515 svn_revnum_t end,
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,
1520 apr_pool_t *pool)
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);