| /* |
| * log.c -- Fetch log data and implement the log queries |
| * |
| * ==================================================================== |
| * 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 "svn_cmdline.h" |
| #include "svn_client.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_string.h" |
| #include "svn_path.h" |
| #include "svn_error.h" |
| #include "svn_sorts.h" |
| #include "svn_pools.h" |
| #include "svn_hash.h" |
| |
| #include "private/svn_fspath.h" |
| #include "private/svn_subr_private.h" |
| #include "private/svn_sorts_private.h" |
| |
| #include "mergeinfo-normalizer.h" |
| |
| #include "svn_private_config.h" |
| |
| |
| |
| /* Describes all changes of a single revision. |
| * Note that all strings are shared within a given svn_min__log_t instance. |
| */ |
| typedef struct log_entry_t |
| { |
| /* Revision being described. */ |
| svn_revnum_t revision; |
| |
| /* FS path that is equal or a parent of any in PATHS. */ |
| const char *common_base; |
| |
| /* Sorted list of all FS paths touched. Elements are const char*. */ |
| apr_array_header_t *paths; |
| } log_entry_t; |
| |
| /* Describes a deletion. |
| * Note that replacements are treated as additions + deletions. |
| */ |
| typedef struct deletion_t |
| { |
| /* Path being deleted (or replaced). */ |
| const char *path; |
| |
| /* Revision in which this deletion happened.*/ |
| svn_revnum_t revision; |
| } deletion_t; |
| |
| /* Note that all FS paths are internalized and shared within this object. |
| */ |
| struct svn_min__log_t |
| { |
| /* Dictionary of all FS paths used in this log. |
| * The hash itself is only temporary and will be destroyed after the log |
| * has been constructed and all paths got internalized. */ |
| apr_hash_t *unique_paths; |
| |
| /* Oldest revision we received. */ |
| svn_revnum_t first_rev; |
| |
| /* Latest revision we received. */ |
| svn_revnum_t head_rev; |
| |
| /* Log contents we received. Entries are log_entry_t *. */ |
| apr_array_header_t *entries; |
| |
| /* List of all copy operations we encountered, sorted by target&rev. */ |
| apr_array_header_t *copies; |
| |
| /* Like COPIES but sorted by source&source-rev. */ |
| apr_array_header_t *copies_by_source; |
| |
| /* List of all deletions we encountered, sorted by path&rev. */ |
| apr_array_header_t *deletions; |
| |
| /* If set, don't show progress nor summary. */ |
| svn_boolean_t quiet; |
| }; |
| |
| /* Comparison function defining the order in svn_min__log_t.COPIES. */ |
| static int |
| copy_order(const void *lhs, |
| const void *rhs) |
| { |
| const svn_min__copy_t *lhs_copy = *(const svn_min__copy_t * const *)lhs; |
| const svn_min__copy_t *rhs_copy = *(const svn_min__copy_t * const *)rhs; |
| |
| int diff = strcmp(lhs_copy->path, rhs_copy->path); |
| if (diff) |
| return diff; |
| |
| if (lhs_copy->revision < rhs_copy->revision) |
| return -1; |
| |
| return lhs_copy->revision == rhs_copy->revision ? 0 : 1; |
| } |
| |
| /* Comparison function defining the order in svn_min__log_t.COPIES_BY_SOURCE. |
| */ |
| static int |
| copy_by_source_order(const void *lhs, |
| const void *rhs) |
| { |
| const svn_min__copy_t *lhs_copy = *(const svn_min__copy_t * const *)lhs; |
| const svn_min__copy_t *rhs_copy = *(const svn_min__copy_t * const *)rhs; |
| |
| int diff = strcmp(lhs_copy->copyfrom_path, rhs_copy->copyfrom_path); |
| if (diff) |
| return diff; |
| |
| if (lhs_copy->copyfrom_revision < rhs_copy->copyfrom_revision) |
| return -1; |
| |
| return lhs_copy->copyfrom_revision == rhs_copy->copyfrom_revision ? 0 : 1; |
| } |
| |
| /* Comparison function defining the order in svn_min__log_t.DELETIONS. */ |
| static int |
| deletion_order(const void *lhs, |
| const void *rhs) |
| { |
| const deletion_t *lhs_deletion = *(const deletion_t * const *)lhs; |
| const deletion_t *rhs_deletion = *(const deletion_t * const *)rhs; |
| |
| int diff = strcmp(lhs_deletion->path, rhs_deletion->path); |
| if (diff) |
| return diff; |
| |
| if (lhs_deletion->revision < rhs_deletion->revision) |
| return -1; |
| |
| return lhs_deletion->revision == rhs_deletion->revision ? 0 : 1; |
| } |
| |
| /* Return the string stored in UNIQUE_PATHS with the value PATH of PATH_LEN |
| * characters. If the hash does not have a matching entry, add one. |
| * Allocate all strings in RESULT_POOL. */ |
| static const char * |
| internalize(apr_hash_t *unique_paths, |
| const char *path, |
| apr_ssize_t path_len, |
| apr_pool_t *result_pool) |
| { |
| const char *result = apr_hash_get(unique_paths, path, path_len); |
| if (result == NULL) |
| { |
| result = apr_pstrmemdup(result_pool, path, path_len); |
| apr_hash_set(unique_paths, result, path_len, result); |
| } |
| |
| return result; |
| } |
| |
| /* Implements svn_log_entry_receiver_t. Copies the info of LOG_ENTRY into |
| * (svn_min__log_t *)BATON. */ |
| static svn_error_t * |
| log_entry_receiver(void *baton, |
| svn_log_entry_t *log_entry, |
| apr_pool_t *scratch_pool) |
| { |
| svn_min__log_t *log = baton; |
| apr_pool_t *result_pool = log->entries->pool; |
| log_entry_t *entry; |
| apr_hash_index_t *hi; |
| const char *common_base; |
| int count; |
| |
| /* Don't care about empty revisions. Skip them. */ |
| if (!log_entry->changed_paths || !apr_hash_count(log_entry->changed_paths)) |
| return SVN_NO_ERROR; |
| |
| /* Copy changed paths list. Collect deletions and copies. */ |
| entry = apr_pcalloc(result_pool, sizeof(*entry)); |
| entry->revision = log_entry->revision; |
| entry->paths = apr_array_make(result_pool, |
| apr_hash_count(log_entry->changed_paths), |
| sizeof(const char *)); |
| |
| for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const char *path = apr_hash_this_key(hi); |
| svn_log_changed_path_t *change = apr_hash_this_val(hi); |
| |
| path = internalize(log->unique_paths, path, apr_hash_this_key_len(hi), |
| result_pool); |
| APR_ARRAY_PUSH(entry->paths, const char *) = path; |
| |
| if (change->action == 'D' || change->action == 'R') |
| { |
| deletion_t *deletion = apr_pcalloc(result_pool, sizeof(*deletion)); |
| deletion->path = path; |
| deletion->revision = log_entry->revision; |
| |
| APR_ARRAY_PUSH(log->deletions, deletion_t *) = deletion; |
| } |
| |
| if (SVN_IS_VALID_REVNUM(change->copyfrom_rev)) |
| { |
| svn_min__copy_t *copy = apr_pcalloc(result_pool, sizeof(*copy)); |
| copy->path = path; |
| copy->revision = log_entry->revision; |
| copy->copyfrom_path = internalize(log->unique_paths, |
| change->copyfrom_path, |
| strlen(change->copyfrom_path), |
| result_pool); |
| copy->copyfrom_revision = change->copyfrom_rev; |
| |
| APR_ARRAY_PUSH(log->copies, svn_min__copy_t *) = copy; |
| } |
| } |
| |
| /* Determine the common base of all changed paths. */ |
| count = entry->paths->nelts; |
| if (count == 1) |
| { |
| entry->common_base = APR_ARRAY_IDX(entry->paths, 0, const char *); |
| } |
| else |
| { |
| svn_sort__array(entry->paths, svn_sort_compare_paths); |
| |
| common_base = svn_dirent_get_longest_ancestor( |
| APR_ARRAY_IDX(entry->paths, 0, const char *), |
| APR_ARRAY_IDX(entry->paths, count - 1, const char *), |
| scratch_pool); |
| entry->common_base = internalize(log->unique_paths, common_base, |
| strlen(common_base), result_pool); |
| } |
| |
| /* Done with that reivison. */ |
| APR_ARRAY_PUSH(log->entries, log_entry_t *) = entry; |
| |
| /* Update log-global state. */ |
| log->first_rev = log_entry->revision; |
| if (log->head_rev == SVN_INVALID_REVNUM) |
| log->head_rev = log_entry->revision; |
| |
| /* Show progress. */ |
| if (log->entries->nelts % 1000 == 0 && !log->quiet) |
| { |
| SVN_ERR(svn_cmdline_printf(scratch_pool, ".")); |
| SVN_ERR(svn_cmdline_fflush(stdout)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Print some statistics about LOG to console. Use SCRATCH_POOL for |
| * temporary allocations. */ |
| static svn_error_t * |
| print_log_stats(svn_min__log_t *log, |
| apr_pool_t *scratch_pool) |
| { |
| int change_count = 0; |
| |
| int i; |
| for (i = 0; i < log->entries->nelts; ++i) |
| { |
| const log_entry_t *entry = APR_ARRAY_IDX(log->entries, i, |
| const log_entry_t *); |
| change_count += entry->paths->nelts; |
| } |
| |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _(" Received %d revisions from %ld to %ld.\n"), |
| log->entries->nelts, log->first_rev, |
| log->head_rev)); |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _(" Received %d path changes.\n"), |
| change_count)); |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _(" Pool has %u different paths.\n\n"), |
| apr_hash_count(log->unique_paths))); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_min__log(svn_min__log_t **log, |
| const char *url, |
| svn_min__cmd_baton_t *baton, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_client_ctx_t *ctx = baton->ctx; |
| svn_min__log_t *result; |
| |
| /* Prepare API parameters for fetching the full log for URL, |
| * including changed paths, excluding revprops. |
| */ |
| apr_array_header_t *targets; |
| apr_array_header_t *revisions; |
| apr_array_header_t *revprops; |
| svn_opt_revision_t peg_revision = { svn_opt_revision_head }; |
| svn_opt_revision_range_t range = { { svn_opt_revision_unspecified }, |
| { svn_opt_revision_unspecified } }; |
| |
| targets = apr_array_make(scratch_pool, 1, sizeof(const char *)); |
| APR_ARRAY_PUSH(targets, const char *) = url; |
| |
| revisions = apr_array_make(scratch_pool, 1, sizeof(&range)); |
| APR_ARRAY_PUSH(revisions, svn_opt_revision_range_t *) = ⦥ |
| |
| revprops = apr_array_make(scratch_pool, 0, sizeof(const char *)); |
| |
| /* The log object to fill. */ |
| result = apr_pcalloc(result_pool, sizeof(*result)); |
| result->unique_paths = svn_hash__make(scratch_pool); |
| result->first_rev = SVN_INVALID_REVNUM; |
| result->head_rev = SVN_INVALID_REVNUM; |
| result->entries = apr_array_make(result_pool, 1024, sizeof(log_entry_t *)); |
| result->copies = apr_array_make(result_pool, 1024, |
| sizeof(svn_min__copy_t *)); |
| result->deletions = apr_array_make(result_pool, 1024, sizeof(deletion_t *)); |
| result->quiet = baton->opt_state->quiet; |
| |
| if (!baton->opt_state->quiet) |
| { |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _("Fetching log for %s ..."), |
| url)); |
| SVN_ERR(svn_cmdline_fflush(stdout)); |
| } |
| |
| SVN_ERR(svn_client_log5(targets, |
| &peg_revision, |
| revisions, |
| 0, /* no limit */ |
| TRUE, /* verbose */ |
| TRUE, /* stop-on-copy */ |
| FALSE, /* merge history */ |
| revprops, |
| log_entry_receiver, |
| result, |
| ctx, |
| scratch_pool)); |
| |
| /* Complete arrays in RESULT. */ |
| result->copies_by_source = apr_array_copy(result_pool, result->copies); |
| |
| svn_sort__array_reverse(result->entries, scratch_pool); |
| svn_sort__array(result->copies, copy_order); |
| svn_sort__array(result->copies_by_source, copy_by_source_order); |
| svn_sort__array(result->deletions, deletion_order); |
| |
| /* Show that we are done. */ |
| if (!baton->opt_state->quiet) |
| { |
| SVN_ERR(svn_cmdline_printf(scratch_pool, "\n")); |
| SVN_ERR(print_log_stats(result, scratch_pool)); |
| } |
| |
| result->unique_paths = NULL; |
| *log = result; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Append REVISION with the INHERITABLE setting to RANGES. RANGES must be |
| * sorted and REVISION must be larger than the largest revision in RANGES. */ |
| static void |
| append_rev_to_ranges(svn_rangelist_t *ranges, |
| svn_revnum_t revision, |
| svn_boolean_t inheritable) |
| { |
| /* In many cases, we can save memory by simply extending the last range. */ |
| svn_merge_range_t *range; |
| if (ranges->nelts) |
| { |
| range = APR_ARRAY_IDX(ranges, ranges->nelts - 1, svn_merge_range_t *); |
| if (range->end + 1 == revision && range->inheritable == inheritable) |
| { |
| range->end = revision; |
| return; |
| } |
| } |
| |
| /* We need to add a new range. */ |
| range = apr_pcalloc(ranges->pool, sizeof(*range)); |
| range->start = revision - 1; |
| range->end = revision; |
| range->inheritable = inheritable; |
| |
| APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range; |
| } |
| |
| /* Comparison function comparing the log_entry_t * in *LHS with the |
| * svn_revnum_t in *rhs. |
| */ |
| static int |
| compare_rev_log_entry(const void *lhs, |
| const void *rhs) |
| { |
| const log_entry_t *entry = *(const log_entry_t * const *)lhs; |
| svn_revnum_t revision = *(const svn_revnum_t *)rhs; |
| |
| if (entry->revision < revision) |
| return -1; |
| |
| return entry->revision == revision ? 0 : 1; |
| } |
| |
| /* Restrict RANGE to the range of revisions covered by LOG. Cut-off from |
| * both sides will be added to RANGES. */ |
| static void |
| restrict_range(svn_min__log_t *log, |
| svn_merge_range_t *range, |
| svn_rangelist_t *ranges) |
| { |
| /* Cut off at the earliest revision. */ |
| if (range->start + 1 < log->first_rev) |
| { |
| svn_merge_range_t *new_range |
| = apr_pmemdup(ranges->pool, range, sizeof(*range)); |
| new_range->end = MIN(new_range->end, log->first_rev - 1); |
| |
| APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = new_range; |
| range->start = new_range->end; |
| } |
| |
| /* Cut off at log HEAD. */ |
| if (range->end > log->head_rev) |
| { |
| svn_merge_range_t *new_range |
| = apr_pmemdup(ranges->pool, range, sizeof(*range)); |
| new_range->start = log->head_rev; |
| |
| APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = new_range; |
| range->end = new_range->start; |
| } |
| } |
| |
| /* Return TRUE if PATH is either equal to, a parent of or sub-path of |
| * CHANGED_PATH. */ |
| static svn_boolean_t |
| is_relevant(const char *changed_path, |
| const char *path) |
| { |
| return svn_dirent_is_ancestor(changed_path, path) |
| || svn_dirent_is_ancestor(path, changed_path); |
| } |
| |
| /* Return TRUE if PATH is either equal to, a parent of or sub-path of |
| * SUB_TREE. Ignore BATON but keep it for a unified signature to be |
| * used with filter_ranges. */ |
| static svn_boolean_t |
| in_subtree(const char *changed_path, |
| const char *sub_tree, |
| const void *baton) |
| { |
| return svn_dirent_is_ancestor(sub_tree, changed_path); |
| } |
| |
| /* Return TRUE if |
| * - CHANGED_PATH is is either equal to or a sub-node of PATH, and |
| * - CHNAGED_PATH is outside the sub-tree given as BATON. */ |
| static svn_boolean_t |
| below_path_outside_subtree(const char *changed_path, |
| const char *path, |
| const void *baton) |
| { |
| const char *subtree = baton; |
| |
| /* Is this a change _below_ PATH but not within SUBTREE? */ |
| return !svn_dirent_is_ancestor(subtree, changed_path) |
| && svn_dirent_is_ancestor(path, changed_path) |
| && strcmp(path, changed_path); |
| } |
| |
| /* In LOG, scan the revisions given in RANGES and return the revision / |
| * ranges that are relevant to PATH with respect to the PATH_RELEVANT |
| * criterion using BATON. Keep revisions that lie outside what is covered |
| * by LOG. Allocate the result in RESULT_POOL. */ |
| static svn_rangelist_t * |
| filter_ranges(svn_min__log_t *log, |
| const char *path, |
| svn_rangelist_t *ranges, |
| svn_boolean_t (*path_relavent)(const char*, const char *, |
| const void *), |
| const void *baton, |
| apr_pool_t *result_pool) |
| { |
| svn_rangelist_t *result; |
| int i, k, l; |
| |
| /* Auto-complete parameters. */ |
| if (!SVN_IS_VALID_REVNUM(log->first_rev)) |
| return svn_rangelist_dup(ranges, result_pool); |
| |
| result = apr_array_make(result_pool, 0, ranges->elt_size); |
| for (i = 0; i < ranges->nelts; ++i) |
| { |
| /* Next revision range to scan. */ |
| svn_merge_range_t range |
| = *APR_ARRAY_IDX(ranges, i, const svn_merge_range_t *); |
| restrict_range(log, &range, result); |
| |
| /* Find the range start and scan the range linearly. */ |
| ++range.start; |
| for (k = svn_sort__bsearch_lower_bound(log->entries, &range.start, |
| compare_rev_log_entry); |
| k < log->entries->nelts; |
| ++k) |
| { |
| const log_entry_t *entry = APR_ARRAY_IDX(log->entries, k, |
| const log_entry_t *); |
| if (entry->revision > range.end) |
| break; |
| |
| /* Skip revisions no relevant to PATH. */ |
| if (!is_relevant(entry->common_base, path)) |
| continue; |
| |
| /* Look for any changed path that meets the filter criterion. */ |
| for (l = 0; l < entry->paths->nelts; ++l) |
| { |
| const char *changed_path |
| = APR_ARRAY_IDX(entry->paths, l, const char *); |
| |
| if (path_relavent(changed_path, path, baton)) |
| { |
| append_rev_to_ranges(result, entry->revision, |
| range.inheritable); |
| break; |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| svn_rangelist_t * |
| svn_min__operative(svn_min__log_t *log, |
| const char *path, |
| svn_rangelist_t *ranges, |
| apr_pool_t *result_pool) |
| { |
| return filter_ranges(log, path, ranges, in_subtree, NULL, result_pool); |
| } |
| |
| svn_rangelist_t * |
| svn_min__operative_outside_subtree(svn_min__log_t *log, |
| const char *path, |
| const char *subtree, |
| svn_rangelist_t *ranges, |
| apr_pool_t *result_pool) |
| { |
| return filter_ranges(log, path, ranges, below_path_outside_subtree, |
| subtree, result_pool); |
| } |
| |
| svn_revnum_t |
| svn_min__find_deletion(svn_min__log_t *log, |
| const char *path, |
| svn_revnum_t start_rev, |
| svn_revnum_t end_rev, |
| apr_pool_t *scratch_pool) |
| { |
| svn_revnum_t latest = SVN_INVALID_REVNUM; |
| |
| deletion_t *to_find = apr_pcalloc(scratch_pool, sizeof(*to_find)); |
| to_find->path = path; |
| to_find->revision = end_rev; |
| |
| /* Auto-complete parameters. */ |
| if (!SVN_IS_VALID_REVNUM(start_rev)) |
| start_rev = log->head_rev; |
| |
| /* Walk up the tree and find the latest deletion of PATH or any of |
| * its parents. */ |
| while (!svn_fspath__is_root(to_find->path, strlen(to_find->path))) |
| { |
| int i; |
| for (i = svn_sort__bsearch_lower_bound(log->deletions, &to_find, |
| deletion_order); |
| i < log->deletions->nelts; |
| ++i) |
| { |
| const deletion_t *deletion = APR_ARRAY_IDX(log->deletions, i, |
| const deletion_t *); |
| if (strcmp(deletion->path, to_find->path)) |
| break; |
| if (deletion->revision > start_rev) |
| break; |
| |
| latest = deletion->revision; |
| to_find->revision = deletion->revision; |
| } |
| |
| to_find->path = svn_fspath__dirname(to_find->path, scratch_pool); |
| } |
| |
| return latest; |
| } |
| |
| apr_array_header_t * |
| svn_min__find_deletions(svn_min__log_t *log, |
| const char *path, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_array_header_t *result = apr_array_make(result_pool, 0, |
| sizeof(svn_revnum_t)); |
| int source, dest; |
| |
| deletion_t *to_find = apr_pcalloc(scratch_pool, sizeof(*to_find)); |
| to_find->path = path; |
| to_find->revision = 0; |
| |
| /* Find deletions for PATH and its parents. */ |
| if (!svn_fspath__is_root(to_find->path, strlen(to_find->path))) |
| { |
| int i; |
| for (i = svn_sort__bsearch_lower_bound(log->deletions, &to_find, |
| deletion_order); |
| i < log->deletions->nelts; |
| ++i) |
| { |
| const deletion_t *deletion = APR_ARRAY_IDX(log->deletions, i, |
| const deletion_t *); |
| if (strcmp(deletion->path, to_find->path)) |
| break; |
| |
| APR_ARRAY_PUSH(result, svn_revnum_t) = deletion->revision; |
| } |
| |
| to_find->path = svn_fspath__dirname(to_find->path, scratch_pool); |
| } |
| |
| /* Remove any duplicates (unlikely but possible). */ |
| svn_sort__array(result, svn_sort_compare_revisions); |
| for (source = 1, dest = 0; source < result->nelts; ++source) |
| { |
| svn_revnum_t source_rev = APR_ARRAY_IDX(result, source, svn_revnum_t); |
| svn_revnum_t dest_rev = APR_ARRAY_IDX(result, dest, svn_revnum_t); |
| if (source_rev != dest_rev) |
| { |
| ++dest_rev; |
| APR_ARRAY_IDX(result, dest, svn_revnum_t) = source_rev; |
| } |
| } |
| |
| if (result->nelts) |
| result->nelts = dest + 1; |
| |
| return result; |
| } |
| |
| /* Starting at REVISION, scan LOG for the next (in REVISION or older) copy |
| * that creates PATH explicitly or implicitly by creating a parent of it. |
| * Return the copy operation found or NULL if none exists. Use SCRATCH_POOL |
| * for temporary allocations. */ |
| static const svn_min__copy_t * |
| next_copy(svn_min__log_t *log, |
| const char *path, |
| svn_revnum_t revision, |
| apr_pool_t *scratch_pool) |
| { |
| const svn_min__copy_t *copy = NULL; |
| int idx; |
| |
| svn_min__copy_t *to_find = apr_pcalloc(scratch_pool, sizeof(*to_find)); |
| to_find->path = path; |
| to_find->revision = revision; |
| |
| idx = svn_sort__bsearch_lower_bound(log->copies, &to_find, copy_order); |
| if (idx < log->copies->nelts) |
| { |
| /* Found an exact match? */ |
| copy = APR_ARRAY_IDX(log->copies, idx, const svn_min__copy_t *); |
| if (copy->revision != revision || strcmp(copy->path, path)) |
| copy = NULL; |
| } |
| |
| if (!copy && idx > 0) |
| { |
| /* No exact match. The predecessor may be the closest copy. */ |
| copy = APR_ARRAY_IDX(log->copies, idx - 1, const svn_min__copy_t *); |
| if (strcmp(copy->path, path)) |
| copy = NULL; |
| } |
| |
| /* Mabye, the parent folder got copied later, i.e. is the closest copy. |
| We implicitly recurse up the tree. */ |
| if (!svn_fspath__is_root(to_find->path, strlen(to_find->path))) |
| { |
| const svn_min__copy_t *parent_copy; |
| to_find->path = svn_fspath__dirname(to_find->path, scratch_pool); |
| |
| parent_copy = next_copy(log, to_find->path, revision, scratch_pool); |
| if (!copy) |
| copy = parent_copy; |
| else if (parent_copy && parent_copy->revision > copy->revision) |
| copy = parent_copy; |
| } |
| |
| return copy; |
| } |
| |
| svn_revnum_t |
| svn_min__find_copy(svn_min__log_t *log, |
| const char *path, |
| svn_revnum_t start_rev, |
| svn_revnum_t end_rev, |
| apr_pool_t *scratch_pool) |
| { |
| const svn_min__copy_t *copy; |
| |
| /* Auto-complete parameters. */ |
| if (!SVN_IS_VALID_REVNUM(start_rev)) |
| start_rev = log->head_rev; |
| |
| /* The actual lookup. */ |
| copy = next_copy(log, path, start_rev, scratch_pool); |
| if (copy && copy->revision >= end_rev) |
| return copy->revision; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| apr_array_header_t * |
| svn_min__get_copies(svn_min__log_t *log, |
| const char *path, |
| svn_revnum_t start_rev, |
| svn_revnum_t end_rev, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_array_header_t *result = apr_array_make(result_pool, 0, |
| sizeof(svn_min__copy_t *)); |
| const svn_min__copy_t **copies = (void *)log->copies_by_source->elts; |
| int idx; |
| |
| /* Find all sub-tree copies, including PATH. */ |
| svn_min__copy_t *to_find = apr_pcalloc(scratch_pool, sizeof(*to_find)); |
| to_find->copyfrom_path = path; |
| to_find->copyfrom_revision = end_rev; |
| |
| for (idx = svn_sort__bsearch_lower_bound(log->copies_by_source, |
| &to_find, |
| copy_by_source_order); |
| (idx < log->copies->nelts) |
| && svn_dirent_is_ancestor(path, copies[idx]->copyfrom_path); |
| ++idx) |
| { |
| if (copies[idx]->copyfrom_revision <= start_rev) |
| APR_ARRAY_PUSH(result, const svn_min__copy_t *) = copies[idx]; |
| } |
| |
| /* Find all parent copies. */ |
| while (!svn_fspath__is_root(to_find->copyfrom_path, |
| strlen(to_find->copyfrom_path))) |
| { |
| to_find->copyfrom_path = svn_fspath__dirname(to_find->copyfrom_path, |
| scratch_pool); |
| |
| for (idx = svn_sort__bsearch_lower_bound(log->copies_by_source, |
| &to_find, |
| copy_by_source_order); |
| (idx < log->copies->nelts) |
| && !strcmp(copies[idx]->copyfrom_path, to_find->copyfrom_path) |
| && (copies[idx]->copyfrom_revision <= start_rev); |
| ++idx) |
| { |
| APR_ARRAY_PUSH(result, const svn_min__copy_t *) = copies[idx]; |
| } |
| } |
| |
| return result; |
| } |
| |
| /* A history segment. Simply a FS path plus the revision range that it is |
| * part of the history of the node. */ |
| typedef struct segment_t |
| { |
| /* FS path at which the node lives in this segment */ |
| const char *path; |
| |
| /* Revision that it appears in or that the history was truncated to. */ |
| svn_revnum_t start; |
| |
| /* Revision from which the node was copied to the next segment or the |
| * revision that the history was truncated to. */ |
| svn_revnum_t end; |
| } segment_t; |
| |
| apr_array_header_t * |
| svn_min__get_history(svn_min__log_t *log, |
| const char *path, |
| svn_revnum_t start_rev, |
| svn_revnum_t end_rev, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| segment_t *segment; |
| const svn_min__copy_t *copy; |
| apr_array_header_t *result = apr_array_make(result_pool, 16, |
| sizeof(segment_t *)); |
| |
| /* Auto-complete parameters. */ |
| if (!SVN_IS_VALID_REVNUM(start_rev)) |
| start_rev = log->head_rev; |
| |
| /* Simply follow all copies, each time adding a segment from "here" to |
| * the next copy. */ |
| for (copy = next_copy(log, path, start_rev, scratch_pool); |
| copy && start_rev >= end_rev; |
| copy = next_copy(log, path, start_rev, scratch_pool)) |
| { |
| segment = apr_pcalloc(result_pool, sizeof(*segment)); |
| segment->start = MAX(end_rev, copy->revision); |
| segment->end = start_rev; |
| segment->path = apr_pstrdup(result_pool, path); |
| |
| APR_ARRAY_PUSH(result, segment_t *) = segment; |
| |
| start_rev = copy->copyfrom_revision; |
| path = svn_fspath__join(copy->copyfrom_path, |
| svn_fspath__skip_ancestor(copy->path, path), |
| scratch_pool); |
| } |
| |
| /* The final segment has no copy-from. */ |
| if (start_rev >= end_rev) |
| { |
| segment = apr_pcalloc(result_pool, sizeof(*segment)); |
| segment->start = end_rev; |
| segment->end = start_rev; |
| segment->path = apr_pstrdup(result_pool, path); |
| |
| APR_ARRAY_PUSH(result, segment_t *) = segment; |
| } |
| |
| return result; |
| } |
| |
| apr_array_header_t * |
| svn_min__intersect_history(apr_array_header_t *lhs, |
| apr_array_header_t *rhs, |
| apr_pool_t *result_pool) |
| { |
| apr_array_header_t *result = apr_array_make(result_pool, 16, |
| sizeof(segment_t *)); |
| |
| int lhs_idx = 0; |
| int rhs_idx = 0; |
| |
| /* Careful: the segments are ordered latest to oldest. */ |
| while (lhs_idx < lhs->nelts && rhs_idx < rhs->nelts) |
| { |
| segment_t *lhs_segment = APR_ARRAY_IDX(lhs, lhs_idx, segment_t *); |
| segment_t *rhs_segment = APR_ARRAY_IDX(rhs, rhs_idx, segment_t *); |
| |
| /* Skip non-overlapping revision segments */ |
| if (lhs_segment->start > rhs_segment->end) |
| { |
| ++lhs_idx; |
| continue; |
| } |
| else if (lhs_segment->end < rhs_segment->start) |
| { |
| ++rhs_idx; |
| continue; |
| } |
| |
| /* Revision ranges overlap. Also the same path? */ |
| if (!strcmp(lhs_segment->path, rhs_segment->path)) |
| { |
| segment_t *segment = apr_pcalloc(result_pool, sizeof(*segment)); |
| segment->start = MAX(lhs_segment->start, rhs_segment->start); |
| segment->end = MIN(lhs_segment->end, rhs_segment->end); |
| segment->path = apr_pstrdup(result_pool, lhs_segment->path); |
| |
| APR_ARRAY_PUSH(result, segment_t *) = segment; |
| } |
| |
| /* The segment that starts earlier may overlap with another one. |
| If they should start at the same rev, the next iteration will |
| skip the respective other segment. */ |
| if (lhs_segment->start > rhs_segment->start) |
| ++lhs_idx; |
| else |
| ++rhs_idx; |
| } |
| |
| return result; |
| } |
| |
| svn_rangelist_t * |
| svn_min__history_ranges(apr_array_header_t *history, |
| apr_pool_t *result_pool) |
| { |
| svn_rangelist_t *result = apr_array_make(result_pool, history->nelts, |
| sizeof(svn_merge_range_t *)); |
| |
| int i; |
| for (i = 0; i < history->nelts; ++i) |
| { |
| const segment_t *segment = APR_ARRAY_IDX(history, i, segment_t *); |
| |
| /* Convert to merge ranges. Note that start+1 is the first rev |
| actually in that range. */ |
| svn_merge_range_t *range = apr_pcalloc(result_pool, sizeof(*range)); |
| range->start = MAX(0, segment->start - 1); |
| range->end = segment->end; |
| range->inheritable = TRUE; |
| |
| APR_ARRAY_PUSH(result, svn_merge_range_t *) = range; |
| } |
| |
| return result; |
| } |