diff options
Diffstat (limited to 'subversion/libsvn_repos/rev_hunt.c')
-rw-r--r-- | subversion/libsvn_repos/rev_hunt.c | 1699 |
1 files changed, 1699 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/rev_hunt.c b/subversion/libsvn_repos/rev_hunt.c new file mode 100644 index 0000000..77b1f2a --- /dev/null +++ b/subversion/libsvn_repos/rev_hunt.c @@ -0,0 +1,1699 @@ +/* rev_hunt.c --- routines to hunt down particular fs revisions and + * their properties. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <string.h> +#include "svn_compat.h" +#include "svn_private_config.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_fs.h" +#include "svn_repos.h" +#include "svn_string.h" +#include "svn_time.h" +#include "svn_sorts.h" +#include "svn_props.h" +#include "svn_mergeinfo.h" +#include "repos.h" +#include "private/svn_fspath.h" + + +/* Note: this binary search assumes that the datestamp properties on + each revision are in chronological order. That is if revision A > + revision B, then A's datestamp is younger then B's datestamp. + + If someone comes along and sets a bogus datestamp, this routine + might not work right. + + ### todo: you know, we *could* have svn_fs_change_rev_prop() do + some semantic checking when it's asked to change special reserved + svn: properties. It could prevent such a problem. */ + + +/* helper for svn_repos_dated_revision(). + + Set *TM to the apr_time_t datestamp on revision REV in FS. */ +static svn_error_t * +get_time(apr_time_t *tm, + svn_fs_t *fs, + svn_revnum_t rev, + apr_pool_t *pool) +{ + svn_string_t *date_str; + + SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE, + pool)); + if (! date_str) + return svn_error_createf + (SVN_ERR_FS_GENERAL, NULL, + _("Failed to find time on revision %ld"), rev); + + return svn_time_from_cstring(tm, date_str->data, pool); +} + + +svn_error_t * +svn_repos_dated_revision(svn_revnum_t *revision, + svn_repos_t *repos, + apr_time_t tm, + apr_pool_t *pool) +{ + svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest; + apr_time_t this_time; + svn_fs_t *fs = repos->fs; + + /* Initialize top and bottom values of binary search. */ + SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool)); + rev_bot = 0; + rev_top = rev_latest; + + while (rev_bot <= rev_top) + { + rev_mid = (rev_top + rev_bot) / 2; + SVN_ERR(get_time(&this_time, fs, rev_mid, pool)); + + if (this_time > tm)/* we've overshot */ + { + apr_time_t previous_time; + + if ((rev_mid - 1) < 0) + { + *revision = 0; + break; + } + + /* see if time falls between rev_mid and rev_mid-1: */ + SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool)); + if (previous_time <= tm) + { + *revision = rev_mid - 1; + break; + } + + rev_top = rev_mid - 1; + } + + else if (this_time < tm) /* we've undershot */ + { + apr_time_t next_time; + + if ((rev_mid + 1) > rev_latest) + { + *revision = rev_latest; + break; + } + + /* see if time falls between rev_mid and rev_mid+1: */ + SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool)); + if (next_time > tm) + { + *revision = rev_mid; + break; + } + + rev_bot = rev_mid + 1; + } + + else + { + *revision = rev_mid; /* exact match! */ + break; + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_repos_get_committed_info(svn_revnum_t *committed_rev, + const char **committed_date, + const char **last_author, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool) +{ + apr_hash_t *revprops; + + svn_fs_t *fs = svn_fs_root_fs(root); + + /* ### It might be simpler just to declare that revision + properties have char * (i.e., UTF-8) values, not arbitrary + binary values, hmmm. */ + svn_string_t *committed_date_s, *last_author_s; + + /* Get the CR field out of the node's skel. */ + SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool)); + + /* Get the revision properties of this revision. */ + SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool)); + + /* Extract date and author from these revprops. */ + committed_date_s = apr_hash_get(revprops, + SVN_PROP_REVISION_DATE, + sizeof(SVN_PROP_REVISION_DATE)-1); + last_author_s = apr_hash_get(revprops, + SVN_PROP_REVISION_AUTHOR, + sizeof(SVN_PROP_REVISION_AUTHOR)-1); + + *committed_date = committed_date_s ? committed_date_s->data : NULL; + *last_author = last_author_s ? last_author_s->data : NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_repos_history2(svn_fs_t *fs, + const char *path, + svn_repos_history_func_t history_func, + void *history_baton, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t cross_copies, + apr_pool_t *pool) +{ + svn_fs_history_t *history; + apr_pool_t *oldpool = svn_pool_create(pool); + apr_pool_t *newpool = svn_pool_create(pool); + const char *history_path; + svn_revnum_t history_rev; + svn_fs_root_t *root; + + /* Validate the revisions. */ + if (! SVN_IS_VALID_REVNUM(start)) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REVISION, 0, + _("Invalid start revision %ld"), start); + if (! SVN_IS_VALID_REVNUM(end)) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REVISION, 0, + _("Invalid end revision %ld"), end); + + /* Ensure that the input is ordered. */ + if (start > end) + { + svn_revnum_t tmprev = start; + start = end; + end = tmprev; + } + + /* Get a revision root for END, and an initial HISTORY baton. */ + SVN_ERR(svn_fs_revision_root(&root, fs, end, pool)); + + if (authz_read_func) + { + svn_boolean_t readable; + SVN_ERR(authz_read_func(&readable, root, path, + authz_read_baton, pool)); + if (! readable) + return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); + } + + SVN_ERR(svn_fs_node_history(&history, root, path, oldpool)); + + /* Now, we loop over the history items, calling svn_fs_history_prev(). */ + do + { + /* Note that we have to do some crazy pool work here. We can't + get rid of the old history until we use it to get the new, so + we alternate back and forth between our subpools. */ + apr_pool_t *tmppool; + svn_error_t *err; + + SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool)); + + /* Only continue if there is further history to deal with. */ + if (! history) + break; + + /* Fetch the location information for this history step. */ + SVN_ERR(svn_fs_history_location(&history_path, &history_rev, + history, newpool)); + + /* If this history item predates our START revision, quit + here. */ + if (history_rev < start) + break; + + /* Is the history item readable? If not, quit. */ + if (authz_read_func) + { + svn_boolean_t readable; + svn_fs_root_t *history_root; + SVN_ERR(svn_fs_revision_root(&history_root, fs, + history_rev, newpool)); + SVN_ERR(authz_read_func(&readable, history_root, history_path, + authz_read_baton, newpool)); + if (! readable) + break; + } + + /* Call the user-provided callback function. */ + err = history_func(history_baton, history_path, history_rev, newpool); + if (err) + { + if (err->apr_err == SVN_ERR_CEASE_INVOCATION) + { + svn_error_clear(err); + goto cleanup; + } + else + { + return svn_error_trace(err); + } + } + + /* We're done with the old history item, so we can clear its + pool, and then toggle our notion of "the old pool". */ + svn_pool_clear(oldpool); + tmppool = oldpool; + oldpool = newpool; + newpool = tmppool; + } + while (history); /* shouldn't hit this */ + + cleanup: + svn_pool_destroy(oldpool); + svn_pool_destroy(newpool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_repos_deleted_rev(svn_fs_t *fs, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_revnum_t *deleted, + apr_pool_t *pool) +{ + apr_pool_t *subpool; + svn_fs_root_t *root, *copy_root; + const char *copy_path; + svn_revnum_t mid_rev; + const svn_fs_id_t *start_node_id, *curr_node_id; + svn_error_t *err; + + /* Validate the revision range. */ + if (! SVN_IS_VALID_REVNUM(start)) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REVISION, 0, + _("Invalid start revision %ld"), start); + if (! SVN_IS_VALID_REVNUM(end)) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REVISION, 0, + _("Invalid end revision %ld"), end); + + /* Ensure that the input is ordered. */ + if (start > end) + { + svn_revnum_t tmprev = start; + start = end; + end = tmprev; + } + + /* Ensure path exists in fs at start revision. */ + SVN_ERR(svn_fs_revision_root(&root, fs, start, pool)); + err = svn_fs_node_id(&start_node_id, root, path, pool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + /* Path must exist in fs at start rev. */ + *deleted = SVN_INVALID_REVNUM; + svn_error_clear(err); + return SVN_NO_ERROR; + } + return svn_error_trace(err); + } + + /* Ensure path was deleted at or before end revision. */ + SVN_ERR(svn_fs_revision_root(&root, fs, end, pool)); + err = svn_fs_node_id(&curr_node_id, root, path, pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + } + else if (err) + { + return svn_error_trace(err); + } + else + { + /* path exists in the end node and the end node is equivalent + or otherwise equivalent to the start node. This can mean + a few things: + + 1) The end node *is* simply the start node, uncopied + and unmodified in the start to end range. + + 2) The start node was modified, but never copied. + + 3) The start node was copied, but this copy occurred at + start or some rev *previous* to start, this is + effectively the same situation as 1 if the node was + never modified or 2 if it was. + + In the first three cases the path was not deleted in + the specified range and we are done, but in the following + cases the start node must have been deleted at least once: + + 4) The start node was deleted and replaced by a copy of + itself at some rev between start and end. This copy + may itself have been replaced with copies of itself. + + 5) The start node was deleted and replaced by a node which + it does not share any history with. + */ + SVN_ERR(svn_fs_node_id(&curr_node_id, root, path, pool)); + if (svn_fs_compare_ids(start_node_id, curr_node_id) != -1) + { + SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, + path, pool)); + if (!copy_root || + (svn_fs_revision_root_revision(copy_root) <= start)) + { + /* Case 1,2 or 3, nothing more to do. */ + *deleted = SVN_INVALID_REVNUM; + return SVN_NO_ERROR; + } + } + } + + /* If we get here we know that path exists in rev start and was deleted + at least once before rev end. To find the revision path was first + deleted we use a binary search. The rules for the determining if + the deletion comes before or after a given median revision are + described by this matrix: + + | Most recent copy event that + | caused mid node to exist. + |----------------------------------------------------- + Compare path | | | | + at start and | Copied at | Copied at | Never copied | + mid nodes. | rev > start | rev <= start | | + | | | | + -------------------------------------------------------------------| + Mid node is | A) Start node | | + equivalent to | replaced with | E) Mid node == start node, | + start node | an unmodified | look HIGHER. | + | copy of | | + | itself, | | + | look LOWER. | | + -------------------------------------------------------------------| + Mid node is | B) Start node | | + otherwise | replaced with | F) Mid node is a modified | + related to | a modified | version of start node, | + start node | copy of | look HIGHER. | + | itself, | | + | look LOWER. | | + -------------------------------------------------------------------| + Mid node is | | + unrelated to | C) Start node replaced with unrelated mid node, | + start node | look LOWER. | + | | + -------------------------------------------------------------------| + Path doesn't | | + exist at mid | D) Start node deleted before mid node, | + node | look LOWER | + | | + -------------------------------------------------------------------- + */ + + mid_rev = (start + end) / 2; + subpool = svn_pool_create(pool); + + while (1) + { + svn_pool_clear(subpool); + + /* Get revision root and node id for mid_rev at that revision. */ + SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, subpool)); + err = svn_fs_node_id(&curr_node_id, root, path, subpool); + + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + /* Case D: Look lower in the range. */ + svn_error_clear(err); + end = mid_rev; + mid_rev = (start + mid_rev) / 2; + } + else + return svn_error_trace(err); + } + else + { + /* Determine the relationship between the start node + and the current node. */ + int cmp = svn_fs_compare_ids(start_node_id, curr_node_id); + SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, + path, subpool)); + if (cmp == -1 || + (copy_root && + (svn_fs_revision_root_revision(copy_root) > start))) + { + /* Cases A, B, C: Look at lower revs. */ + end = mid_rev; + mid_rev = (start + mid_rev) / 2; + } + else if (end - mid_rev == 1) + { + /* Found the node path was deleted. */ + *deleted = end; + break; + } + else + { + /* Cases E, F: Look at higher revs. */ + start = mid_rev; + mid_rev = (start + end) / 2; + } + } + } + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + +/* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is + unreadable. */ +static svn_error_t * +check_readability(svn_fs_root_t *root, + const char *path, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + apr_pool_t *pool) +{ + svn_boolean_t readable; + SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool)); + if (! readable) + return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, + _("Unreadable path encountered; access denied")); + return SVN_NO_ERROR; +} + + +/* The purpose of this function is to discover if fs_path@future_rev + * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */ + +static svn_error_t * +check_ancestry_of_peg_path(svn_boolean_t *is_ancestor, + svn_fs_t *fs, + const char *fs_path, + svn_revnum_t peg_revision, + svn_revnum_t future_revision, + apr_pool_t *pool) +{ + svn_fs_root_t *root; + svn_fs_history_t *history; + const char *path = NULL; + svn_revnum_t revision; + apr_pool_t *lastpool, *currpool; + + lastpool = svn_pool_create(pool); + currpool = svn_pool_create(pool); + + SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool)); + + SVN_ERR(svn_fs_node_history(&history, root, fs_path, lastpool)); + + /* Since paths that are different according to strcmp may still be + equivalent (due to number of consecutive slashes and the fact that + "" is the same as "/"), we get the "canonical" path in the first + iteration below so that the comparison after the loop will work + correctly. */ + fs_path = NULL; + + while (1) + { + apr_pool_t *tmppool; + + SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool)); + + if (!history) + break; + + SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool)); + + if (!fs_path) + fs_path = apr_pstrdup(pool, path); + + if (revision <= peg_revision) + break; + + /* Clear old pool and flip. */ + svn_pool_clear(lastpool); + tmppool = lastpool; + lastpool = currpool; + currpool = tmppool; + } + + /* We must have had at least one iteration above where we + reassigned fs_path. Else, the path wouldn't have existed at + future_revision and svn_fs_history would have thrown. */ + SVN_ERR_ASSERT(fs_path != NULL); + + *is_ancestor = (history && strcmp(path, fs_path) == 0); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_repos__prev_location(svn_revnum_t *appeared_rev, + const char **prev_path, + svn_revnum_t *prev_rev, + svn_fs_t *fs, + svn_revnum_t revision, + const char *path, + apr_pool_t *pool) +{ + svn_fs_root_t *root, *copy_root; + const char *copy_path, *copy_src_path, *remainder; + svn_revnum_t copy_src_rev; + + /* Initialize return variables. */ + if (appeared_rev) + *appeared_rev = SVN_INVALID_REVNUM; + if (prev_rev) + *prev_rev = SVN_INVALID_REVNUM; + if (prev_path) + *prev_path = NULL; + + /* Ask about the most recent copy which affected PATH@REVISION. If + there was no such copy, we're done. */ + SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool)); + SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, path, pool)); + if (! copy_root) + return SVN_NO_ERROR; + + /* Ultimately, it's not the path of the closest copy's source that + we care about -- it's our own path's location in the copy source + revision. So we'll tack the relative path that expresses the + difference between the copy destination and our path in the copy + revision onto the copy source path to determine this information. + + In other words, if our path is "/branches/my-branch/foo/bar", and + we know that the closest relevant copy was a copy of "/trunk" to + "/branches/my-branch", then that relative path under the copy + destination is "/foo/bar". Tacking that onto the copy source + path tells us that our path was located at "/trunk/foo/bar" + before the copy. + */ + SVN_ERR(svn_fs_copied_from(©_src_rev, ©_src_path, + copy_root, copy_path, pool)); + remainder = svn_fspath__skip_ancestor(copy_path, path); + if (prev_path) + *prev_path = svn_fspath__join(copy_src_path, remainder, pool); + if (appeared_rev) + *appeared_rev = svn_fs_revision_root_revision(copy_root); + if (prev_rev) + *prev_rev = copy_src_rev; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_repos_trace_node_locations(svn_fs_t *fs, + apr_hash_t **locations, + const char *fs_path, + svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions_orig, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + apr_pool_t *pool) +{ + apr_array_header_t *location_revisions; + svn_revnum_t *revision_ptr, *revision_ptr_end; + svn_fs_root_t *root; + const char *path; + svn_revnum_t revision; + svn_boolean_t is_ancestor; + apr_pool_t *lastpool, *currpool; + const svn_fs_id_t *id; + + SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t)); + + /* Ensure that FS_PATH is absolute, because our path-math below will + depend on that being the case. */ + if (*fs_path != '/') + fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL); + + /* Another sanity check. */ + if (authz_read_func) + { + svn_fs_root_t *peg_root; + SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool)); + SVN_ERR(check_readability(peg_root, fs_path, + authz_read_func, authz_read_baton, pool)); + } + + *locations = apr_hash_make(pool); + + /* We flip between two pools in the second loop below. */ + lastpool = svn_pool_create(pool); + currpool = svn_pool_create(pool); + + /* First - let's sort the array of the revisions from the greatest revision + * downward, so it will be easier to search on. */ + location_revisions = apr_array_copy(pool, location_revisions_orig); + qsort(location_revisions->elts, location_revisions->nelts, + sizeof(*revision_ptr), svn_sort_compare_revisions); + + revision_ptr = (svn_revnum_t *)location_revisions->elts; + revision_ptr_end = revision_ptr + location_revisions->nelts; + + /* Ignore revisions R that are younger than the peg_revisions where + path@peg_revision is not an ancestor of path@R. */ + is_ancestor = FALSE; + while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision) + { + svn_pool_clear(currpool); + SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path, + peg_revision, *revision_ptr, + currpool)); + if (is_ancestor) + break; + ++revision_ptr; + } + + revision = is_ancestor ? *revision_ptr : peg_revision; + path = fs_path; + if (authz_read_func) + { + SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool)); + SVN_ERR(check_readability(root, fs_path, authz_read_func, + authz_read_baton, pool)); + } + + while (revision_ptr < revision_ptr_end) + { + apr_pool_t *tmppool; + svn_revnum_t appeared_rev, prev_rev; + const char *prev_path; + + /* Find the target of the innermost copy relevant to path@revision. + The copy may be of path itself, or of a parent directory. */ + SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, + fs, revision, path, currpool)); + if (! prev_path) + break; + + if (authz_read_func) + { + svn_boolean_t readable; + svn_fs_root_t *tmp_root; + + SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool)); + SVN_ERR(authz_read_func(&readable, tmp_root, path, + authz_read_baton, currpool)); + if (! readable) + { + svn_pool_destroy(lastpool); + svn_pool_destroy(currpool); + + return SVN_NO_ERROR; + } + } + + /* Assign the current path to all younger revisions until we reach + the copy target rev. */ + while ((revision_ptr < revision_ptr_end) + && (*revision_ptr >= appeared_rev)) + { + /* *revision_ptr is allocated out of pool, so we can point + to in the hash table. */ + apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr), + apr_pstrdup(pool, path)); + revision_ptr++; + } + + /* Ignore all revs between the copy target rev and the copy + source rev (non-inclusive). */ + while ((revision_ptr < revision_ptr_end) + && (*revision_ptr > prev_rev)) + revision_ptr++; + + /* State update. */ + path = prev_path; + revision = prev_rev; + + /* Clear last pool and switch. */ + svn_pool_clear(lastpool); + tmppool = lastpool; + lastpool = currpool; + currpool = tmppool; + } + + /* There are no copies relevant to path@revision. So any remaining + revisions either predate the creation of path@revision or have + the node existing at the same path. We will look up path@lrev + for each remaining location-revision and make sure it is related + to path@revision. */ + SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool)); + SVN_ERR(svn_fs_node_id(&id, root, path, pool)); + while (revision_ptr < revision_ptr_end) + { + svn_node_kind_t kind; + const svn_fs_id_t *lrev_id; + + svn_pool_clear(currpool); + SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool)); + SVN_ERR(svn_fs_check_path(&kind, root, path, currpool)); + if (kind == svn_node_none) + break; + SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool)); + if (! svn_fs_check_related(id, lrev_id)) + break; + + /* The node exists at the same path; record that and advance. */ + apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr), + apr_pstrdup(pool, path)); + revision_ptr++; + } + + /* Ignore any remaining location-revisions; they predate the + creation of path@revision. */ + + svn_pool_destroy(lastpool); + svn_pool_destroy(currpool); + + return SVN_NO_ERROR; +} + + +/* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of + its revision range fits between END_REV and START_REV, possibly + cropping the range so that it fits *entirely* in that range. */ +static svn_error_t * +maybe_crop_and_send_segment(svn_location_segment_t *segment, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_location_segment_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + /* We only want to transmit this segment if some portion of it + is between our END_REV and START_REV. */ + if (! ((segment->range_start > start_rev) + || (segment->range_end < end_rev))) + { + /* Correct our segment range when the range straddles one of + our requested revision boundaries. */ + if (segment->range_start < end_rev) + segment->range_start = end_rev; + if (segment->range_end > start_rev) + segment->range_end = start_rev; + SVN_ERR(receiver(segment, receiver_baton, pool)); + } + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_repos_node_location_segments(svn_repos_t *repos, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_location_segment_receiver_t receiver, + void *receiver_baton, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + apr_pool_t *pool) +{ + svn_fs_t *fs = svn_repos_fs(repos); + svn_stringbuf_t *current_path; + svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev; + apr_pool_t *subpool; + + /* No PEG_REVISION? We'll use HEAD. */ + if (! SVN_IS_VALID_REVNUM(peg_revision)) + { + SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); + peg_revision = youngest_rev; + } + + /* No START_REV? We'll use HEAD (which we may have already fetched). */ + if (! SVN_IS_VALID_REVNUM(start_rev)) + { + if (SVN_IS_VALID_REVNUM(youngest_rev)) + start_rev = youngest_rev; + else + SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool)); + } + + /* No END_REV? We'll use 0. */ + end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0; + + /* Are the revision properly ordered? They better be -- the API + demands it. */ + SVN_ERR_ASSERT(end_rev <= start_rev); + SVN_ERR_ASSERT(start_rev <= peg_revision); + + /* Ensure that PATH is absolute, because our path-math will depend + on that being the case. */ + if (*path != '/') + path = apr_pstrcat(pool, "/", path, (char *)NULL); + + /* Auth check. */ + if (authz_read_func) + { + svn_fs_root_t *peg_root; + SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool)); + SVN_ERR(check_readability(peg_root, path, + authz_read_func, authz_read_baton, pool)); + } + + /* Okay, let's get searching! */ + subpool = svn_pool_create(pool); + current_rev = peg_revision; + current_path = svn_stringbuf_create(path, pool); + while (current_rev >= end_rev) + { + svn_revnum_t appeared_rev, prev_rev; + const char *cur_path, *prev_path; + svn_location_segment_t *segment; + + svn_pool_clear(subpool); + + cur_path = apr_pstrmemdup(subpool, current_path->data, + current_path->len); + segment = apr_pcalloc(subpool, sizeof(*segment)); + segment->range_end = current_rev; + segment->range_start = end_rev; + /* segment path should be absolute without leading '/'. */ + segment->path = cur_path + 1; + + SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, + fs, current_rev, cur_path, subpool)); + + /* If there are no previous locations for this thing (meaning, + it originated at the current path), then we simply need to + find its revision of origin to populate our final segment. + Otherwise, the APPEARED_REV is the start of current segment's + range. */ + if (! prev_path) + { + svn_fs_root_t *revroot; + SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool)); + SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot, + cur_path, subpool)); + if (segment->range_start < end_rev) + segment->range_start = end_rev; + current_rev = SVN_INVALID_REVNUM; + } + else + { + segment->range_start = appeared_rev; + svn_stringbuf_set(current_path, prev_path); + current_rev = prev_rev; + } + + /* Report our segment, providing it passes authz muster. */ + if (authz_read_func) + { + svn_boolean_t readable; + svn_fs_root_t *cur_rev_root; + + /* authz_read_func requires path to have a leading slash. */ + const char *abs_path = apr_pstrcat(subpool, "/", segment->path, + (char *)NULL); + + SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, + segment->range_end, subpool)); + SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path, + authz_read_baton, subpool)); + if (! readable) + return SVN_NO_ERROR; + } + + /* Transmit the segment (if it's within the scope of our concern). */ + SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev, + receiver, receiver_baton, subpool)); + + /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done + (and didn't ever reach END_REV). */ + if (! SVN_IS_VALID_REVNUM(current_rev)) + break; + + /* If there's a gap in the history, we need to report as much + (if the gap is within the scope of our concern). */ + if (segment->range_start - current_rev > 1) + { + svn_location_segment_t *gap_segment; + gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment)); + gap_segment->range_end = segment->range_start - 1; + gap_segment->range_start = current_rev + 1; + gap_segment->path = NULL; + SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev, + receiver, receiver_baton, + subpool)); + } + } + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + +/* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */ +static svn_error_t * +get_path_mergeinfo(apr_hash_t **mergeinfo, + svn_fs_t *fs, + const char *path, + svn_revnum_t revnum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_mergeinfo_catalog_t tmp_catalog; + svn_fs_root_t *root; + apr_array_header_t *paths = apr_array_make(scratch_pool, 1, + sizeof(const char *)); + + APR_ARRAY_PUSH(paths, const char *) = path; + + SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool)); + /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz) + because we will filter out unreadable revisions in + find_interesting_revision(), above */ + SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths, + svn_mergeinfo_inherited, FALSE, TRUE, + result_pool, scratch_pool)); + + *mergeinfo = svn_hash_gets(tmp_catalog, path); + if (!*mergeinfo) + *mergeinfo = apr_hash_make(result_pool); + + return SVN_NO_ERROR; +} + +static APR_INLINE svn_boolean_t +is_path_in_hash(apr_hash_t *duplicate_path_revs, + const char *path, + svn_revnum_t revision, + apr_pool_t *pool) +{ + const char *key = apr_psprintf(pool, "%s:%ld", path, revision); + void *ptr; + + ptr = svn_hash_gets(duplicate_path_revs, key); + return ptr != NULL; +} + +struct path_revision +{ + svn_revnum_t revnum; + const char *path; + + /* Does this path_rev have merges to also be included? */ + apr_hash_t *merged_mergeinfo; + + /* Is this a merged revision? */ + svn_boolean_t merged; +}; + +/* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store + the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The + returned *MERGED_MERGEINFO will be NULL if there are no changes. */ +static svn_error_t * +get_merged_mergeinfo(apr_hash_t **merged_mergeinfo, + svn_repos_t *repos, + struct path_revision *old_path_rev, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed; + svn_error_t *err; + svn_fs_root_t *root; + apr_hash_t *changed_paths; + const char *path = old_path_rev->path; + + /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it + if there is a property change. */ + SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum, + scratch_pool)); + SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool)); + while (1) + { + svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path); + if (changed_path && changed_path->prop_mod) + break; + if (svn_fspath__is_root(path, strlen(path))) + { + *merged_mergeinfo = NULL; + return SVN_NO_ERROR; + } + path = svn_fspath__dirname(path, scratch_pool); + } + + /* First, find the mergeinfo difference for old_path_rev->revnum, and + old_path_rev->revnum - 1. */ + err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path, + old_path_rev->revnum, scratch_pool, + scratch_pool); + if (err) + { + if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + { + /* Issue #3896: If invalid mergeinfo is encountered the + best we can do is ignore it and act is if there are + no mergeinfo differences. */ + svn_error_clear(err); + *merged_mergeinfo = NULL; + return SVN_NO_ERROR; + } + else + { + return svn_error_trace(err); + } + } + + err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path, + old_path_rev->revnum - 1, scratch_pool, + scratch_pool); + if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND + || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)) + { + /* If the path doesn't exist in the previous revision or it does exist + but has invalid mergeinfo (Issue #3896), assume no merges. */ + svn_error_clear(err); + *merged_mergeinfo = NULL; + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + /* Then calculate and merge the differences. */ + SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo, + curr_mergeinfo, FALSE, result_pool, + scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool)); + + /* Store the result. */ + if (apr_hash_count(changed)) + *merged_mergeinfo = changed; + else + *merged_mergeinfo = NULL; + + return SVN_NO_ERROR; +} + +static svn_error_t * +find_interesting_revisions(apr_array_header_t *path_revisions, + svn_repos_t *repos, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t include_merged_revisions, + svn_boolean_t mark_as_merged, + apr_hash_t *duplicate_path_revs, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool, *last_pool; + svn_fs_history_t *history; + svn_fs_root_t *root; + svn_node_kind_t kind; + + /* We switch between two pools while looping, since we need information from + the last iteration to be available. */ + iterpool = svn_pool_create(scratch_pool); + last_pool = svn_pool_create(scratch_pool); + + /* The path had better be a file in this revision. */ + SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool)); + SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool)); + if (kind != svn_node_file) + return svn_error_createf + (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"), + path, end); + + /* Open a history object. */ + SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool)); + while (1) + { + struct path_revision *path_rev; + svn_revnum_t tmp_revnum; + const char *tmp_path; + apr_pool_t *tmp_pool; + + svn_pool_clear(iterpool); + + /* Fetch the history object to walk through. */ + SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool)); + if (!history) + break; + SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum, + history, iterpool)); + + /* Check to see if we already saw this path (and it's ancestors) */ + if (include_merged_revisions + && is_path_in_hash(duplicate_path_revs, tmp_path, + tmp_revnum, iterpool)) + break; + + /* Check authorization. */ + if (authz_read_func) + { + svn_boolean_t readable; + svn_fs_root_t *tmp_root; + + SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum, + iterpool)); + SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path, + authz_read_baton, iterpool)); + if (! readable) + break; + } + + /* We didn't break, so we must really want this path-rev. */ + path_rev = apr_palloc(result_pool, sizeof(*path_rev)); + path_rev->path = apr_pstrdup(result_pool, tmp_path); + path_rev->revnum = tmp_revnum; + path_rev->merged = mark_as_merged; + APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev; + + if (include_merged_revisions) + SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos, + path_rev, result_pool, iterpool)); + else + path_rev->merged_mergeinfo = NULL; + + /* Add the path/rev pair to the hash, so we can filter out future + occurrences of it. We only care about this if including merged + revisions, 'cause that's the only time we can have duplicates. */ + svn_hash_sets(duplicate_path_revs, + apr_psprintf(result_pool, "%s:%ld", path_rev->path, + path_rev->revnum), + (void *)0xdeadbeef); + + if (path_rev->revnum <= start) + break; + + /* Swap pools. */ + tmp_pool = iterpool; + iterpool = last_pool; + last_pool = tmp_pool; + } + + svn_pool_destroy(iterpool); + svn_pool_destroy(last_pool); + + return SVN_NO_ERROR; +} + +/* Comparison function to sort path/revisions in increasing order */ +static int +compare_path_revisions(const void *a, const void *b) +{ + struct path_revision *a_pr = *(struct path_revision *const *)a; + struct path_revision *b_pr = *(struct path_revision *const *)b; + + if (a_pr->revnum == b_pr->revnum) + return 0; + + return a_pr->revnum < b_pr->revnum ? 1 : -1; +} + +static svn_error_t * +find_merged_revisions(apr_array_header_t **merged_path_revisions_out, + svn_revnum_t start, + const apr_array_header_t *mainline_path_revisions, + svn_repos_t *repos, + apr_hash_t *duplicate_path_revs, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *old; + apr_array_header_t *new_merged_path_revs; + apr_pool_t *iterpool, *last_pool; + apr_array_header_t *merged_path_revisions = + apr_array_make(scratch_pool, 0, sizeof(struct path_revision *)); + + old = mainline_path_revisions; + iterpool = svn_pool_create(scratch_pool); + last_pool = svn_pool_create(scratch_pool); + + do + { + int i; + apr_pool_t *temp_pool; + + svn_pool_clear(iterpool); + new_merged_path_revs = apr_array_make(iterpool, 0, + sizeof(struct path_revision *)); + + /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather + path_revisions for any merged revisions, and store those in NEW. */ + for (i = 0; i < old->nelts; i++) + { + apr_pool_t *iterpool2; + apr_hash_index_t *hi; + struct path_revision *old_pr = APR_ARRAY_IDX(old, i, + struct path_revision *); + if (!old_pr->merged_mergeinfo) + continue; + + iterpool2 = svn_pool_create(iterpool); + + /* Determine and trace the merge sources. */ + for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi; + hi = apr_hash_next(hi)) + { + apr_pool_t *iterpool3; + svn_rangelist_t *rangelist; + const char *path; + int j; + + svn_pool_clear(iterpool2); + iterpool3 = svn_pool_create(iterpool2); + + apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist); + + for (j = 0; j < rangelist->nelts; j++) + { + svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j, + svn_merge_range_t *); + svn_node_kind_t kind; + svn_fs_root_t *root; + + if (range->end < start) + continue; + + svn_pool_clear(iterpool3); + + SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end, + iterpool3)); + SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3)); + if (kind != svn_node_file) + continue; + + /* Search and find revisions to add to the NEW list. */ + SVN_ERR(find_interesting_revisions(new_merged_path_revs, + repos, path, + range->start, range->end, + TRUE, TRUE, + duplicate_path_revs, + authz_read_func, + authz_read_baton, + result_pool, iterpool3)); + } + svn_pool_destroy(iterpool3); + } + svn_pool_destroy(iterpool2); + } + + /* Append the newly found path revisions with the old ones. */ + merged_path_revisions = apr_array_append(iterpool, merged_path_revisions, + new_merged_path_revs); + + /* Swap data structures */ + old = new_merged_path_revs; + temp_pool = last_pool; + last_pool = iterpool; + iterpool = temp_pool; + } + while (new_merged_path_revs->nelts > 0); + + /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */ + qsort(merged_path_revisions->elts, merged_path_revisions->nelts, + sizeof(struct path_revision *), compare_path_revisions); + + /* Copy to the output array. */ + *merged_path_revisions_out = apr_array_copy(result_pool, + merged_path_revisions); + + svn_pool_destroy(iterpool); + svn_pool_destroy(last_pool); + + return SVN_NO_ERROR; +} + +struct send_baton +{ + apr_pool_t *iterpool; + apr_pool_t *last_pool; + apr_hash_t *last_props; + const char *last_path; + svn_fs_root_t *last_root; +}; + +/* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by + SB. */ +static svn_error_t * +send_path_revision(struct path_revision *path_rev, + svn_repos_t *repos, + struct send_baton *sb, + svn_file_rev_handler_t handler, + void *handler_baton) +{ + apr_hash_t *rev_props; + apr_hash_t *props; + apr_array_header_t *prop_diffs; + svn_fs_root_t *root; + svn_txdelta_stream_t *delta_stream; + svn_txdelta_window_handler_t delta_handler = NULL; + void *delta_baton = NULL; + apr_pool_t *tmp_pool; /* For swapping */ + svn_boolean_t contents_changed; + + svn_pool_clear(sb->iterpool); + + /* Get the revision properties. */ + SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs, + path_rev->revnum, sb->iterpool)); + + /* Open the revision root. */ + SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum, + sb->iterpool)); + + /* Get the file's properties for this revision and compute the diffs. */ + SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path, + sb->iterpool)); + SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props, + sb->iterpool)); + + /* Check if the contents changed. */ + /* Special case: In the first revision, we always provide a delta. */ + if (sb->last_root) + SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root, + sb->last_path, root, path_rev->path, + sb->iterpool)); + else + contents_changed = TRUE; + + /* We have all we need, give to the handler. */ + SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum, + rev_props, path_rev->merged, + contents_changed ? &delta_handler : NULL, + contents_changed ? &delta_baton : NULL, + prop_diffs, sb->iterpool)); + + /* Compute and send delta if client asked for it. + Note that this was initialized to NULL, so if !contents_changed, + no deltas will be computed. */ + if (delta_handler) + { + /* Get the content delta. */ + SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, + sb->last_root, sb->last_path, + root, path_rev->path, + sb->iterpool)); + /* And send. */ + SVN_ERR(svn_txdelta_send_txstream(delta_stream, + delta_handler, delta_baton, + sb->iterpool)); + } + + /* Remember root, path and props for next iteration. */ + sb->last_root = root; + sb->last_path = path_rev->path; + sb->last_props = props; + + /* Swap the pools. */ + tmp_pool = sb->iterpool; + sb->iterpool = sb->last_pool; + sb->last_pool = tmp_pool; + + return SVN_NO_ERROR; +} + +/* Similar to svn_repos_get_file_revs2() but returns paths while walking + history instead of after collecting all history. + + This allows implementing clients to immediately start processing and + stop when they got the information they need. (E.g. all or a specific set + of lines were modified) */ +static svn_error_t * +get_file_revs_backwards(svn_repos_t *repos, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + svn_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool, *last_pool; + svn_fs_history_t *history; + svn_fs_root_t *root; + svn_node_kind_t kind; + struct send_baton sb; + + /* We switch between two pools while looping and so does the path-rev + handler for actually reported revisions. We do this as we + need just information from last iteration to be available. */ + + iterpool = svn_pool_create(scratch_pool); + last_pool = svn_pool_create(scratch_pool); + sb.iterpool = svn_pool_create(scratch_pool); + sb.last_pool = svn_pool_create(scratch_pool); + + /* We want the first txdelta to be against the empty file. */ + sb.last_root = NULL; + sb.last_path = NULL; + + /* Create an empty hash table for the first property diff. */ + sb.last_props = apr_hash_make(sb.last_pool); + + /* The path had better be a file in this revision. */ + SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool)); + SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool)); + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_FS_NOT_FILE, + NULL, _("'%s' is not a file in revision %ld"), + path, end); + + /* Open a history object. */ + SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool)); + while (1) + { + struct path_revision *path_rev; + svn_revnum_t tmp_revnum; + const char *tmp_path; + + svn_pool_clear(iterpool); + + /* Fetch the history object to walk through. */ + SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool)); + if (!history) + break; + SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum, + history, iterpool)); + + /* Check authorization. */ + if (authz_read_func) + { + svn_boolean_t readable; + svn_fs_root_t *tmp_root; + + SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum, + iterpool)); + SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path, + authz_read_baton, iterpool)); + if (! readable) + break; + } + + /* We didn't break, so we must really want this path-rev. */ + path_rev = apr_palloc(iterpool, sizeof(*path_rev)); + path_rev->path = tmp_path; + path_rev->revnum = tmp_revnum; + path_rev->merged = FALSE; + + SVN_ERR(send_path_revision(path_rev, repos, &sb, + handler, handler_baton)); + + if (path_rev->revnum <= start) + break; + + /* Swap pools. */ + { + apr_pool_t *tmp_pool = iterpool; + iterpool = last_pool; + last_pool = tmp_pool; + } + } + + svn_pool_destroy(iterpool); + svn_pool_destroy(last_pool); + svn_pool_destroy(sb.last_pool); + svn_pool_destroy(sb.iterpool); + + return SVN_NO_ERROR; + +} + + +/* We don't yet support sending revisions in reverse order; the caller wait + * until we've traced back through the entire history, and then accept + * them from oldest to youngest. Someday this may change, but in the meantime, + * the general algorithm is thus: + * + * 1) Trace back through the history of an object, adding each revision + * found to the MAINLINE_PATH_REVISIONS array, marking any which were + * merges. + * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the + * merged revisions, including them in the MERGED_PATH_REVISIONS, and using + * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple + * times. + * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from + * oldest to youngest, interleaving as appropriate. This is implemented + * similar to an insertion sort, but instead of inserting into another + * array, we just call the appropriate handler. + * + * 2013-02: Added a very simple reverse for mainline only changes. Before this, + * this would return an error (path not found) or just the first + * revision before end. + */ +svn_error_t * +svn_repos_get_file_revs2(svn_repos_t *repos, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t include_merged_revisions, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + svn_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *mainline_path_revisions, *merged_path_revisions; + apr_hash_t *duplicate_path_revs; + struct send_baton sb; + int mainline_pos, merged_pos; + + if (end < start) + { + if (include_merged_revisions) + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); + + return svn_error_trace( + get_file_revs_backwards(repos, path, + end, start, + authz_read_func, + authz_read_baton, + handler, + handler_baton, + scratch_pool)); + } + + /* We switch between two pools while looping, since we need information from + the last iteration to be available. */ + sb.iterpool = svn_pool_create(scratch_pool); + sb.last_pool = svn_pool_create(scratch_pool); + + /* We want the first txdelta to be against the empty file. */ + sb.last_root = NULL; + sb.last_path = NULL; + + /* Create an empty hash table for the first property diff. */ + sb.last_props = apr_hash_make(sb.last_pool); + + + /* Get the revisions we are interested in. */ + duplicate_path_revs = apr_hash_make(scratch_pool); + mainline_path_revisions = apr_array_make(scratch_pool, 100, + sizeof(struct path_revision *)); + SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path, + start, end, include_merged_revisions, + FALSE, duplicate_path_revs, + authz_read_func, authz_read_baton, + scratch_pool, sb.iterpool)); + + /* If we are including merged revisions, go get those, too. */ + if (include_merged_revisions) + SVN_ERR(find_merged_revisions(&merged_path_revisions, start, + mainline_path_revisions, repos, + duplicate_path_revs, authz_read_func, + authz_read_baton, + scratch_pool, sb.iterpool)); + else + merged_path_revisions = apr_array_make(scratch_pool, 0, + sizeof(struct path_revision *)); + + /* We must have at least one revision to get. */ + SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0); + + /* Walk through both mainline and merged revisions, and send them in + reverse chronological order, interleaving as appropriate. */ + mainline_pos = mainline_path_revisions->nelts - 1; + merged_pos = merged_path_revisions->nelts - 1; + while (mainline_pos >= 0 && merged_pos >= 0) + { + struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions, + mainline_pos, + struct path_revision *); + struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions, + merged_pos, + struct path_revision *); + + if (main_pr->revnum <= merged_pr->revnum) + { + SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, + handler_baton)); + mainline_pos -= 1; + } + else + { + SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler, + handler_baton)); + merged_pos -= 1; + } + } + + /* Send any remaining revisions from the mainline list. */ + for (; mainline_pos >= 0; mainline_pos -= 1) + { + struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions, + mainline_pos, + struct path_revision *); + SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton)); + } + + /* Ditto for the merged list. */ + for (; merged_pos >= 0; merged_pos -= 1) + { + struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions, + merged_pos, + struct path_revision *); + SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler, + handler_baton)); + } + + svn_pool_destroy(sb.last_pool); + svn_pool_destroy(sb.iterpool); + + return SVN_NO_ERROR; +} |